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

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

11 12 13 14 15 16
	host "github.com/ipfs/go-ipfs/p2p/host"
	inet "github.com/ipfs/go-ipfs/p2p/net"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	config "github.com/ipfs/go-ipfs/repo/config"
	math2 "github.com/ipfs/go-ipfs/thirdparty/math2"
	lgbl "github.com/ipfs/go-ipfs/util/eventlog/loggables"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17

18 19 20 21
	goprocess "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
	procctx "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/context"
	periodicproc "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
22 23
)

24 25 26 27
// ErrNotEnoughBootstrapPeers signals that we do not have enough bootstrap
// peers to bootstrap correctly.
var ErrNotEnoughBootstrapPeers = errors.New("not enough bootstrap peers to bootstrap")

28 29 30
// BootstrapConfig specifies parameters used in an IpfsNode's network
// bootstrapping process.
type BootstrapConfig struct {
31

32 33
	// MinPeerThreshold governs whether to bootstrap more connections. If the
	// node has less open connections than this number, it will open connections
34 35 36 37
	// to the bootstrap nodes. From there, the routing system should be able
	// to use the connections to the bootstrap nodes to connect to even more
	// peers. Routing systems like the IpfsDHT do so in their own Bootstrap
	// process, which issues random queries to find more peers.
38 39 40 41 42 43
	MinPeerThreshold int

	// Period governs the periodic interval at which the node will
	// attempt to bootstrap. The bootstrap process is not very expensive, so
	// this threshold can afford to be small (<=30s).
	Period time.Duration
44

45
	// ConnectionTimeout determines how long to wait for a bootstrap
46
	// connection attempt before cancelling it.
47 48 49 50 51 52 53
	ConnectionTimeout time.Duration

	// BootstrapPeers is a function that returns a set of bootstrap peers
	// for the bootstrap process to use. This makes it possible for clients
	// to control the peers the process uses at any moment.
	BootstrapPeers func() []peer.PeerInfo
}
54

55 56 57 58 59
// DefaultBootstrapConfig specifies default sane parameters for bootstrapping.
var DefaultBootstrapConfig = BootstrapConfig{
	MinPeerThreshold:  4,
	Period:            30 * time.Second,
	ConnectionTimeout: (30 * time.Second) / 3, // Perod / 3
60
}
61

62 63 64 65 66 67 68 69 70 71 72 73 74
func BootstrapConfigWithPeers(pis []peer.PeerInfo) BootstrapConfig {
	cfg := DefaultBootstrapConfig
	cfg.BootstrapPeers = func() []peer.PeerInfo {
		return pis
	}
	return cfg
}

