github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/routing/dht/dht_bootstrap.go (about) 1 // Package dht implements a distributed hash table that satisfies the ipfs routing 2 // interface. This DHT is modeled after kademlia with Coral and S/Kademlia modifications. 3 package dht 4 5 import ( 6 "crypto/rand" 7 "fmt" 8 "sync" 9 "time" 10 11 peer "github.com/ipfs/go-ipfs/p2p/peer" 12 routing "github.com/ipfs/go-ipfs/routing" 13 u "github.com/ipfs/go-ipfs/util" 14 15 goprocess "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess" 16 periodicproc "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic" 17 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 18 ) 19 20 // BootstrapConfig specifies parameters used bootstrapping the DHT. 21 // 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 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 38 // For now, this is set to 1 minute, which is a medium 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. 41 Period: time.Duration(5 * time.Minute), 42 43 Timeout: time.Duration(10 * time.Second), 44 } 45 46 // Bootstrap ensures the dht routing table remains healthy as peers come and go. 47 // it builds up a list of peers by requesting random peer IDs. The Bootstrap 48 // process will run a number of queries each time, and run every time signal fires. 49 // These parameters are configurable. 50 // 51 // As opposed to BootstrapWithConfig, Bootstrap satisfies the routing interface 52 func (dht *IpfsDHT) Bootstrap(ctx context.Context) error { 53 proc, err := dht.BootstrapWithConfig(DefaultBootstrapConfig) 54 if err != nil { 55 return err 56 } 57 58 // wait till ctx or dht.Context exits. 59 // we have to do it this way to satisfy the Routing interface (contexts) 60 go func() { 61 defer proc.Close() 62 select { 63 case <-ctx.Done(): 64 case <-dht.Context().Done(): 65 } 66 }() 67 68 return nil 69 } 70 71 // BootstrapWithConfig ensures the dht routing table remains healthy as peers come and go. 72 // it builds up a list of peers by requesting random peer IDs. The Bootstrap 73 // process will run a number of queries each time, and run every time signal fires. 74 // These parameters are configurable. 75 // 76 // BootstrapWithConfig returns a process, so the user can stop it. 77 func (dht *IpfsDHT) BootstrapWithConfig(config BootstrapConfig) (goprocess.Process, error) { 78 sig := time.Tick(config.Period) 79 return dht.BootstrapOnSignal(config, sig) 80 } 81 82 // SignalBootstrap ensures the dht routing table remains healthy as peers come and go. 83 // it builds up a list of peers by requesting random peer IDs. The Bootstrap 84 // process will run a number of queries each time, and run every time signal fires. 85 // These parameters are configurable. 86 // 87 // SignalBootstrap returns a process, so the user can stop it. 88 func (dht *IpfsDHT) BootstrapOnSignal(cfg BootstrapConfig, signal <-chan time.Time) (goprocess.Process, error) { 89 if cfg.Queries <= 0 { 90 return nil, fmt.Errorf("invalid number of queries: %d", cfg.Queries) 91 } 92 93 if signal == nil { 94 return nil, fmt.Errorf("invalid signal: %v", signal) 95 } 96 97 proc := periodicproc.Ticker(signal, func(worker goprocess.Process) { 98 // it would be useful to be able to send out signals of when we bootstrap, too... 99 // maybe this is a good case for whole module event pub/sub? 100 101 ctx := dht.Context() 102 if err := dht.runBootstrap(ctx, cfg); err != nil { 103 log.Warning(err) 104 // A bootstrapping error is important to notice but not fatal. 105 } 106 }) 107 108 return proc, nil 109 } 110 111 // runBootstrap builds up list of peers by requesting random peer IDs 112 func (dht *IpfsDHT) runBootstrap(ctx context.Context, cfg BootstrapConfig) error { 113 bslog := func(msg string) { 114 log.Debugf("DHT %s dhtRunBootstrap %s -- routing table size: %d", dht.self, msg, dht.routingTable.Size()) 115 } 116 bslog("start") 117 defer bslog("end") 118 defer log.EventBegin(ctx, "dhtRunBootstrap").Done() 119 120 var merr u.MultiErr 121 122 randomID := func() peer.ID { 123 // 16 random bytes is not a valid peer id. it may be fine becuase 124 // the dht will rehash to its own keyspace anyway. 125 id := make([]byte, 16) 126 rand.Read(id) 127 id = u.Hash(id) 128 return peer.ID(id) 129 } 130 131 // bootstrap sequentially, as results will compound 132 ctx, cancel := context.WithTimeout(ctx, cfg.Timeout) 133 defer cancel() 134 runQuery := func(ctx context.Context, id peer.ID) { 135 p, err := dht.FindPeer(ctx, id) 136 if err == routing.ErrNotFound { 137 // this isn't an error. this is precisely what we expect. 138 } else if err != nil { 139 merr = append(merr, err) 140 } else { 141 // woah, actually found a peer with that ID? this shouldn't happen normally 142 // (as the ID we use is not a real ID). this is an odd error worth logging. 143 err := fmt.Errorf("Bootstrap peer error: Actually FOUND peer. (%s, %s)", id, p) 144 log.Warningf("%s", err) 145 merr = append(merr, err) 146 } 147 } 148 149 sequential := true 150 if sequential { 151 // these should be parallel normally. but can make them sequential for debugging. 152 // note that the core/bootstrap context deadline should be extended too for that. 153 for i := 0; i < cfg.Queries; i++ { 154 id := randomID() 155 log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id) 156 runQuery(ctx, id) 157 } 158 159 } else { 160 // note on parallelism here: the context is passed in to the queries, so they 161 // **should** exit when it exceeds, making this function exit on ctx cancel. 162 // normally, we should be selecting on ctx.Done() here too, but this gets 163 // complicated to do with WaitGroup, and doesnt wait for the children to exit. 164 var wg sync.WaitGroup 165 for i := 0; i < cfg.Queries; i++ { 166 wg.Add(1) 167 go func() { 168 defer wg.Done() 169 170 id := randomID() 171 log.Debugf("Bootstrapping query (%d/%d) to random ID: %s", i+1, cfg.Queries, id) 172 runQuery(ctx, id) 173 }() 174 } 175 wg.Wait() 176 } 177 178 if len(merr) > 0 { 179 return merr 180 } 181 return nil 182 }