bootstrap.go 4.38 KB
Newer Older
1 2 3
package core

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
4
	"errors"
5
	"math/rand"
6 7 8
	"sync"
	"time"

9
	config "github.com/jbenet/go-ipfs/config"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
10
	host "github.com/jbenet/go-ipfs/p2p/host"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
11
	inet "github.com/jbenet/go-ipfs/p2p/net"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
12
	peer "github.com/jbenet/go-ipfs/p2p/peer"
13
	dht "github.com/jbenet/go-ipfs/routing/dht"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14
	lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
15
	math2 "github.com/jbenet/go-ipfs/util/math2"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
16 17 18

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
19 20
)

21
const (
Brian Tiger Chow's avatar
Brian Tiger Chow committed
22 23 24
	period                               = 30 * time.Second // how often to check connection status
	connectiontimeout      time.Duration = period / 3       // duration to wait when attempting to connect
	recoveryThreshold                    = 4                // attempt to bootstrap if connection count falls below this value
Brian Tiger Chow's avatar
Brian Tiger Chow committed
25
	numDHTBootstrapQueries               = 15               // number of DHT queries to execute
26
)
27 28

func superviseConnections(parent context.Context,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
29
	h host.Host,
30
	route *dht.IpfsDHT, // TODO depend on abstract interface for testing purposes
31
	store peer.Peerstore,
32
	peers []peer.PeerInfo) error {
33 34

	for {
35
		ctx, _ := context.WithTimeout(parent, connectiontimeout)
36 37
		// TODO get config from disk so |peers| always reflects the latest
		// information
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38
		if err := bootstrap(ctx, h, route, store, peers); err != nil {
39 40 41 42 43 44 45 46 47 48 49 50
			log.Error(err)
		}
		select {
		case <-parent.Done():
			return parent.Err()
		case <-time.Tick(period):
		}
	}
	return nil
}

func bootstrap(ctx context.Context,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51
	h host.Host,
52 53
	r *dht.IpfsDHT,
	ps peer.Peerstore,
54
	bootstrapPeers []peer.PeerInfo) error {
55

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56
	connectedPeers := h.Network().Peers()
57
	if len(connectedPeers) >= recoveryThreshold {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
58
		log.Event(ctx, "bootstrapSkip", h.ID())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59
		log.Debugf("%s bootstrap skipped -- connected to %d (> %d) nodes",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60
			h.ID(), len(connectedPeers), recoveryThreshold)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
61

62 63
		return nil
	}
64
	numCxnsToCreate := recoveryThreshold - len(connectedPeers)
65

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
66 67
	log.Event(ctx, "bootstrapStart", h.ID())
	log.Debugf("%s bootstrapping to %d more nodes", h.ID(), numCxnsToCreate)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
68

69
	var notConnected []peer.PeerInfo
70
	for _, p := range bootstrapPeers {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
71
		if h.Network().Connectedness(p.ID) != inet.Connected {
72 73 74
			notConnected = append(notConnected, p)
		}
	}
75

76 77 78 79 80 81 82 83 84
	// if not connected to all bootstrap peer candidates
	if len(notConnected) > 0 {
		var randomSubset = randomSubsetOfPeers(notConnected, numCxnsToCreate)
		log.Debugf("%s bootstrapping to %d nodes: %s", h.ID(), numCxnsToCreate, randomSubset)
		if err := connect(ctx, ps, r, randomSubset); err != nil {
			log.Event(ctx, "bootstrapError", h.ID(), lgbl.Error(err))
			log.Errorf("%s bootstrap error: %s", h.ID(), err)
			return err
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
85 86
	}

87
	// we can try running dht bootstrap even if we're connected to all bootstrap peers.
88 89 90 91 92 93
	if len(h.Network().Conns()) > 0 {
		if err := r.Bootstrap(ctx, numDHTBootstrapQueries); err != nil {
			// log this as Info. later on, discern better between errors.
			log.Infof("dht bootstrap err: %s", err)
			return nil
		}
94 95 96 97
	}
	return nil
}

98
func connect(ctx context.Context, ps peer.Peerstore, r *dht.IpfsDHT, peers []peer.PeerInfo) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
99 100 101 102
	if len(peers) < 1 {
		return errors.New("bootstrap set empty")
	}

103 104 105 106 107 108 109 110
	var wg sync.WaitGroup
	for _, p := range peers {

		// performed asynchronously because when performed synchronously, if
		// one `Connect` call hangs, subsequent calls are more likely to
		// fail/abort due to an expiring context.

		wg.Add(1)
111
		go func(p peer.PeerInfo) {
112
			defer wg.Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113 114 115
			log.Event(ctx, "bootstrapDial", r.LocalPeer(), p.ID)
			log.Debugf("%s bootstrapping to %s", r.LocalPeer(), p.ID)

116 117
			ps.AddAddresses(p.ID, p.Addrs)
			err := r.Connect(ctx, p.ID)
118
			if err != nil {
119 120
				log.Event(ctx, "bootstrapFailed", p.ID)
				log.Criticalf("failed to bootstrap with %v", p.ID)
121 122
				return
			}
123 124
			log.Event(ctx, "bootstrapSuccess", p.ID)
			log.Infof("bootstrapped with %v", p.ID)
125 126 127 128 129 130
		}(p)
	}
	wg.Wait()
	return nil
}

131
func toPeer(bootstrap config.BootstrapPeer) (p peer.PeerInfo, err error) {
132
	id, err := peer.IDB58Decode(bootstrap.PeerID)
133
	if err != nil {
134
		return
135 136 137
	}
	maddr, err := ma.NewMultiaddr(bootstrap.Address)
	if err != nil {
138 139 140 141 142
		return
	}
	p = peer.PeerInfo{
		ID:    id,
		Addrs: []ma.Multiaddr{maddr},
143
	}
144
	return
145
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
146

147
func randomSubsetOfPeers(in []peer.PeerInfo, max int) []peer.PeerInfo {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
148
	n := math2.IntMin(max, len(in))
149
	var out []peer.PeerInfo
Brian Tiger Chow's avatar
Brian Tiger Chow committed
150 151 152 153 154
	for _, val := range rand.Perm(n) {
		out = append(out, in[val])
	}
	return out
}