2015-01-16 12:52:12 -08:00
|
|
|
package dht
|
|
|
|
|
|
|
|
import (
|
2016-09-30 10:24:03 -07:00
|
|
|
"context"
|
2015-01-16 12:52:12 -08:00
|
|
|
"fmt"
|
2019-08-18 22:21:15 +08:00
|
|
|
"strings"
|
|
|
|
"sync"
|
2015-01-16 12:52:12 -08:00
|
|
|
"time"
|
|
|
|
|
2019-11-01 00:38:28 -07:00
|
|
|
process "github.com/jbenet/goprocess"
|
|
|
|
processctx "github.com/jbenet/goprocess/context"
|
2019-05-26 23:33:15 +01:00
|
|
|
"github.com/libp2p/go-libp2p-core/routing"
|
|
|
|
"github.com/multiformats/go-multiaddr"
|
2019-02-27 05:21:11 +00:00
|
|
|
_ "github.com/multiformats/go-multiaddr-dns"
|
2019-08-18 22:21:15 +08:00
|
|
|
"github.com/pkg/errors"
|
2015-01-16 12:52:12 -08:00
|
|
|
)
|
|
|
|
|
2019-01-23 17:02:50 +11:00
|
|
|
var DefaultBootstrapPeers []multiaddr.Multiaddr
|
|
|
|
|
2019-09-05 22:09:16 +08:00
|
|
|
var minRTBootstrapThreshold = 4
|
|
|
|
|
2019-01-23 17:02:50 +11:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:37:15 +08:00
|
|
|
type bootstrapReq struct {
|
|
|
|
errChan chan error
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeBootstrapReq() *bootstrapReq {
|
|
|
|
errChan := make(chan error, 1)
|
|
|
|
return &bootstrapReq{errChan}
|
|
|
|
}
|
|
|
|
|
2019-11-01 00:38:28 -07:00
|
|
|
// Bootstrap i
|
|
|
|
func (dht *IpfsDHT) startBootstrapping() error {
|
|
|
|
// scan the RT table periodically & do a random walk on k-buckets that haven't been queried since the given bucket period
|
|
|
|
dht.proc.Go(func(proc process.Process) {
|
|
|
|
ctx := processctx.OnClosingContext(proc)
|
2019-11-02 00:37:15 +08:00
|
|
|
|
|
|
|
scanInterval := time.NewTicker(dht.bootstrapCfg.BucketPeriod)
|
2019-11-01 00:38:28 -07:00
|
|
|
defer scanInterval.Stop()
|
2019-09-05 22:09:16 +08:00
|
|
|
|
2019-11-02 00:37:15 +08:00
|
|
|
// run bootstrap if option is set
|
|
|
|
if dht.triggerAutoBootstrap {
|
2019-11-02 11:37:44 +08:00
|
|
|
if err := dht.doBootstrap(ctx, true); err != nil {
|
2019-11-02 00:37:15 +08:00
|
|
|
logger.Warningf("bootstrap error: %s", err)
|
2019-01-31 10:06:56 +11:00
|
|
|
}
|
2019-11-03 19:57:50 +08:00
|
|
|
} else {
|
|
|
|
// disable the "auto-bootstrap" ticker so that no more ticks are sent to his channel
|
|
|
|
scanInterval.Stop()
|
2019-11-02 00:37:15 +08:00
|
|
|
}
|
2019-11-01 00:38:28 -07:00
|
|
|
|
2019-11-02 00:37:15 +08:00
|
|
|
for {
|
2018-06-29 12:32:09 -04:00
|
|
|
select {
|
2019-11-01 00:38:28 -07:00
|
|
|
case now := <-scanInterval.C:
|
2019-11-02 11:37:44 +08:00
|
|
|
walkSelf := now.After(dht.latestSelfWalk.Add(dht.bootstrapCfg.SelfQueryInterval))
|
|
|
|
if err := dht.doBootstrap(ctx, walkSelf); err != nil {
|
2019-11-02 00:37:15 +08:00
|
|
|
logger.Warning("bootstrap error: %s", err)
|
|
|
|
}
|
|
|
|
case req := <-dht.triggerBootstrap:
|
2019-11-01 00:38:28 -07:00
|
|
|
logger.Infof("triggering a bootstrap: RT has %d peers", dht.routingTable.Size())
|
2019-11-02 11:37:44 +08:00
|
|
|
err := dht.doBootstrap(ctx, true)
|
2019-11-02 00:37:15 +08:00
|
|
|
select {
|
|
|
|
case req.errChan <- err:
|
|
|
|
close(req.errChan)
|
|
|
|
default:
|
|
|
|
}
|
2019-01-31 10:06:56 +11:00
|
|
|
case <-ctx.Done():
|
2018-06-29 12:32:09 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-11-01 00:38:28 -07:00
|
|
|
})
|
2019-09-05 22:09:16 +08:00
|
|
|
|
2019-01-31 10:06:56 +11:00
|
|
|
return nil
|
2019-01-23 11:18:24 +11:00
|
|
|
}
|
2016-12-08 11:58:06 +02:00
|
|
|
|
2019-11-02 11:37:44 +08:00
|
|
|
func (dht *IpfsDHT) doBootstrap(ctx context.Context, walkSelf bool) error {
|
2019-11-02 00:37:15 +08:00
|
|
|
if walkSelf {
|
|
|
|
if err := dht.selfWalk(ctx); err != nil {
|
|
|
|
return fmt.Errorf("self walk: error: %s", err)
|
|
|
|
}
|
2019-11-03 19:57:50 +08:00
|
|
|
dht.latestSelfWalk = time.Now()
|
2019-11-02 00:37:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := dht.bootstrapBuckets(ctx); err != nil {
|
|
|
|
return fmt.Errorf("bootstrap buckets: error bootstrapping: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-05 22:09:16 +08:00
|
|
|
// bootstrapBuckets scans the routing table, and does a random walk on k-buckets that haven't been queried since the given bucket period
|
2019-09-01 22:10:40 +08:00
|
|
|
func (dht *IpfsDHT) bootstrapBuckets(ctx context.Context) error {
|
2019-09-05 22:09:16 +08:00
|
|
|
doQuery := func(bucketId int, target string, f func(context.Context) error) error {
|
2019-08-18 22:21:15 +08:00
|
|
|
logger.Infof("starting bootstrap query for bucket %d to %s (routing table size was %d)",
|
2019-09-05 22:09:16 +08:00
|
|
|
bucketId, target, dht.routingTable.Size())
|
2019-03-12 13:26:54 +11:00
|
|
|
defer func() {
|
2019-08-18 22:21:15 +08:00
|
|
|
logger.Infof("finished bootstrap query for bucket %d to %s (routing table size is now %d)",
|
2019-09-05 22:09:16 +08:00
|
|
|
bucketId, target, dht.routingTable.Size())
|
2019-03-12 13:26:54 +11:00
|
|
|
}()
|
2019-09-01 22:10:40 +08:00
|
|
|
queryCtx, cancel := context.WithTimeout(ctx, dht.bootstrapCfg.Timeout)
|
2018-06-09 08:48:33 -07:00
|
|
|
defer cancel()
|
2019-02-15 14:46:42 +11:00
|
|
|
err := f(queryCtx)
|
|
|
|
if err == context.DeadlineExceeded && queryCtx.Err() == context.DeadlineExceeded && ctx.Err() == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
2015-01-16 12:52:12 -08:00
|
|
|
}
|
|
|
|
|
2019-08-18 22:21:15 +08:00
|
|
|
buckets := dht.routingTable.GetAllBuckets()
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
errChan := make(chan error)
|
|
|
|
|
|
|
|
for bucketID, bucket := range buckets {
|
2019-09-01 22:10:40 +08:00
|
|
|
if time.Since(bucket.RefreshedAt()) > dht.bootstrapCfg.BucketPeriod {
|
2019-08-18 22:21:15 +08:00
|
|
|
wg.Add(1)
|
|
|
|
go func(bucketID int, errChan chan<- error) {
|
|
|
|
defer wg.Done()
|
|
|
|
// gen rand peer in the bucket
|
2019-08-29 16:08:58 -07:00
|
|
|
randPeerInBucket := dht.routingTable.GenRandPeerID(bucketID)
|
2019-08-18 22:21:15 +08:00
|
|
|
|
|
|
|
// walk to the generated peer
|
|
|
|
walkFnc := func(c context.Context) error {
|
2019-09-01 22:10:40 +08:00
|
|
|
_, err := dht.FindPeer(ctx, randPeerInBucket)
|
2019-08-18 22:21:15 +08:00
|
|
|
if err == routing.ErrNotFound {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := doQuery(bucketID, randPeerInBucket.String(), walkFnc); err != nil {
|
|
|
|
errChan <- errors.Wrapf(err, "failed to do a random walk on bucket %d", bucketID)
|
|
|
|
}
|
|
|
|
}(bucketID, errChan)
|
2019-01-31 10:06:56 +11:00
|
|
|
}
|
2019-01-24 14:06:38 -08:00
|
|
|
}
|
|
|
|
|
2019-08-18 22:21:15 +08:00
|
|
|
// wait for all walks to finish & close the error channel
|
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
|
|
|
close(errChan)
|
|
|
|
}()
|
|
|
|
|
2019-09-05 22:09:16 +08:00
|
|
|
// accumulate errors from all go-routines. ensures wait group is completed by reading errChan until closure.
|
2019-08-18 22:21:15 +08:00
|
|
|
var errStrings []string
|
|
|
|
for err := range errChan {
|
|
|
|
errStrings = append(errStrings, err.Error())
|
|
|
|
}
|
|
|
|
if len(errStrings) == 0 {
|
|
|
|
return nil
|
|
|
|
} else {
|
2019-09-05 22:09:16 +08:00
|
|
|
return fmt.Errorf("errors encountered while running bootstrap on RT:\n%s", strings.Join(errStrings, "\n"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Traverse the DHT toward the self ID
|
|
|
|
func (dht *IpfsDHT) selfWalk(ctx context.Context) error {
|
|
|
|
queryCtx, cancel := context.WithTimeout(ctx, dht.bootstrapCfg.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
_, err := dht.FindPeer(queryCtx, dht.self)
|
|
|
|
if err == routing.ErrNotFound {
|
|
|
|
return nil
|
2019-08-18 22:21:15 +08:00
|
|
|
}
|
2019-09-05 22:09:16 +08:00
|
|
|
return err
|
2019-08-18 22:21:15 +08:00
|
|
|
}
|
|
|
|
|
2019-11-01 00:38:28 -07:00
|
|
|
// Bootstrap tells the DHT to get into a bootstrapped state.
|
|
|
|
//
|
|
|
|
// Note: the context is ignored.
|
|
|
|
func (dht *IpfsDHT) Bootstrap(_ context.Context) error {
|
|
|
|
select {
|
2019-11-02 11:37:44 +08:00
|
|
|
case dht.triggerBootstrap <- makeBootstrapReq():
|
2019-11-01 00:38:28 -07:00
|
|
|
default:
|
2019-08-18 22:21:15 +08:00
|
|
|
}
|
2019-11-01 00:38:28 -07:00
|
|
|
return nil
|
2015-01-16 12:52:12 -08:00
|
|
|
}
|