7
7
"time"
8
8
9
9
u "github.com/ipfs/go-ipfs-util"
10
+ goprocess "github.com/jbenet/goprocess"
11
+ periodicproc "github.com/jbenet/goprocess/periodic"
10
12
peer "github.com/libp2p/go-libp2p-peer"
11
- pstore "github.com/libp2p/go-libp2p-peerstore"
12
13
routing "github.com/libp2p/go-libp2p-routing"
13
14
)
14
15
@@ -38,73 +39,87 @@ var DefaultBootstrapConfig = BootstrapConfig{
38
39
Timeout : time .Duration (10 * time .Second ),
39
40
}
40
41
41
- // A method in the IpfsRouting interface. It calls BootstrapWithConfig with
42
- // the default bootstrap config.
42
+ // Bootstrap ensures the dht routing table remains healthy as peers come and go.
43
+ // it builds up a list of peers by requesting random peer IDs. The Bootstrap
44
+ // process will run a number of queries each time, and run every time signal fires.
45
+ // These parameters are configurable.
46
+ //
47
+ // As opposed to BootstrapWithConfig, Bootstrap satisfies the routing interface
43
48
func (dht * IpfsDHT ) Bootstrap (ctx context.Context ) error {
44
- return dht .BootstrapWithConfig (ctx , DefaultBootstrapConfig )
49
+ proc , err := dht .BootstrapWithConfig (DefaultBootstrapConfig )
50
+ if err != nil {
51
+ return err
52
+ }
53
+
54
+ // wait till ctx or dht.Context exits.
55
+ // we have to do it this way to satisfy the Routing interface (contexts)
56
+ go func () {
57
+ defer proc .Close ()
58
+ select {
59
+ case <- ctx .Done ():
60
+ case <- dht .Context ().Done ():
61
+ }
62
+ }()
63
+
64
+ return nil
45
65
}
46
66
47
- // Runs cfg.Queries bootstrap queries every cfg.Period.
48
- func (dht * IpfsDHT ) BootstrapWithConfig (ctx context.Context , cfg BootstrapConfig ) error {
49
- // Because this method is not synchronous, we have to duplicate sanity
50
- // checks on the config so that callers aren't oblivious.
67
+ // BootstrapWithConfig ensures the dht routing table remains healthy as peers come and go.
68
+ // it builds up a list of peers by requesting random peer IDs. The Bootstrap
69
+ // process will run a number of queries each time, and run every time signal fires.
70
+ // These parameters are configurable.
71
+ //
72
+ // BootstrapWithConfig returns a process, so the user can stop it.
73
+ func (dht * IpfsDHT ) BootstrapWithConfig (cfg BootstrapConfig ) (goprocess.Process , error ) {
51
74
if cfg .Queries <= 0 {
52
- return fmt .Errorf ("invalid number of queries: %d" , cfg .Queries )
75
+ return nil , fmt .Errorf ("invalid number of queries: %d" , cfg .Queries )
53
76
}
54
- go func () {
77
+
78
+ proc := dht .Process ().Go (func (p goprocess.Process ) {
79
+ <- p .Go (dht .bootstrapWorker (cfg )).Closed ()
55
80
for {
56
- err := dht .runBootstrap (ctx , cfg )
57
- if err != nil {
58
- log .Warningf ("error bootstrapping: %s" , err )
59
- }
60
81
select {
61
82
case <- time .After (cfg .Period ):
62
- case <- ctx .Done ():
83
+ <- p .Go (dht .bootstrapWorker (cfg )).Closed ()
84
+ case <- p .Closing ():
63
85
return
64
86
}
65
87
}
66
- }()
67
- return nil
88
+ })
89
+
90
+ return proc , nil
68
91
}
69
92
70
- // This is a synchronous bootstrap. cfg.Queries queries will run each with a
71
- // timeout of cfg.Timeout. cfg.Period is not used.
72
- func (dht * IpfsDHT ) BootstrapOnce (ctx context.Context , cfg BootstrapConfig ) error {
93
+ // SignalBootstrap ensures the dht routing table remains healthy as peers come and go.
94
+ // it builds up a list of peers by requesting random peer IDs. The Bootstrap
95
+ // process will run a number of queries each time, and run every time signal fires.
96
+ // These parameters are configurable.
97
+ //
98
+ // SignalBootstrap returns a process, so the user can stop it.
99
+ func (dht * IpfsDHT ) BootstrapOnSignal (cfg BootstrapConfig , signal <- chan time.Time ) (goprocess.Process , error ) {
73
100
if cfg .Queries <= 0 {
74
- return fmt .Errorf ("invalid number of queries: %d" , cfg .Queries )
101
+ return nil , fmt .Errorf ("invalid number of queries: %d" , cfg .Queries )
75
102
}
76
- return dht .runBootstrap (ctx , cfg )
77
- }
78
103
79
- func newRandomPeerId () peer.ID {
80
- id := make ([]byte , 32 ) // SHA256 is the default. TODO: Use a more canonical way to generate random IDs.
81
- rand .Read (id )
82
- id = u .Hash (id ) // TODO: Feed this directly into the multihash instead of hashing it.
83
- return peer .ID (id )
84
- }
104
+ if signal == nil {
105
+ return nil , fmt .Errorf ("invalid signal: %v" , signal )
106
+ }
85
107
86
- // Traverse the DHT toward the given ID.
87
- func (dht * IpfsDHT ) walk (ctx context.Context , target peer.ID ) (pstore.PeerInfo , error ) {
88
- // TODO: Extract the query action (traversal logic?) inside FindPeer,
89
- // don't actually call through the FindPeer machinery, which can return
90
- // things out of the peer store etc.
91
- return dht .FindPeer (ctx , target )
108
+ proc := periodicproc .Ticker (signal , dht .bootstrapWorker (cfg ))
109
+
110
+ return proc , nil
92
111
}
93
112
94
- // Traverse the DHT toward a random ID.
95
- func (dht * IpfsDHT ) randomWalk (ctx context.Context ) error {
96
- id := newRandomPeerId ()
97
- p , err := dht .walk (ctx , id )
98
- switch err {
99
- case routing .ErrNotFound :
100
- return nil
101
- case nil :
102
- // We found a peer from a randomly generated ID. This should be very
103
- // unlikely.
104
- log .Warningf ("random walk toward %s actually found peer: %s" , id , p )
105
- return nil
106
- default :
107
- return err
113
+ func (dht * IpfsDHT ) bootstrapWorker (cfg BootstrapConfig ) func (worker goprocess.Process ) {
114
+ return func (worker goprocess.Process ) {
115
+ // it would be useful to be able to send out signals of when we bootstrap, too...
116
+ // maybe this is a good case for whole module event pub/sub?
117
+
118
+ ctx := dht .Context ()
119
+ if err := dht .runBootstrap (ctx , cfg ); err != nil {
120
+ log .Warning (err )
121
+ // A bootstrapping error is important to notice but not fatal.
122
+ }
108
123
}
109
124
}
110
125
@@ -117,24 +132,51 @@ func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error
117
132
defer bslog ("end" )
118
133
defer log .EventBegin (ctx , "dhtRunBootstrap" ).Done ()
119
134
120
- doQuery := func (n int , target string , f func (context.Context ) error ) error {
121
- log .Debugf ("Bootstrapping query (%d/%d) to %s" , n , cfg .Queries , target )
135
+ var merr u.MultiErr
136
+
137
+ randomID := func () peer.ID {
138
+ // 16 random bytes is not a valid peer id. it may be fine becuase
139
+ // the dht will rehash to its own keyspace anyway.
140
+ id := make ([]byte , 16 )
141
+ rand .Read (id )
142
+ id = u .Hash (id )
143
+ return peer .ID (id )
144
+ }
145
+
146
+ // bootstrap sequentially, as results will compound
147
+ runQuery := func (ctx context.Context , id peer.ID ) {
122
148
ctx , cancel := context .WithTimeout (ctx , cfg .Timeout )
123
149
defer cancel ()
124
- return f (ctx )
125
- }
126
150
127
- // Do all but one of the bootstrap queries as random walks.
128
- for i := 1 ; i < cfg .Queries ; i ++ {
129
- err := doQuery (i , "random ID" , dht .randomWalk )
130
- if err != nil {
131
- return err
151
+ p , err := dht .FindPeer (ctx , id )
152
+ if err == routing .ErrNotFound {
153
+ // this isn't an error. this is precisely what we expect.
154
+ } else if err != nil {
155
+ merr = append (merr , err )
156
+ } else {
157
+ // woah, actually found a peer with that ID? this shouldn't happen normally
158
+ // (as the ID we use is not a real ID). this is an odd error worth logging.
159
+ err := fmt .Errorf ("Bootstrap peer error: Actually FOUND peer. (%s, %s)" , id , p )
160
+ log .Warningf ("%s" , err )
161
+ merr = append (merr , err )
132
162
}
133
163
}
134
164
165
+ // these should be parallel normally. but can make them sequential for debugging.
166
+ // note that the core/bootstrap context deadline should be extended too for that.
167
+ for i := 0 ; i < cfg .Queries ; i ++ {
168
+ id := randomID ()
169
+ log .Debugf ("Bootstrapping query (%d/%d) to random ID: %s" , i + 1 , cfg .Queries , id )
170
+ runQuery (ctx , id )
171
+ }
172
+
135
173
// Find self to distribute peer info to our neighbors.
136
- return doQuery (cfg .Queries , fmt .Sprintf ("self: %s" , dht .self ), func (ctx context.Context ) error {
137
- _ , err := dht .walk (ctx , dht .self )
138
- return err
139
- })
174
+ // Do this after bootstrapping.
175
+ log .Debugf ("Bootstrapping query to self: %s" , dht .self )
176
+ runQuery (ctx , dht .self )
177
+
178
+ if len (merr ) > 0 {
179
+ return merr
180
+ }
181
+ return nil
140
182
}
0 commit comments