Skip to content

Commit 08ab423

Browse files
Boostrap empty RT and Optimize allocs when we discover new peers (#631)
* Bootstrap when RT is empty and optimize allocations. Co-authored-by: Steven Allen <[email protected]>
1 parent 9be7169 commit 08ab423

File tree

7 files changed

+347
-15
lines changed

7 files changed

+347
-15
lines changed

dht.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"math"
9+
"math/rand"
910
"sync"
1011
"time"
1112

@@ -86,7 +87,8 @@ type IpfsDHT struct {
8687

8788
// DHT protocols we query with. We'll only add peers to our routing
8889
// table if they speak these protocols.
89-
protocols []protocol.ID
90+
protocols []protocol.ID
91+
protocolsStrs []string
9092

9193
// DHT protocols we can respond to.
9294
serverProtocols []protocol.ID
@@ -108,6 +110,11 @@ type IpfsDHT struct {
108110
triggerRtRefresh chan chan<- error
109111
triggerSelfLookup chan chan<- error
110112

113+
// A set of bootstrap peers to fallback on if all other attempts to fix
114+
// the routing table fail (or, e.g., this is the first time this node is
115+
// connecting to the network).
116+
bootstrapPeers []peer.AddrInfo
117+
111118
maxRecordAge time.Duration
112119

113120
// Allows disabling dht subsystems. These should _only_ be set on
@@ -254,6 +261,7 @@ func makeDHT(ctx context.Context, h host.Host, cfg config) (*IpfsDHT, error) {
254261
strmap: make(map[peer.ID]*messageSender),
255262
birth: time.Now(),
256263
protocols: protocols,
264+
protocolsStrs: protocol.ConvertToStrings(protocols),
257265
serverProtocols: serverProtocols,
258266
bucketSize: cfg.bucketSize,
259267
alpha: cfg.concurrency,
@@ -262,7 +270,7 @@ func makeDHT(ctx context.Context, h host.Host, cfg config) (*IpfsDHT, error) {
262270
triggerSelfLookup: make(chan chan<- error),
263271
queryPeerFilter: cfg.queryPeerFilter,
264272
routingTablePeerFilter: cfg.routingTable.peerFilter,
265-
fixLowPeersChan: make(chan struct{}),
273+
fixLowPeersChan: make(chan struct{}, 1),
266274
}
267275

268276
// construct routing table
@@ -271,6 +279,7 @@ func makeDHT(ctx context.Context, h host.Host, cfg config) (*IpfsDHT, error) {
271279
return nil, fmt.Errorf("failed to construct routing table,err=%s", err)
272280
}
273281
dht.routingTable = rt
282+
dht.bootstrapPeers = cfg.bootstrapPeers
274283

275284
// create a DHT proc with the given context
276285
dht.proc = goprocessctx.WithContext(ctx)
@@ -323,22 +332,70 @@ func (dht *IpfsDHT) Mode() ModeOpt {
323332
return dht.auto
324333
}
325334

326-
// fixLowPeers tries to get more peers into the routing table if we're below the threshold
335+
// fixLowPeersRoutine tries to get more peers into the routing table if we're below the threshold
327336
func (dht *IpfsDHT) fixLowPeersRoutine(proc goprocess.Process) {
337+
timer := time.NewTimer(periodicBootstrapInterval)
338+
defer timer.Stop()
339+
328340
for {
329341
select {
330342
case <-dht.fixLowPeersChan:
343+
case <-timer.C:
331344
case <-proc.Closing():
332345
return
333346
}
347+
334348
if dht.routingTable.Size() > minRTRefreshThreshold {
335349
continue
336350
}
337351

352+
// we try to add all peers we are connected to to the Routing Table
353+
// in case they aren't already there.
338354
for _, p := range dht.host.Network().Peers() {
339355
dht.peerFound(dht.Context(), p, false)
340356
}
341357

358+
// TODO Active Bootstrapping
359+
// We should first use non-bootstrap peers we knew of from previous
360+
// snapshots of the Routing Table before we connect to the bootstrappers.
361+
// See https://github.com/libp2p/go-libp2p-kad-dht/issues/387.
362+
if dht.routingTable.Size() == 0 {
363+
if len(dht.bootstrapPeers) == 0 {
364+
// No point in continuing, we have no peers!
365+
continue
366+
}
367+
368+
found := 0
369+
for _, i := range rand.Perm(len(dht.bootstrapPeers)) {
370+
ai := dht.bootstrapPeers[i]
371+
err := dht.Host().Connect(dht.Context(), ai)
372+
if err == nil {
373+
found++
374+
} else {
375+
logger.Warnw("failed to bootstrap", "peer", ai.ID, "error", err)
376+
}
377+
378+
// Wait for two bootstrap peers, or try them all.
379+
//
380+
// Why two? In theory, one should be enough
381+
// normally. However, if the network were to
382+
// restart and everyone connected to just one
383+
// bootstrapper, we'll end up with a mostly
384+
// partitioned network.
385+
//
386+
// So we always bootstrap with two random peers.
387+
if found == maxNBoostrappers {
388+
break
389+
}
390+
}
391+
}
392+
393+
// if we still don't have peers in our routing table(probably because Identify hasn't completed),
394+
// there is no point in triggering a Refresh.
395+
if dht.routingTable.Size() == 0 {
396+
continue
397+
}
398+
342399
if dht.autoRefresh {
343400
select {
344401
case dht.triggerRtRefresh <- nil:
@@ -504,9 +561,6 @@ func (dht *IpfsDHT) peerStoppedDHT(ctx context.Context, p peer.ID) {
504561
// A peer that does not support the DHT protocol is dead for us.
505562
// There's no point in talking to anymore till it starts supporting the DHT protocol again.
506563
dht.routingTable.RemovePeer(p)
507-
508-
// since we lost a peer from the RT, we should do this here
509-
dht.fixRTIfNeeded()
510564
}
511565

512566
func (dht *IpfsDHT) fixRTIfNeeded() {

dht_bootstrap.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ var minRTRefreshThreshold = 10
2222

2323
// timeout for pinging one peer
2424
const peerPingTimeout = 10 * time.Second
25+
const (
26+
periodicBootstrapInterval = 2 * time.Minute
27+
maxNBoostrappers = 2
28+
)
2529

2630
func init() {
2731
for _, s := range []string{

dht_options.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/libp2p/go-libp2p-core/protocol"
1414
"github.com/libp2p/go-libp2p-kad-dht/providers"
1515
record "github.com/libp2p/go-libp2p-record"
16+
ma "github.com/multiformats/go-multiaddr"
1617
)
1718

1819
// ModeOpt describes what mode the dht should operate in
@@ -60,6 +61,7 @@ type config struct {
6061

6162
// set to true if we're operating in v1 dht compatible mode
6263
v1CompatibleMode bool
64+
bootstrapPeers []peer.AddrInfo
6365
}
6466

6567
func emptyQueryFilter(_ *IpfsDHT, ai peer.AddrInfo) bool { return true }
@@ -393,3 +395,16 @@ func V1CompatibleMode(enable bool) Option {
393395
return nil
394396
}
395397
}
398+
399+
// BootstrapPeers configures the bootstrapping nodes that we will connect to to seed
400+
// and refresh our Routing Table if it becomes empty.
401+
func BootstrapPeers(addrs ...ma.Multiaddr) Option {
402+
return func(c *config) error {
403+
bootstrappers, err := peer.AddrInfosFromP2pAddrs(addrs...)
404+
if err != nil {
405+
return fmt.Errorf("failed to parse bootstrap peer addresses: %w", err)
406+
}
407+
c.bootstrapPeers = bootstrappers
408+
return nil
409+
}
410+
}

dht_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,3 +1966,107 @@ func TestRoutingFilter(t *testing.T) {
19661966
case <-time.After(time.Millisecond * 200):
19671967
}
19681968
}
1969+
1970+
func TestBootStrapWhenRTIsEmpty(t *testing.T) {
1971+
ctx, cancel := context.WithCancel(context.Background())
1972+
defer cancel()
1973+
1974+
// create three boostrap peers each of which is connected to 1 other peer.
1975+
nBootStraps := 3
1976+
bootstrappers := setupDHTS(t, ctx, nBootStraps)
1977+
defer func() {
1978+
for i := 0; i < nBootStraps; i++ {
1979+
bootstrappers[i].Close()
1980+
defer bootstrappers[i].host.Close()
1981+
}
1982+
}()
1983+
1984+
bootstrapcons := setupDHTS(t, ctx, nBootStraps)
1985+
defer func() {
1986+
for i := 0; i < nBootStraps; i++ {
1987+
bootstrapcons[i].Close()
1988+
defer bootstrapcons[i].host.Close()
1989+
}
1990+
}()
1991+
for i := 0; i < nBootStraps; i++ {
1992+
connect(t, ctx, bootstrappers[i], bootstrapcons[i])
1993+
}
1994+
1995+
// convert the bootstrap addresses to a p2p address
1996+
bootstrapAddrs := make([]ma.Multiaddr, nBootStraps)
1997+
for i := 0; i < nBootStraps; i++ {
1998+
b, err := peer.AddrInfoToP2pAddrs(&peer.AddrInfo{ID: bootstrappers[i].self,
1999+
Addrs: bootstrappers[i].host.Addrs()})
2000+
require.NoError(t, err)
2001+
bootstrapAddrs[i] = b[0]
2002+
}
2003+
2004+
//----------------
2005+
// We will initialize a DHT with 1 bootstrapper, connect it to another DHT,
2006+
// then remove the latter from the Routing Table
2007+
// This should add the bootstrap peer and the peer that the bootstrap peer is conencted to
2008+
// to it's Routing Table.
2009+
// AutoRefresh needs to be enabled for this.
2010+
dht1, err := New(
2011+
ctx,
2012+
bhost.New(swarmt.GenSwarm(t, ctx, swarmt.OptDisableReuseport)),
2013+
testPrefix,
2014+
NamespacedValidator("v", blankValidator{}),
2015+
Mode(ModeServer),
2016+
BootstrapPeers(bootstrapAddrs[0]),
2017+
)
2018+
require.NoError(t, err)
2019+
dht2 := setupDHT(ctx, t, false)
2020+
defer func() {
2021+
dht1.host.Close()
2022+
dht2.host.Close()
2023+
dht1.Close()
2024+
dht2.Close()
2025+
}()
2026+
connect(t, ctx, dht1, dht2)
2027+
require.NoError(t, dht2.Close())
2028+
require.NoError(t, dht2.host.Close())
2029+
require.NoError(t, dht1.host.Network().ClosePeer(dht2.self))
2030+
dht1.routingTable.RemovePeer(dht2.self)
2031+
require.NotContains(t, dht2.self, dht1.routingTable.ListPeers())
2032+
2033+
require.Eventually(t, func() bool {
2034+
return dht1.routingTable.Size() == 2 && dht1.routingTable.Find(bootstrappers[0].self) != "" &&
2035+
dht1.routingTable.Find(bootstrapcons[0].self) != ""
2036+
}, 5*time.Second, 500*time.Millisecond)
2037+
2038+
//----------------
2039+
// We will initialize a DHT with 2 bootstrappers, connect it to another DHT,
2040+
// then remove the DHT handler from the other DHT which should make the first DHT's
2041+
// routing table empty.
2042+
// This should add the bootstrap peers and the peer thats the bootstrap peers are connected to
2043+
// to it's Routing Table.
2044+
// AutoRefresh needs to be enabled for this.
2045+
dht1, err = New(
2046+
ctx,
2047+
bhost.New(swarmt.GenSwarm(t, ctx, swarmt.OptDisableReuseport)),
2048+
testPrefix,
2049+
NamespacedValidator("v", blankValidator{}),
2050+
Mode(ModeServer),
2051+
BootstrapPeers(bootstrapAddrs[1], bootstrapAddrs[2]),
2052+
)
2053+
require.NoError(t, err)
2054+
2055+
dht2 = setupDHT(ctx, t, false)
2056+
connect(t, ctx, dht1, dht2)
2057+
defer func() {
2058+
dht1.host.Close()
2059+
dht2.host.Close()
2060+
dht1.Close()
2061+
dht2.Close()
2062+
}()
2063+
connect(t, ctx, dht1, dht2)
2064+
require.NoError(t, dht2.setMode(modeClient))
2065+
2066+
require.Eventually(t, func() bool {
2067+
rt := dht1.routingTable
2068+
2069+
return rt.Size() == 4 && rt.Find(bootstrappers[1].self) != "" &&
2070+
rt.Find(bootstrappers[2].self) != "" && rt.Find(bootstrapcons[1].self) != "" && rt.Find(bootstrapcons[2].self) != ""
2071+
}, 5*time.Second, 500*time.Millisecond)
2072+
}

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ require (
1717
github.com/jbenet/goprocess v0.1.4
1818
github.com/libp2p/go-eventbus v0.1.0
1919
github.com/libp2p/go-libp2p v0.8.2
20-
github.com/libp2p/go-libp2p-core v0.5.3
21-
github.com/libp2p/go-libp2p-kbucket v0.4.1
22-
github.com/libp2p/go-libp2p-peerstore v0.2.3
20+
github.com/libp2p/go-libp2p-core v0.5.4
21+
github.com/libp2p/go-libp2p-kbucket v0.4.2
22+
github.com/libp2p/go-libp2p-peerstore v0.2.4
2323
github.com/libp2p/go-libp2p-record v0.1.2
2424
github.com/libp2p/go-libp2p-routing-helpers v0.2.3
2525
github.com/libp2p/go-libp2p-swarm v0.2.3
@@ -28,7 +28,7 @@ require (
2828
github.com/libp2p/go-netroute v0.1.2
2929
github.com/multiformats/go-base32 v0.0.3
3030
github.com/multiformats/go-multiaddr v0.2.1
31-
github.com/multiformats/go-multiaddr-net v0.1.4
31+
github.com/multiformats/go-multiaddr-net v0.1.5
3232
github.com/multiformats/go-multihash v0.0.13
3333
github.com/multiformats/go-multistream v0.1.1
3434
github.com/stretchr/testify v1.5.1

0 commit comments

Comments
 (0)