aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--p2p/dial.go28
-rw-r--r--p2p/dial_test.go100
-rw-r--r--p2p/server.go2
3 files changed, 121 insertions, 9 deletions
diff --git a/p2p/dial.go b/p2p/dial.go
index bb3befab2..b77971396 100644
--- a/p2p/dial.go
+++ b/p2p/dial.go
@@ -38,6 +38,10 @@ const (
// once every few seconds.
lookupInterval = 4 * time.Second
+ // If no peers are found for this amount of time, the initial bootnodes are
+ // attempted to be connected.
+ fallbackInterval = 20 * time.Second
+
// Endpoint resolution is throttled with bounded backoff.
initialResolveDelay = 60 * time.Second
maxResolveDelay = time.Hour
@@ -57,6 +61,9 @@ type dialstate struct {
randomNodes []*discover.Node // filled from Table
static map[discover.NodeID]*dialTask
hist *dialHistory
+
+ start time.Time // time when the dialer was first used
+ bootnodes []*discover.Node // default dials when there are no peers
}
type discoverTable interface {
@@ -102,16 +109,18 @@ type waitExpireTask struct {
time.Duration
}
-func newDialState(static []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate {
+func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate {
s := &dialstate{
maxDynDials: maxdyn,
ntab: ntab,
netrestrict: netrestrict,
static: make(map[discover.NodeID]*dialTask),
dialing: make(map[discover.NodeID]connFlag),
+ bootnodes: make([]*discover.Node, len(bootnodes)),
randomNodes: make([]*discover.Node, maxdyn/2),
hist: new(dialHistory),
}
+ copy(s.bootnodes, bootnodes)
for _, n := range static {
s.addStatic(n)
}
@@ -130,6 +139,10 @@ func (s *dialstate) removeStatic(n *discover.Node) {
}
func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task {
+ if s.start == (time.Time{}) {
+ s.start = now
+ }
+
var newtasks []task
addDial := func(flag connFlag, n *discover.Node) bool {
if err := s.checkDial(n, peers); err != nil {
@@ -169,7 +182,18 @@ func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now
newtasks = append(newtasks, t)
}
}
-
+ // If we don't have any peers whatsoever, try to dial a random bootnode. This
+ // scenario is useful for the testnet (and private networks) where the discovery
+ // table might be full of mostly bad peers, making it hard to find good ones.
+ if len(peers) == 0 && len(s.bootnodes) > 0 && needDynDials > 0 && now.Sub(s.start) > fallbackInterval {
+ bootnode := s.bootnodes[0]
+ s.bootnodes = append(s.bootnodes[:0], s.bootnodes[1:]...)
+ s.bootnodes = append(s.bootnodes, bootnode)
+
+ if addDial(dynDialedConn, bootnode) {
+ needDynDials--
+ }
+ }
// Use random nodes from the table for half of the necessary
// dynamic dials.
randomCandidates := needDynDials / 2
diff --git a/p2p/dial_test.go b/p2p/dial_test.go
index c850233db..08e863bae 100644
--- a/p2p/dial_test.go
+++ b/p2p/dial_test.go
@@ -87,7 +87,7 @@ func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf,
// This test checks that dynamic dials are launched from discovery results.
func TestDialStateDynDial(t *testing.T) {
runDialTest(t, dialtest{
- init: newDialState(nil, fakeTable{}, 5, nil),
+ init: newDialState(nil, nil, fakeTable{}, 5, nil),
rounds: []round{
// A discovery query is launched.
{
@@ -219,6 +219,94 @@ func TestDialStateDynDial(t *testing.T) {
})
}
+// Tests that bootnodes are dialed if no peers are connectd, but not otherwise.
+func TestDialStateDynDialBootnode(t *testing.T) {
+ bootnodes := []*discover.Node{
+ {ID: uintID(1)},
+ {ID: uintID(2)},
+ {ID: uintID(3)},
+ }
+ table := fakeTable{
+ {ID: uintID(4)},
+ {ID: uintID(5)},
+ {ID: uintID(6)},
+ {ID: uintID(7)},
+ {ID: uintID(8)},
+ }
+ runDialTest(t, dialtest{
+ init: newDialState(nil, bootnodes, table, 5, nil),
+ rounds: []round{
+ // 2 dynamic dials attempted, bootnodes pending fallback interval
+ {
+ new: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ &discoverTask{},
+ },
+ },
+ // No dials succeed, bootnodes still pending fallback interval
+ {
+ done: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ },
+ },
+ // No dials succeed, bootnodes still pending fallback interval
+ {},
+ // No dials succeed, 2 dynamic dials attempted and 1 bootnode too as fallback interval was reached
+ {
+ new: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ },
+ },
+ // No dials succeed, 2nd bootnode is attempted
+ {
+ done: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ },
+ new: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ },
+ },
+ // No dials succeed, 3rd bootnode is attempted
+ {
+ done: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
+ },
+ new: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ },
+ },
+ // No dials succeed, 1st bootnode is attempted again, expired random nodes retried
+ {
+ done: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
+ },
+ new: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ },
+ },
+ // Random dial succeeds, no more bootnodes are attempted
+ {
+ peers: []*Peer{
+ {rw: &conn{flags: dynDialedConn, id: uintID(4)}},
+ },
+ done: []task{
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
+ &dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
+ },
+ },
+ },
+ })
+}
+
func TestDialStateDynDialFromTable(t *testing.T) {
// This table always returns the same random nodes
// in the order given below.
@@ -234,7 +322,7 @@ func TestDialStateDynDialFromTable(t *testing.T) {
}
runDialTest(t, dialtest{
- init: newDialState(nil, table, 10, nil),
+ init: newDialState(nil, nil, table, 10, nil),
rounds: []round{
// 5 out of 8 of the nodes returned by ReadRandomNodes are dialed.
{
@@ -332,7 +420,7 @@ func TestDialStateNetRestrict(t *testing.T) {
restrict.Add("127.0.2.0/24")
runDialTest(t, dialtest{
- init: newDialState(nil, table, 10, restrict),
+ init: newDialState(nil, nil, table, 10, restrict),
rounds: []round{
{
new: []task{
@@ -355,7 +443,7 @@ func TestDialStateStaticDial(t *testing.T) {
}
runDialTest(t, dialtest{
- init: newDialState(wantStatic, fakeTable{}, 0, nil),
+ init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
rounds: []round{
// Static dials are launched for the nodes that
// aren't yet connected.
@@ -436,7 +524,7 @@ func TestDialStateCache(t *testing.T) {
}
runDialTest(t, dialtest{
- init: newDialState(wantStatic, fakeTable{}, 0, nil),
+ init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
rounds: []round{
// Static dials are launched for the nodes that
// aren't yet connected.
@@ -498,7 +586,7 @@ func TestDialStateCache(t *testing.T) {
func TestDialResolve(t *testing.T) {
resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
table := &resolveMock{answer: resolved}
- state := newDialState(nil, table, 0, nil)
+ state := newDialState(nil, nil, table, 0, nil)
// Check that the task is generated with an incomplete ID.
dest := discover.NewNode(uintID(1), nil, 0, 0)
diff --git a/p2p/server.go b/p2p/server.go
index 48b4e8be3..b2b8c9762 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -396,7 +396,7 @@ func (srv *Server) Start() (err error) {
if !srv.Discovery {
dynPeers = 0
}
- dialer := newDialState(srv.StaticNodes, srv.ntab, dynPeers, srv.NetRestrict)
+ dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
// handshake
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}