mirror of
https://gitlab.com/pulsechaincom/go-pulse.git
synced 2024-12-22 11:31:02 +00:00
p2p: if no nodes are connected, attempt dialing bootnodes (#13874)
This commit is contained in:
parent
542e42b21e
commit
04fcae207d
26
p2p/dial.go
26
p2p/dial.go
@ -38,6 +38,10 @@ const (
|
|||||||
// once every few seconds.
|
// once every few seconds.
|
||||||
lookupInterval = 4 * time.Second
|
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.
|
// Endpoint resolution is throttled with bounded backoff.
|
||||||
initialResolveDelay = 60 * time.Second
|
initialResolveDelay = 60 * time.Second
|
||||||
maxResolveDelay = time.Hour
|
maxResolveDelay = time.Hour
|
||||||
@ -57,6 +61,9 @@ type dialstate struct {
|
|||||||
randomNodes []*discover.Node // filled from Table
|
randomNodes []*discover.Node // filled from Table
|
||||||
static map[discover.NodeID]*dialTask
|
static map[discover.NodeID]*dialTask
|
||||||
hist *dialHistory
|
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 {
|
type discoverTable interface {
|
||||||
@ -102,16 +109,18 @@ type waitExpireTask struct {
|
|||||||
time.Duration
|
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{
|
s := &dialstate{
|
||||||
maxDynDials: maxdyn,
|
maxDynDials: maxdyn,
|
||||||
ntab: ntab,
|
ntab: ntab,
|
||||||
netrestrict: netrestrict,
|
netrestrict: netrestrict,
|
||||||
static: make(map[discover.NodeID]*dialTask),
|
static: make(map[discover.NodeID]*dialTask),
|
||||||
dialing: make(map[discover.NodeID]connFlag),
|
dialing: make(map[discover.NodeID]connFlag),
|
||||||
|
bootnodes: make([]*discover.Node, len(bootnodes)),
|
||||||
randomNodes: make([]*discover.Node, maxdyn/2),
|
randomNodes: make([]*discover.Node, maxdyn/2),
|
||||||
hist: new(dialHistory),
|
hist: new(dialHistory),
|
||||||
}
|
}
|
||||||
|
copy(s.bootnodes, bootnodes)
|
||||||
for _, n := range static {
|
for _, n := range static {
|
||||||
s.addStatic(n)
|
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 {
|
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
|
var newtasks []task
|
||||||
addDial := func(flag connFlag, n *discover.Node) bool {
|
addDial := func(flag connFlag, n *discover.Node) bool {
|
||||||
if err := s.checkDial(n, peers); err != nil {
|
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)
|
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
|
// Use random nodes from the table for half of the necessary
|
||||||
// dynamic dials.
|
// dynamic dials.
|
||||||
randomCandidates := needDynDials / 2
|
randomCandidates := needDynDials / 2
|
||||||
|
100
p2p/dial_test.go
100
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.
|
// This test checks that dynamic dials are launched from discovery results.
|
||||||
func TestDialStateDynDial(t *testing.T) {
|
func TestDialStateDynDial(t *testing.T) {
|
||||||
runDialTest(t, dialtest{
|
runDialTest(t, dialtest{
|
||||||
init: newDialState(nil, fakeTable{}, 5, nil),
|
init: newDialState(nil, nil, fakeTable{}, 5, nil),
|
||||||
rounds: []round{
|
rounds: []round{
|
||||||
// A discovery query is launched.
|
// 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) {
|
func TestDialStateDynDialFromTable(t *testing.T) {
|
||||||
// This table always returns the same random nodes
|
// This table always returns the same random nodes
|
||||||
// in the order given below.
|
// in the order given below.
|
||||||
@ -234,7 +322,7 @@ func TestDialStateDynDialFromTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runDialTest(t, dialtest{
|
runDialTest(t, dialtest{
|
||||||
init: newDialState(nil, table, 10, nil),
|
init: newDialState(nil, nil, table, 10, nil),
|
||||||
rounds: []round{
|
rounds: []round{
|
||||||
// 5 out of 8 of the nodes returned by ReadRandomNodes are dialed.
|
// 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")
|
restrict.Add("127.0.2.0/24")
|
||||||
|
|
||||||
runDialTest(t, dialtest{
|
runDialTest(t, dialtest{
|
||||||
init: newDialState(nil, table, 10, restrict),
|
init: newDialState(nil, nil, table, 10, restrict),
|
||||||
rounds: []round{
|
rounds: []round{
|
||||||
{
|
{
|
||||||
new: []task{
|
new: []task{
|
||||||
@ -355,7 +443,7 @@ func TestDialStateStaticDial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runDialTest(t, dialtest{
|
runDialTest(t, dialtest{
|
||||||
init: newDialState(wantStatic, fakeTable{}, 0, nil),
|
init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
|
||||||
rounds: []round{
|
rounds: []round{
|
||||||
// Static dials are launched for the nodes that
|
// Static dials are launched for the nodes that
|
||||||
// aren't yet connected.
|
// aren't yet connected.
|
||||||
@ -436,7 +524,7 @@ func TestDialStateCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runDialTest(t, dialtest{
|
runDialTest(t, dialtest{
|
||||||
init: newDialState(wantStatic, fakeTable{}, 0, nil),
|
init: newDialState(wantStatic, nil, fakeTable{}, 0, nil),
|
||||||
rounds: []round{
|
rounds: []round{
|
||||||
// Static dials are launched for the nodes that
|
// Static dials are launched for the nodes that
|
||||||
// aren't yet connected.
|
// aren't yet connected.
|
||||||
@ -498,7 +586,7 @@ func TestDialStateCache(t *testing.T) {
|
|||||||
func TestDialResolve(t *testing.T) {
|
func TestDialResolve(t *testing.T) {
|
||||||
resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
|
resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
|
||||||
table := &resolveMock{answer: resolved}
|
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.
|
// Check that the task is generated with an incomplete ID.
|
||||||
dest := discover.NewNode(uintID(1), nil, 0, 0)
|
dest := discover.NewNode(uintID(1), nil, 0, 0)
|
||||||
|
@ -396,7 +396,7 @@ func (srv *Server) Start() (err error) {
|
|||||||
if !srv.Discovery {
|
if !srv.Discovery {
|
||||||
dynPeers = 0
|
dynPeers = 0
|
||||||
}
|
}
|
||||||
dialer := newDialState(srv.StaticNodes, srv.ntab, dynPeers, srv.NetRestrict)
|
dialer := newDialState(srv.StaticNodes, srv.BootstrapNodes, srv.ntab, dynPeers, srv.NetRestrict)
|
||||||
|
|
||||||
// handshake
|
// handshake
|
||||||
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
|
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: discover.PubkeyID(&srv.PrivateKey.PublicKey)}
|
||||||
|
Loading…
Reference in New Issue
Block a user