diff --git a/config.go b/config.go index 56f3689c..c8441e44 100644 --- a/config.go +++ b/config.go @@ -256,6 +256,8 @@ type config struct { RejectPush bool `long:"rejectpush" description:"If true, lnd will not accept channel opening requests with non-zero push amounts. This should prevent accidental pushes to merchant nodes."` + StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"` + net tor.Net Routing *routing.Conf `group:"routing" namespace:"routing"` diff --git a/server.go b/server.go index 955c782f..81bb55c9 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "fmt" "image/color" "math/big" + prand "math/rand" "net" "path/filepath" "regexp" @@ -60,6 +61,18 @@ const ( // durations exceeding this value will be eligible to have their // backoffs reduced. defaultStableConnDuration = 10 * time.Minute + + // numInstantInitReconnect specifies how many persistent peers we should + // always attempt outbound connections to immediately. After this value + // is surpassed, the remaining peers will be randomly delayed using + // maxInitReconnectDelay. + numInstantInitReconnect = 10 + + // maxInitReconnectDelay specifies the maximum delay in seconds we will + // apply in attempting to reconnect to persistent peers on startup. The + // value used or a particular peer will be chosen between 0s and this + // value. + maxInitReconnectDelay = 30 ) var ( @@ -1932,6 +1945,7 @@ func (s *server) establishPersistentConnections() error { // Iterate through the combined list of addresses from prior links and // node announcements and attempt to reconnect to each node. + var numOutboundConns int for pubStr, nodeAddr := range nodeAddrsMap { // Add this peer to the set of peers we should maintain a // persistent connection with. @@ -1962,13 +1976,42 @@ func (s *server) establishPersistentConnections() error { s.persistentConnReqs[pubStr] = append( s.persistentConnReqs[pubStr], connReq) - go s.connMgr.Connect(connReq) + // We'll connect to the first 10 peers immediately, then + // randomly stagger any remaining connections if the + // stagger initial reconnect flag is set. This ensures + // that mobile nodes or nodes with a small number of + // channels obtain connectivity quickly, but larger + // nodes are able to disperse the costs of connecting to + // all peers at once. + if numOutboundConns < numInstantInitReconnect || + !cfg.StaggerInitialReconnect { + + go s.connMgr.Connect(connReq) + } else { + go s.delayInitialReconnect(connReq) + } } + + numOutboundConns++ } return nil } +// delayInitialReconnect will attempt a reconnection using the passed connreq +// after sampling a value for the delay between 0s and the +// maxInitReconnectDelay. +// +// NOTE: This method MUST be run as a goroutine. +func (s *server) delayInitialReconnect(connReq *connmgr.ConnReq) { + delay := time.Duration(prand.Intn(maxInitReconnectDelay)) * time.Second + select { + case <-time.After(delay): + s.connMgr.Connect(connReq) + case <-s.quit: + } +} + // prunePersistentPeerConnection removes all internal state related to // persistent connections to a peer within the server. This is used to avoid // persistent connection retries to peers we do not have any open channels with.