@cryptotaxi247 / kubo / commits / 95d58b2a4

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 {