core: cleaned up bootstrap process
Juan Batiz-Benet committed
Jan 23, 2015 at 04:36 UTC
95d58b2a4a79c308def0afb67d9688bb33ee46cb
5 files changed
+211
-221
core/bootstrap.go
+93
-91
@@ -3,6 +3,8 @@ package core
3
import (
4
"errors"
5
"fmt"
6
+ "io"
7
+ "io/ioutil"
8
"math/rand"
9
"sync"
10
"time"
@@ -18,6 +20,7 @@ import (
20
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
21
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
22
goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
23
+ procctx "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
24
periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
25
)
26
@@ -25,128 +28,116 @@ import (
28
// peers to bootstrap correctly.
29
var ErrNotEnoughBootstrapPeers = errors.New("not enough bootstrap peers to bootstrap")
30
28
-const (
29
- // BootstrapPeriod governs the periodic interval at which the node will
30
- // attempt to bootstrap. The bootstrap process is not very expensive, so
31
- // this threshold can afford to be small (<=30s).
32
- BootstrapPeriod = 30 * time.Second
31
+// BootstrapConfig specifies parameters used in an IpfsNode's network
32
+// bootstrapping process.
33
+type BootstrapConfig struct {
34
34
- // BootstrapPeerThreshold governs the node Bootstrap process. If the node
35
- // has less open connections than this number, it will open connections
35
+ // MinPeerThreshold governs whether to bootstrap more connections. If the
36
+ // node has less open connections than this number, it will open connections
37
// to the bootstrap nodes. From there, the routing system should be able
38
// to use the connections to the bootstrap nodes to connect to even more
39
// peers. Routing systems like the IpfsDHT do so in their own Bootstrap
40
// process, which issues random queries to find more peers.
40
- BootstrapPeerThreshold = 4
41
+ MinPeerThreshold int
42
+
43
+ // Period governs the periodic interval at which the node will
44
+ // attempt to bootstrap. The bootstrap process is not very expensive, so
45
+ // this threshold can afford to be small (<=30s).
46
+ Period time.Duration
47
42
- // BootstrapConnectionTimeout determines how long to wait for a bootstrap
48
+ // ConnectionTimeout determines how long to wait for a bootstrap
49
// connection attempt before cancelling it.
44
- BootstrapConnectionTimeout time.Duration = BootstrapPeriod / 3
45
-)
50
+ ConnectionTimeout time.Duration
51
+
52
+ // BootstrapPeers is a function that returns a set of bootstrap peers
53
+ // for the bootstrap process to use. This makes it possible for clients
54
+ // to control the peers the process uses at any moment.
55
+ BootstrapPeers func() []peer.PeerInfo
56
+}
57
47
-// nodeBootstrapper is a small object used to bootstrap an IpfsNode.
48
-type nodeBootstrapper struct {
49
- node *IpfsNode
58
+// DefaultBootstrapConfig specifies default sane parameters for bootstrapping.
59
+var DefaultBootstrapConfig = BootstrapConfig{
60
+ MinPeerThreshold: 4,
61
+ Period: 30 * time.Second,
62
+ ConnectionTimeout: (30 * time.Second) / 3, // Perod / 3
63
}
64
52
-// TryToBootstrap starts IpfsNode bootstrapping. This function will run an
53
-// initial bootstrapping phase before exiting: connect to several bootstrap
54
-// nodes. This allows callers to call this function synchronously to:
55
-// - check if an error occurrs (bootstrapping unsuccessful)
56
-// - wait before starting services which require the node to be bootstrapped
57
-//
58
-// If bootstrapping initially fails, Bootstrap() will try again for a total of
59
-// three times, before giving up completely. Note that in environments where a
60
-// node may be initialized offline, as normal operation, BootstrapForever()
61
-// should be used instead.
62
-//
63
-// Note: this function could be much cleaner if we were to relax the constraint
64
-// that we want to exit **after** we have performed initial bootstrapping (and are
65
-// thus connected to nodes). The constraint may not be that useful in practice.
66
-// Consider cases when we initialize the node while disconnected from the internet.
67
-// We don't want this launch to fail... want to continue launching the node, hoping
68
-// that bootstrapping will work in the future if we get connected.
69
-func (nb *nodeBootstrapper) TryToBootstrap(ctx context.Context, peers []peer.PeerInfo) error {
70
- n := nb.node
65
+func BootstrapConfigWithPeers(pis []peer.PeerInfo) BootstrapConfig {
66
+ cfg := DefaultBootstrapConfig
67
+ cfg.BootstrapPeers = func() []peer.PeerInfo {
68
+ return pis
69
+ }
70
+ return cfg
71
+}
72
+
73
+// Bootstrap kicks off IpfsNode bootstrapping. This function will periodically
74
+// check the number of open connections and -- if there are too few -- initiate
75
+// connections to well-known bootstrap peers. It also kicks off subsystem
76
+// bootstrapping (i.e. routing).
77
+func Bootstrap(n *IpfsNode, cfg BootstrapConfig) (io.Closer, error) {
78
79
// TODO what bootstrapping should happen if there is no DHT? i.e. we could
80
// continue connecting to our bootstrap peers, but for what purpose? for now
81
// simply exit without connecting to any of them. When we introduce another
82
// routing system that uses bootstrap peers we can change this.
76
- dht, ok := n.Routing.(*dht.IpfsDHT)
83
+ thedht, ok := n.Routing.(*dht.IpfsDHT)
84
if !ok {
78
- return nil
85
+ return ioutil.NopCloser(nil), nil
86
}
87
81
- for i := 0; i < 3; i++ {
82
- if err := bootstrapRound(ctx, n.PeerHost, dht, n.Peerstore, peers); err != nil {
83
- return err
88
+ // the periodic bootstrap function -- the connection supervisor
89
+ periodic := func(worker goprocess.Process) {
90
+ ctx := procctx.WithProcessClosing(context.Background(), worker)
91
+ defer log.EventBegin(ctx, "periodicBootstrap", n.Identity).Done()
92
+
93
+ if err := bootstrapRound(ctx, n.PeerHost, thedht, n.Peerstore, cfg); err != nil {
94
+ log.Event(ctx, "bootstrapError", n.Identity, lgbl.Error(err))
95
+ log.Errorf("%s bootstrap error: %s", n.Identity, err)
96
}
97
}
98
87
- // at this point we have done at least one round of initial bootstrap.
88
- // we're ready to kick off dht bootstrapping.
89
- dbproc, err := dht.Bootstrap(ctx)
99
+ // kick off the node's periodic bootstrapping
100
+ proc := periodicproc.Tick(cfg.Period, periodic)
101
+ proc.Go(periodic) // run one right now.
102
+
103
+ // kick off dht bootstrapping.
104
+ dbproc, err := thedht.Bootstrap(dht.DefaultBootstrapConfig)
105
if err != nil {
91
- return err
106
+ proc.Close()
107
+ return nil, err
108
}
109
94
- // kick off the node's periodic bootstrapping
95
- proc := periodicproc.Tick(BootstrapPeriod, func(worker goprocess.Process) {
96
- defer log.EventBegin(ctx, "periodicBootstrap", n.Identity).Done()
97
- if err := bootstrapRound(ctx, n.PeerHost, dht, n.Peerstore, peers); err != nil {
98
- log.Error(err)
99
- }
100
- })
101
-
110
// add dht bootstrap proc as a child, so it is closed automatically when we are.
111
proc.AddChild(dbproc)
104
-
105
- // we were given a context. instead of returning proc for the caller
106
- // to manage, for now we just close the proc when context is done.
107
- go func() {
108
- <-ctx.Done()
109
- proc.Close()
110
- }()
111
- return nil
112
-}
113
-
114
-// BootstrapForever starts IpfsNode bootstrapping. Unlike TryToBootstrap(),
115
-// BootstrapForever() will run indefinitely (until its context is cancelled).
116
-// This is particularly useful for the daemon and other services, which may
117
-// be started offline and will come online at a future date.
118
-//
119
-// TODO: check offline --to--> online case works well and doesn't hurt perf.
120
-// We may still be dialing. We should check network config.
121
-func (nb *nodeBootstrapper) BootstrapForever(ctx context.Context, peers []peer.PeerInfo) error {
122
- for {
123
- if err := nb.TryToBootstrap(ctx, peers); err == nil {
124
- return nil
125
- }
126
- }
112
+ return proc, nil
113
}
114
115
func bootstrapRound(ctx context.Context,
116
host host.Host,
117
route *dht.IpfsDHT,
118
peerstore peer.Peerstore,
133
- bootstrapPeers []peer.PeerInfo) error {
119
+ cfg BootstrapConfig) error {
120
+
121
+ ctx, _ = context.WithTimeout(ctx, cfg.ConnectionTimeout)
122
+ id := host.ID()
123
135
- ctx, _ = context.WithTimeout(ctx, BootstrapConnectionTimeout)
124
+ // get bootstrap peers from config. retrieving them here makes
125
+ // sure we remain observant of changes to client configuration.
126
+ peers := cfg.BootstrapPeers()
127
128
// determine how many bootstrap connections to open
138
- connectedPeers := host.Network().Peers()
139
- if len(connectedPeers) >= BootstrapPeerThreshold {
140
- log.Event(ctx, "bootstrapSkip", host.ID())
129
+ connected := host.Network().Peers()
130
+ if len(connected) >= cfg.MinPeerThreshold {
131
+ log.Event(ctx, "bootstrapSkip", id)
132
log.Debugf("%s core bootstrap skipped -- connected to %d (> %d) nodes",
142
- host.ID(), len(connectedPeers), BootstrapPeerThreshold)
133
+ id, len(connected), cfg.MinPeerThreshold)
134
return nil
135
}
145
- numCxnsToCreate := BootstrapPeerThreshold - len(connectedPeers)
136
+ numToDial := cfg.MinPeerThreshold - len(connected)
137
138
// filter out bootstrap nodes we are already connected to
139
var notConnected []peer.PeerInfo
149
- for _, p := range bootstrapPeers {
140
+ for _, p := range peers {
141
if host.Network().Connectedness(p.ID) != inet.Connected {
142
notConnected = append(notConnected, p)
143
}
@@ -154,17 +145,16 @@ func bootstrapRound(ctx context.Context,
145
146
// if connected to all bootstrap peer candidates, exit
147
if len(notConnected) < 1 {
157
- log.Debugf("%s no more bootstrap peers to create %d connections", host.ID(), numCxnsToCreate)
148
+ log.Debugf("%s no more bootstrap peers to create %d connections", id, numToDial)
149
return ErrNotEnoughBootstrapPeers
150
}
151
152
// connect to a random susbset of bootstrap candidates
162
- randomSubset := randomSubsetOfPeers(notConnected, numCxnsToCreate)
163
- defer log.EventBegin(ctx, "bootstrapStart", host.ID()).Done()
164
- log.Debugf("%s bootstrapping to %d nodes: %s", host.ID(), numCxnsToCreate, randomSubset)
165
- if err := bootstrapConnect(ctx, peerstore, route, randomSubset); err != nil {
166
- log.Event(ctx, "bootstrapError", host.ID(), lgbl.Error(err))
167
- log.Errorf("%s bootstrap error: %s", host.ID(), err)
153
+ randSubset := randomSubsetOfPeers(notConnected, numToDial)
154
+
155
+ defer log.EventBegin(ctx, "bootstrapStart", id).Done()
156
+ log.Debugf("%s bootstrapping to %d nodes: %s", id, numToDial, randSubset)
157
+ if err := bootstrapConnect(ctx, peerstore, route, randSubset); err != nil {
158
return err
159
}
160
return nil
@@ -196,12 +186,12 @@ func bootstrapConnect(ctx context.Context,
186
ps.AddAddresses(p.ID, p.Addrs)
187
err := route.Connect(ctx, p.ID)
188
if err != nil {
199
- log.Event(ctx, "bootstrapFailed", p.ID)
189
+ log.Event(ctx, "bootstrapDialFailed", p.ID)
190
log.Errorf("failed to bootstrap with %v: %s", p.ID, err)
191
errs <- err
192
return
193
}
204
- log.Event(ctx, "bootstrapSuccess", p.ID)
194
+ log.Event(ctx, "bootstrapDialSuccess", p.ID)
195
log.Infof("bootstrapped with %v", p.ID)
196
}(p)
197
}
@@ -223,7 +213,19 @@ func bootstrapConnect(ctx context.Context,
213
return nil
214
}
215
226
-func toPeer(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) {
216
+func toPeerInfos(bpeers []config.BootstrapPeer) ([]peer.PeerInfo, error) {
217
+ var peers []peer.PeerInfo
218
+ for _, bootstrap := range bpeers {
219
+ p, err := toPeerInfo(bootstrap)
220
+ if err != nil {
221
+ return nil, err
222
+ }
223
+ peers = append(peers, p)
224
+ }
225
+ return peers, nil
226
+}
227
+
228
+func toPeerInfo(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) {
229
id, err := peer.IDB58Decode(bootstrap.PeerID)
230
if err != nil {
231
return
core/core.go
+58
-55
@@ -11,33 +11,36 @@ import (
11
datastore "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
12
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
13
14
+ eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
15
+ debugerror "github.com/jbenet/go-ipfs/util/debugerror"
16
+
17
+ diag "github.com/jbenet/go-ipfs/diagnostics"
18
+ ic "github.com/jbenet/go-ipfs/p2p/crypto"
19
+ p2phost "github.com/jbenet/go-ipfs/p2p/host"
20
+ p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic"
21
+ swarm "github.com/jbenet/go-ipfs/p2p/net/swarm"
22
+ addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
23
+ peer "github.com/jbenet/go-ipfs/p2p/peer"
24
+
25
+ routing "github.com/jbenet/go-ipfs/routing"
26
+ dht "github.com/jbenet/go-ipfs/routing/dht"
27
+ offroute "github.com/jbenet/go-ipfs/routing/offline"
28
+
29
bstore "github.com/jbenet/go-ipfs/blocks/blockstore"
30
bserv "github.com/jbenet/go-ipfs/blockservice"
16
- diag "github.com/jbenet/go-ipfs/diagnostics"
31
exchange "github.com/jbenet/go-ipfs/exchange"
32
bitswap "github.com/jbenet/go-ipfs/exchange/bitswap"
33
bsnet "github.com/jbenet/go-ipfs/exchange/bitswap/network"
34
offline "github.com/jbenet/go-ipfs/exchange/offline"
35
rp "github.com/jbenet/go-ipfs/exchange/reprovide"
36
+
37
mount "github.com/jbenet/go-ipfs/fuse/mount"
38
merkledag "github.com/jbenet/go-ipfs/merkledag"
39
namesys "github.com/jbenet/go-ipfs/namesys"
25
- ic "github.com/jbenet/go-ipfs/p2p/crypto"
26
- p2phost "github.com/jbenet/go-ipfs/p2p/host"
27
- p2pbhost "github.com/jbenet/go-ipfs/p2p/host/basic"
28
- swarm "github.com/jbenet/go-ipfs/p2p/net/swarm"
29
- addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
30
- peer "github.com/jbenet/go-ipfs/p2p/peer"
40
path "github.com/jbenet/go-ipfs/path"
41
pin "github.com/jbenet/go-ipfs/pin"
42
repo "github.com/jbenet/go-ipfs/repo"
43
config "github.com/jbenet/go-ipfs/repo/config"
35
- routing "github.com/jbenet/go-ipfs/routing"
36
- dht "github.com/jbenet/go-ipfs/routing/dht"
37
- offroute "github.com/jbenet/go-ipfs/routing/offline"
38
- eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
39
- debugerror "github.com/jbenet/go-ipfs/util/debugerror"
40
- lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables"
44
)
45
46
const IpnsValidatorTag = "ipns"
@@ -75,13 +78,14 @@ type IpfsNode struct {
78
Resolver *path.Resolver // the path resolution system
79
80
// Online
78
- PrivateKey ic.PrivKey // the local node's private Key
79
- PeerHost p2phost.Host // the network host (server+client)
80
- Routing routing.IpfsRouting // the routing system. recommend ipfs-dht
81
- Exchange exchange.Interface // the block exchange + strategy (bitswap)
82
- Namesys namesys.NameSystem // the name system, resolves paths to hashes
83
- Diagnostics *diag.Diagnostics // the diagnostics service
84
- Reprovider *rp.Reprovider // the value reprovider system
81
+ PrivateKey ic.PrivKey // the local node's private Key
82
+ PeerHost p2phost.Host // the network host (server+client)
83
+ Bootstrapper io.Closer // the periodic bootstrapper
84
+ Routing routing.IpfsRouting // the routing system. recommend ipfs-dht
85
+ Exchange exchange.Interface // the block exchange + strategy (bitswap)
86
+ Namesys namesys.NameSystem // the name system, resolves paths to hashes
87
+ Diagnostics *diag.Diagnostics // the diagnostics service
88
+ Reprovider *rp.Reprovider // the value reprovider system
89
90
ctxgroup.ContextGroup
91
@@ -238,14 +242,7 @@ func (n *IpfsNode) StartOnlineServices(ctx context.Context) error {
242
n.Reprovider = rp.NewReprovider(n.Routing, n.Blockstore)
243
go n.Reprovider.ProvideEvery(ctx, kReprovideFrequency)
244
241
- // prepare bootstrap peers from config
242
- bpeers, err := n.loadBootstrapPeers()
243
- if err != nil {
244
- log.Event(ctx, "bootstrapError", n.Identity, lgbl.Error(err))
245
- log.Errorf("%s bootstrap error: %s", n.Identity, err)
246
- return debugerror.Wrap(err)
247
- }
248
- return n.Bootstrap(ctx, bpeers)
245
+ return n.Bootstrap(DefaultBootstrapConfig)
246
}
247
248
// teardown closes owned children. If any errors occur, this function returns
@@ -254,20 +251,20 @@ func (n *IpfsNode) teardown() error {
251
// owned objects are closed in this teardown to ensure that they're closed
252
// regardless of which constructor was used to add them to the node.
253
var closers []io.Closer
257
- if n.Repo != nil {
258
- closers = append(closers, n.Repo)
259
- }
260
- if n.Blocks != nil {
261
- closers = append(closers, n.Blocks)
262
- }
263
- if n.Routing != nil {
264
- if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
265
- closers = append(closers, dht)
254
+ addCloser := func(c io.Closer) {
255
+ if c != nil {
256
+ closers = append(closers, c)
257
}
258
}
268
- if n.PeerHost != nil {
269
- closers = append(closers, n.PeerHost)
259
+
260
+ addCloser(n.Bootstrapper)
261
+ addCloser(n.Repo)
262
+ addCloser(n.Blocks)
263
+ if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
264
+ addCloser(dht)
265
}
266
+ addCloser(n.PeerHost)
267
+
268
var errs []error
269
for _, closer := range closers {
270
if err := closer.Close(); err != nil {
@@ -293,16 +290,34 @@ func (n *IpfsNode) Resolve(path string) (*merkledag.Node, error) {
290
return n.Resolver.ResolvePath(path)
291
}
292
296
-// Bootstrap is undefined when node is not in OnlineMode
297
-func (n *IpfsNode) Bootstrap(ctx context.Context, peers []peer.PeerInfo) error {
293
+func (n *IpfsNode) Bootstrap(cfg BootstrapConfig) error {
294
295
// TODO what should return value be when in offlineMode?
296
if n.Routing == nil {
297
return nil
298
}
299
304
- nb := nodeBootstrapper{n}
305
- return nb.TryToBootstrap(ctx, peers)
300
+ if n.Bootstrapper != nil {
301
+ n.Bootstrapper.Close() // stop previous bootstrap process.
302
+ }
303
+
304
+ // if the caller did not specify a bootstrap peer function, get the
305
+ // freshest bootstrap peers from config. this responds to live changes.
306
+ if cfg.BootstrapPeers == nil {
307
+ cfg.BootstrapPeers = func() []peer.PeerInfo {
308
+ bpeers := n.Repo.Config().Bootstrap
309
+ ps, err := toPeerInfos(bpeers)
310
+ if err != nil {
311
+ log.Error("failed to parse bootstrap peers from config: %s", bpeers)
312
+ return nil
313
+ }
314
+ return ps
315
+ }
316
+ }
317
+
318
+ var err error
319
+ n.Bootstrapper, err = Bootstrap(n, cfg)
320
+ return err
321
}
322
323
func (n *IpfsNode) loadID() error {
@@ -342,18 +357,6 @@ func (n *IpfsNode) loadPrivateKey() error {
357
return nil
358
}
359
345
-func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
346
- var peers []peer.PeerInfo
347
- for _, bootstrap := range n.Repo.Config().Bootstrap {
348
- p, err := toPeer(bootstrap)
349
- if err != nil {
350
- return nil, err
351
- }
352
- peers = append(peers, p)
353
- }
354
- return peers, nil
355
-}
356
-
360
// SetupOfflineRouting loads the local nodes private key and
361
// uses it to instantiate a routing system in offline mode.
362
// This is primarily used for offline ipns modifications.
routing/dht/dht_bootstrap.go
+42
-70
@@ -17,52 +17,42 @@ import (
17
periodicproc "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
18
)
19
20
-// DefaultBootstrapQueries specifies how many queries to run,
21
-// if the user does not specify a different number as an option.
20
+// BootstrapConfig specifies parameters used bootstrapping the DHT.
21
//
23
-// For now, this is set to 16 queries, which is an aggressive number.
24
-// We are currently more interested in ensuring we have a properly formed
25
-// DHT than making sure our dht minimizes traffic. Once we are more certain
26
-// of our implementation's robustness, we should lower this down to 8 or 4.
27
-//
28
-// Note there is also a tradeoff between the bootstrap period and the number
29
-// of queries. We could support a higher period with a smaller number of
30
-// queries
31
-const DefaultBootstrapQueries = 1
22
+// Note there is a tradeoff between the bootstrap period and the
23
+// number of queries. We could support a higher period with less
24
+// queries.
25
+type BootstrapConfig struct {
26
+ Queries int // how many queries to run per period
27
+ Period time.Duration // how often to run periodi cbootstrap.
28
+ Timeout time.Duration // how long to wait for a bootstrao query to run
29
+}
30
33
-// DefaultBootstrapPeriod specifies how often to periodically run bootstrap,
34
-// if the user does not specify a different number as an option.
35
-//
36
-// For now, this is set to 10 seconds, which is an aggressive period. We are
37
-// We are currently more interested in ensuring we have a properly formed
38
-// DHT than making sure our dht minimizes traffic. Once we are more certain
39
-// implementation's robustness, we should lower this down to 30s or 1m.
40
-//
41
-// Note there is also a tradeoff between the bootstrap period and the number
42
-// of queries. We could support a higher period with a smaller number of
43
-// queries
44
-const DefaultBootstrapPeriod = time.Duration(10 * time.Second)
45
-
46
-// DefaultBootstrapTimeout specifies how long to wait for a bootstrap query
47
-// to run.
48
-const DefaultBootstrapTimeout = time.Duration(10 * time.Second)
49
-
50
-// Bootstrap runs bootstrapping once, then calls SignalBootstrap with default
51
-// parameters: DefaultBootstrapQueries and DefaultBootstrapPeriod. This allows
52
-// the user to catch an error off the bat if the connections are faulty. It also
53
-// allows BootstrapOnSignal not to run bootstrap at the beginning, which is useful
54
-// for instrumenting it on tests, or delaying bootstrap until the network is online
55
-// and connected to at least a few nodes.
56
-//
57
-// Like PeriodicBootstrap, Bootstrap returns a process, so the user can stop it.
58
-func (dht *IpfsDHT) Bootstrap(ctx context.Context) (goprocess.Process, error) {
31
+var DefaultBootstrapConfig = BootstrapConfig{
32
+ // For now, this is set to 1 query.
33
+ // We are currently more interested in ensuring we have a properly formed
34
+ // DHT than making sure our dht minimizes traffic. Once we are more certain
35
+ // of our implementation's robustness, we should lower this down to 8 or 4.
36
+ Queries: 1,
37
60
- if err := dht.runBootstrap(ctx, DefaultBootstrapQueries); err != nil {
61
- return nil, err
62
- }
38
+ // For now, this is set to 10 seconds, which is an aggressive period. We are
39
+ // We are currently more interested in ensuring we have a properly formed
40
+ // DHT than making sure our dht minimizes traffic. Once we are more certain
41
+ // implementation's robustness, we should lower this down to 30s or 1m.
42
+ Period: time.Duration(20 * time.Second),
43
64
- sig := time.Tick(DefaultBootstrapPeriod)
65
- return dht.BootstrapOnSignal(DefaultBootstrapQueries, sig)
44
+ Timeout: time.Duration(20 * time.Second),
45
+}
46
+
47
+// Bootstrap ensures the dht routing table remains healthy as peers come and go.
48
+// it builds up a list of peers by requesting random peer IDs. The Bootstrap
49
+// process will run a number of queries each time, and run every time signal fires.
50
+// These parameters are configurable.
51
+//
52
+// Bootstrap returns a process, so the user can stop it.
53
+func (dht *IpfsDHT) Bootstrap(config BootstrapConfig) (goprocess.Process, error) {
54
+ sig := time.Tick(config.Period)
55
+ return dht.BootstrapOnSignal(config, sig)
56
}
57
58
// SignalBootstrap ensures the dht routing table remains healthy as peers come and go.
@@ -71,9 +61,9 @@ func (dht *IpfsDHT) Bootstrap(ctx context.Context) (goprocess.Process, error) {
61
// These parameters are configurable.
62
//
63
// SignalBootstrap returns a process, so the user can stop it.
74
-func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (goprocess.Process, error) {
75
- if queries <= 0 {
76
- return nil, fmt.Errorf("invalid number of queries: %d", queries)
64
+func (dht *IpfsDHT) BootstrapOnSignal(cfg BootstrapConfig, signal <-chan time.Time) (goprocess.Process, error) {
65
+ if cfg.Queries <= 0 {
66
+ return nil, fmt.Errorf("invalid number of queries: %d", cfg.Queries)
67
}
68
69
if signal == nil {
@@ -85,27 +75,9 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop
75
// maybe this is a good case for whole module event pub/sub?
76
77
ctx := dht.Context()
88
- if err := dht.runBootstrap(ctx, queries); err != nil {
78
+ if err := dht.runBootstrap(ctx, cfg); err != nil {
79
log.Error(err)
80
// A bootstrapping error is important to notice but not fatal.
91
- // maybe the client should be able to consume these errors,
92
- // though I dont have a clear use case in mind-- what **could**
93
- // the client do if one of the bootstrap calls fails?
94
- //
95
- // This is also related to the core's bootstrap failures.
96
- // superviseConnections should perhaps allow clients to detect
97
- // bootstrapping problems.
98
- //
99
- // Anyway, passing errors could be done with a bootstrapper object.
100
- // this would imply the client should be able to consume a lot of
101
- // other non-fatal dht errors too. providing this functionality
102
- // should be done correctly DHT-wide.
103
- // NB: whatever the design, clients must ensure they drain errors!
104
- // This pattern is common to many things, perhaps long-running services
105
- // should have something like an ErrStream that allows clients to consume
106
- // periodic errors and take action. It should allow the user to also
107
- // ignore all errors with something like an ErrStreamDiscard. We should
108
- // study what other systems do for ideas.
81
}
82
})
83
@@ -113,7 +85,7 @@ func (dht *IpfsDHT) BootstrapOnSignal(queries int, signal <-chan time.Time) (gop
85
}
86
87
// runBootstrap builds up list of peers by requesting random peer IDs
116
-func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
88
+func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error {
89
bslog := func(msg string) {
90
log.Debugf("DHT %s dhtRunBootstrap %s -- routing table size: %d", dht.self, msg, dht.routingTable.Size())
91
}
@@ -133,7 +105,7 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
105
}
106
107
// bootstrap sequentially, as results will compound
136
- ctx, cancel := context.WithTimeout(ctx, DefaultBootstrapTimeout)
108
+ ctx, cancel := context.WithTimeout(ctx, cfg.Timeout)
109
defer cancel()
110
runQuery := func(ctx context.Context, id peer.ID) {
111
p, err := dht.FindPeer(ctx, id)
@@ -154,9 +126,9 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
126
if sequential {
127
// these should be parallel normally. but can make them sequential for debugging.
128
// note that the core/bootstrap context deadline should be extended too for that.
157
- for i := 0; i < queries; i++ {
129
+ for i := 0; i < cfg.Queries; i++ {
130
id := randomID()
159
- log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, queries, id)
131
+ log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id)
132
runQuery(ctx, id)
133
}
134
@@ -166,13 +138,13 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, queries int) error {
138
// normally, we should be selecting on ctx.Done() here too, but this gets
139
// complicated to do with WaitGroup, and doesnt wait for the children to exit.
140
var wg sync.WaitGroup
169
- for i := 0; i < queries; i++ {
141
+ for i := 0; i < cfg.Queries; i++ {
142
wg.Add(1)
143
go func() {
144
defer wg.Done()
145
146
id := randomID()
175
- log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, queries, id)
147
+ log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id)
148
runQuery(ctx, id)
149
}()
150
}
test/epictest/addcat_test.go
+9
-2
@@ -115,8 +115,15 @@ func DirectAddCat(data []byte, conf testutil.LatencyConfig) error {
115
}
116
defer catter.Close()
117
118
- catter.Bootstrap(ctx, []peer.PeerInfo{adder.Peerstore.PeerInfo(adder.Identity)})
119
- adder.Bootstrap(ctx, []peer.PeerInfo{catter.Peerstore.PeerInfo(catter.Identity)})
118
+ bs1 := []peer.PeerInfo{adder.Peerstore.PeerInfo(adder.Identity)}
119
+ bs2 := []peer.PeerInfo{catter.Peerstore.PeerInfo(catter.Identity)}
120
+
121
+ if err := catter.Bootstrap(core.BootstrapConfigWithPeers(bs1)); err != nil {
122
+ return err
123
+ }
124
+ if err := adder.Bootstrap(core.BootstrapConfigWithPeers(bs2)); err != nil {
125
+ return err
126
+ }
127
128
keyAdded, err := coreunix.Add(adder, bytes.NewReader(data))
129
if err != nil {
test/epictest/three_legged_cat_test.go
+9
-3
@@ -62,9 +62,15 @@ func RunThreeLeggedCat(data []byte, conf testutil.LatencyConfig) error {
62
return err
63
}
64
defer bootstrap.Close()
65
- boostrapInfo := bootstrap.Peerstore.PeerInfo(bootstrap.PeerHost.ID())
66
- adder.Bootstrap(ctx, []peer.PeerInfo{boostrapInfo})
67
- catter.Bootstrap(ctx, []peer.PeerInfo{boostrapInfo})
65
+
66
+ bis := bootstrap.Peerstore.PeerInfo(bootstrap.PeerHost.ID())
67
+ bcfg := core.BootstrapConfigWithPeers([]peer.PeerInfo{bis})
68
+ if err := adder.Bootstrap(bcfg); err != nil {
69
+ return err
70
+ }
71
+ if err := catter.Bootstrap(bcfg); err != nil {
72
+ return err
73
+ }
74
75
keyAdded, err := coreunix.Add(adder, bytes.NewReader(data))
76
if err != nil {