// Bootstrap kicks off IpfsNode bootstrapping. This function will periodically
// check the number of open connections and -- if there are too few -- initiate
// connections to well-known bootstrap peers. It also kicks off subsystem
// bootstrapping (i.e. routing).
func Bootstrap(n *IpfsNode, cfg BootstrapConfig) (io.Closer, error) {
75

76 77 78
	// make a signal to wait for one bootstrap round to complete.
	doneWithRound := make(chan struct{})

79 80
	// the periodic bootstrap function -- the connection supervisor
	periodic := func(worker goprocess.Process) {
81
		ctx := procctx.OnClosingContext(worker)
82 83
		defer log.EventBegin(ctx, "periodicBootstrap", n.Identity).Done()

84
		if err := bootstrapRound(ctx, n.PeerHost, cfg); err != nil {
85
			log.Event(ctx, "bootstrapError", n.Identity, lgbl.Error(err))
86
			log.Debugf("%s bootstrap error: %s", n.Identity, err)
87
		}
88 89

		<-doneWithRound
90
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
91

92 93 94 95
	// kick off the node's periodic bootstrapping
	proc := periodicproc.Tick(cfg.Period, periodic)
	proc.Go(periodic) // run one right now.

96 97
	// kick off Routing.Bootstrap
	if n.Routing != nil {
98
		ctx := procctx.OnClosingContext(proc)
99 100 101 102
		if err := n.Routing.Bootstrap(ctx); err != nil {
			proc.Close()
			return nil, err
		}
103 104
	}

105 106
	doneWithRound <- struct{}{}
	close(doneWithRound) // it no longer blocks periodic
107
	return proc, nil
108 109
}

110
func bootstrapRound(ctx context.Context, host host.Host, cfg BootstrapConfig) error {
111

rht's avatar
rht committed
112 113
	ctx, cancel := context.WithTimeout(ctx, cfg.ConnectionTimeout)
	defer cancel()
114
	id := host.ID()
115

116 117 118
	// get bootstrap peers from config. retrieving them here makes
	// sure we remain observant of changes to client configuration.
	peers := cfg.BootstrapPeers()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
119

120
	// determine how many bootstrap connections to open
121 122 123
	connected := host.Network().Peers()
	if len(connected) >= cfg.MinPeerThreshold {
		log.Event(ctx, "bootstrapSkip", id)
124
		log.Debugf("%s core bootstrap skipped -- connected to %d (> %d) nodes",
125
			id, len(connected), cfg.MinPeerThreshold)
126 127
		return nil
	}
128
	numToDial := cfg.MinPeerThreshold - len(connected)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129

130
	// filter out bootstrap nodes we are already connected to
131
	var notConnected []peer.PeerInfo
132
	for _, p := range peers {
133
		if host.Network().Connectedness(p.ID) != inet.Connected {
134 135 136
			notConnected = append(notConnected, p)
		}
	}
137

138 139
	// if connected to all bootstrap peer candidates, exit
	if len(notConnected) < 1 {
140
		log.Debugf("%s no more bootstrap peers to create %d connections", id, numToDial)
141 142 143 144
		return ErrNotEnoughBootstrapPeers
	}

	// connect to a random susbset of bootstrap candidates
145 146 147 148
	randSubset := randomSubsetOfPeers(notConnected, numToDial)

	defer log.EventBegin(ctx, "bootstrapStart", id).Done()
	log.Debugf("%s bootstrapping to %d nodes: %s", id, numToDial, randSubset)
149
	if err := bootstrapConnect(ctx, host, randSubset); err != nil {
150
		return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151
	}
152 153 154
	return nil
}

155
func bootstrapConnect(ctx context.Context, ph host.Host, peers []peer.PeerInfo) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
	if len(peers) < 1 {
157
		return ErrNotEnoughBootstrapPeers
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158 159
	}

160
	errs := make(chan error, len(peers))
161 162 163 164 165 166
	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.
167
		// Also, performed asynchronously for dial speed.
168 169

		wg.Add(1)
170
		go func(p peer.PeerInfo) {
171
			defer wg.Done()
172 173
			defer log.EventBegin(ctx, "bootstrapDial", ph.ID(), p.ID).Done()
			log.Debugf("%s bootstrapping to %s", ph.ID(), p.ID)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174

175 176
			ph.Peerstore().AddAddrs(p.ID, p.Addrs, peer.PermanentAddrTTL)
			if err := ph.Connect(ctx, p); err != nil {
177
				log.Event(ctx, "bootstrapDialFailed", p.ID)
178
				log.Debugf("failed to bootstrap with %v: %s", p.ID, err)
179
				errs <- err
180 181
				return
			}
182
			log.Event(ctx, "bootstrapDialSuccess", p.ID)
183
			log.Infof("bootstrapped with %v", p.ID)
184 185 186
		}(p)
	}
	wg.Wait()
187 188 189 190 191 192 193 194 195 196 197 198 199 200

	// our failure condition is when no connection attempt succeeded.
	// So drain the errs channel, counting the results.
	close(errs)
	count := 0
	var err error
	for err = range errs {
		if err != nil {
			count++
		}
	}
	if count == len(peers) {
		return fmt.Errorf("failed to bootstrap. %s", err)
	}
201 202 203
	return nil
}

Jeromy's avatar
Jeromy committed
204
func toPeerInfos(bpeers []config.BootstrapPeer) []peer.PeerInfo {
205
	pinfos := make(map[peer.ID]*peer.PeerInfo)
206
	for _, bootstrap := range bpeers {
207 208 209 210 211 212
		pinfo, ok := pinfos[bootstrap.ID()]
		if !ok {
			pinfo = new(peer.PeerInfo)
			pinfos[bootstrap.ID()] = pinfo
			pinfo.ID = bootstrap.ID()
		}
213 214

		pinfo.Addrs = append(pinfo.Addrs, bootstrap.Transport())
215 216 217 218 219
	}

	var peers []peer.PeerInfo
	for _, pinfo := range pinfos {
		peers = append(peers, *pinfo)
220
	}
221

Jeromy's avatar
Jeromy committed
222
	return peers
223
}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
224

225
func randomSubsetOfPeers(in []peer.PeerInfo, max int) []peer.PeerInfo {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
226
	n := math2.IntMin(max, len(in))
227
	var out []peer.PeerInfo
228
	for _, val := range rand.Perm(len(in)) {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
229
		out = append(out, in[val])
230 231 232
		if len(out) >= n {
			break
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
233 234 235
	}
	return out
}