Skip to content

Commit f4c79d3

Browse files
authored
fix(dot/network): fix discovery between gossamer nodes (#1594)
1 parent af9c925 commit f4c79d3

File tree

11 files changed

+509
-299
lines changed

11 files changed

+509
-299
lines changed

dot/network/discovery.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright 2019 ChainSafe Systems (ON) Corp.
2+
// This file is part of gossamer.
3+
//
4+
// The gossamer library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The gossamer library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the gossamer library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package network
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
badger "github.com/ipfs/go-ds-badger2"
25+
libp2phost "github.com/libp2p/go-libp2p-core/host"
26+
"github.com/libp2p/go-libp2p-core/peer"
27+
"github.com/libp2p/go-libp2p-core/peerstore"
28+
"github.com/libp2p/go-libp2p-core/protocol"
29+
libp2pdiscovery "github.com/libp2p/go-libp2p-discovery"
30+
kaddht "github.com/libp2p/go-libp2p-kad-dht"
31+
"github.com/libp2p/go-libp2p-kad-dht/dual"
32+
)
33+
34+
var (
35+
startDHTTimeout = time.Second * 10
36+
initialAdvertisementTimeout = time.Millisecond
37+
tryAdvertiseTimeout = time.Second * 30
38+
connectToPeersTimeout = time.Minute
39+
)
40+
41+
// discovery handles discovery of new peers via the kademlia DHT
42+
type discovery struct {
43+
ctx context.Context
44+
dht *dual.DHT
45+
h libp2phost.Host
46+
bootnodes []peer.AddrInfo
47+
ds *badger.Datastore
48+
pid protocol.ID
49+
minPeers, maxPeers int
50+
}
51+
52+
func newDiscovery(ctx context.Context, h libp2phost.Host, bootnodes []peer.AddrInfo, ds *badger.Datastore, pid protocol.ID, min, max int) *discovery {
53+
return &discovery{
54+
ctx: ctx,
55+
h: h,
56+
bootnodes: bootnodes,
57+
ds: ds,
58+
pid: pid,
59+
minPeers: min,
60+
maxPeers: max,
61+
}
62+
}
63+
64+
// start creates the DHT.
65+
func (d *discovery) start() error {
66+
if len(d.bootnodes) == 0 {
67+
// get all currently connected peers and use them to bootstrap the DHT
68+
peers := d.h.Network().Peers()
69+
70+
for {
71+
if len(peers) > 0 {
72+
break
73+
}
74+
75+
select {
76+
case <-time.After(startDHTTimeout):
77+
logger.Debug("no peers yet, waiting to start DHT...")
78+
// wait for peers to connect before starting DHT, otherwise DHT bootstrap nodes
79+
// will be empty and we will fail to fill the routing table
80+
case <-d.ctx.Done():
81+
return nil
82+
}
83+
84+
peers = d.h.Network().Peers()
85+
}
86+
87+
for _, p := range peers {
88+
d.bootnodes = append(d.bootnodes, d.h.Peerstore().PeerInfo(p))
89+
}
90+
}
91+
92+
logger.Debug("starting DHT...", "bootnodes", d.bootnodes)
93+
94+
dhtOpts := []dual.Option{
95+
dual.DHTOption(kaddht.Datastore(d.ds)),
96+
dual.DHTOption(kaddht.BootstrapPeers(d.bootnodes...)),
97+
dual.DHTOption(kaddht.V1ProtocolOverride(d.pid + "/kad")),
98+
dual.DHTOption(kaddht.Mode(kaddht.ModeAutoServer)),
99+
}
100+
101+
// create DHT service
102+
dht, err := dual.New(d.ctx, d.h, dhtOpts...)
103+
if err != nil {
104+
return err
105+
}
106+
107+
d.dht = dht
108+
return d.discoverAndAdvertise()
109+
}
110+
111+
func (d *discovery) stop() error {
112+
if d.dht == nil {
113+
return nil
114+
}
115+
116+
return d.dht.Close()
117+
}
118+
119+
func (d *discovery) discoverAndAdvertise() error {
120+
rd := libp2pdiscovery.NewRoutingDiscovery(d.dht)
121+
122+
err := d.dht.Bootstrap(d.ctx)
123+
if err != nil {
124+
return fmt.Errorf("failed to bootstrap DHT: %w", err)
125+
}
126+
127+
// wait to connect to bootstrap peers
128+
time.Sleep(time.Second)
129+
130+
go func() {
131+
ttl := initialAdvertisementTimeout
132+
133+
for {
134+
select {
135+
case <-time.After(ttl):
136+
logger.Debug("advertising ourselves in the DHT...")
137+
err := d.dht.Bootstrap(d.ctx)
138+
if err != nil {
139+
logger.Warn("failed to bootstrap DHT", "error", err)
140+
continue
141+
}
142+
143+
ttl, err = rd.Advertise(d.ctx, string(d.pid))
144+
if err != nil {
145+
logger.Debug("failed to advertise in the DHT", "error", err)
146+
ttl = tryAdvertiseTimeout
147+
}
148+
case <-d.ctx.Done():
149+
return
150+
}
151+
}
152+
}()
153+
154+
go func() {
155+
logger.Debug("attempting to find DHT peers...")
156+
peerCh, err := rd.FindPeers(d.ctx, string(d.pid))
157+
if err != nil {
158+
logger.Warn("failed to begin finding peers via DHT", "err", err)
159+
return
160+
}
161+
162+
peersToTry := make(map[*peer.AddrInfo]struct{})
163+
164+
for {
165+
select {
166+
case <-d.ctx.Done():
167+
return
168+
case <-time.After(connectToPeersTimeout):
169+
if len(d.h.Network().Peers()) > d.minPeers {
170+
continue
171+
}
172+
173+
// reconnect to peers if peer count is low
174+
for p := range peersToTry {
175+
err = d.h.Connect(d.ctx, *p)
176+
if err != nil {
177+
logger.Trace("failed to connect to discovered peer", "peer", p.ID, "err", err)
178+
delete(peersToTry, p)
179+
}
180+
}
181+
case peer := <-peerCh:
182+
if peer.ID == d.h.ID() || peer.ID == "" {
183+
continue
184+
}
185+
186+
logger.Trace("found new peer via DHT", "peer", peer.ID)
187+
188+
// found a peer, try to connect if we need more peers
189+
if len(d.h.Network().Peers()) < d.maxPeers {
190+
err = d.h.Connect(d.ctx, peer)
191+
if err != nil {
192+
logger.Trace("failed to connect to discovered peer", "peer", peer.ID, "err", err)
193+
}
194+
} else {
195+
d.h.Peerstore().AddAddrs(peer.ID, peer.Addrs, peerstore.PermanentAddrTTL)
196+
peersToTry[&peer] = struct{}{}
197+
}
198+
}
199+
}
200+
}()
201+
202+
logger.Debug("DHT discovery started!")
203+
return nil
204+
}

0 commit comments

Comments
 (0)