From 7f48ff671787e5d789394a2effb5de7c35c796c0 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 21 Jun 2019 15:45:02 -0700 Subject: [PATCH 1/6] watchtower: fix linter errors --- watchtower/wtclient/session_queue.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/watchtower/wtclient/session_queue.go b/watchtower/wtclient/session_queue.go index 649bf8fe..de802734 100644 --- a/watchtower/wtclient/session_queue.go +++ b/watchtower/wtclient/session_queue.go @@ -316,8 +316,8 @@ func (q *sessionQueue) drainBackups() { // before attempting to dequeue any pending updates. stateUpdate, isPending, backupID, err := q.nextStateUpdate() if err != nil { - log.Errorf("SessionQueue(%s) unable to get next state "+ - "update: %v", err) + log.Errorf("SessionQueue(%v) unable to get next state "+ + "update: %v", q.ID(), err) return } @@ -557,7 +557,7 @@ func (q *sessionQueue) sendStateUpdate(conn wtserver.Peer, // TODO(conner): borked watchtower err = fmt.Errorf("unable to ack seqnum=%d: %v", stateUpdate.SeqNum, err) - log.Errorf("SessionQueue(%s) failed to ack update: %v", err) + log.Errorf("SessionQueue(%v) failed to ack update: %v", q.ID(), err) return err case err == wtdb.ErrLastAppliedReversion: From 9c957193cffa6fda18fb07a4c2967293e4a558a2 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Jun 2019 18:48:57 -0700 Subject: [PATCH 2/6] discovery: remove retries from DNS based SampleNodeAddrs, allow down seeds In this commit, we modify the `SampleNodeAddrs` method to no longer retry itself. Instead, we'll now leave this task to the caller of the this method. Additionally, we'll no longer return with an error if we can't hit a particular seed. Instead, we'll log the error and move onto the next seed. Finally, we'll also no longer require that the DNS seed has a secondary seed in order to support a wider array of DNS seeds. --- discovery/bootstrapper.go | 247 ++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 118 deletions(-) diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index e53ecfe3..5ead175c 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -379,135 +379,146 @@ func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32, var netAddrs []*lnwire.NetAddress - // We'll continue this loop until we reach our target address limit. - // Each SRV query to the seed will return 25 random nodes, so we can - // continue to query until we reach our target. + // We'll try all the registered DNS seeds, exiting early if one of them + // gives us all the peers we need. + // + // TODO(roasbeef): should combine results from both search: - for uint32(len(netAddrs)) < numAddrs { - for _, dnsSeedTuple := range d.dnsSeeds { - // We'll first query the seed with an SRV record so we - // can obtain a random sample of the encoded public - // keys of nodes. We use the lndLookupSRV function for - // this task. - primarySeed := dnsSeedTuple[0] - _, addrs, err := d.net.LookupSRV("nodes", "tcp", primarySeed) - if err != nil { - log.Tracef("Unable to lookup SRV records via "+ - "primary seed: %v", err) + for _, dnsSeedTuple := range d.dnsSeeds { + // We'll first query the seed with an SRV record so we can + // obtain a random sample of the encoded public keys of nodes. + // We use the lndLookupSRV function for this task. + primarySeed := dnsSeedTuple[0] + _, addrs, err := d.net.LookupSRV("nodes", "tcp", primarySeed) + if err != nil { + log.Tracef("Unable to lookup SRV records via "+ + "primary seed (%v): %v", primarySeed, err) - log.Trace("Falling back to secondary") + log.Trace("Falling back to secondary") - // If the host of the secondary seed is blank, - // then we'll bail here as we can't proceed. - if dnsSeedTuple[1] == "" { - return nil, fmt.Errorf("Secondary seed is blank") - } - - // If we get an error when trying to query via - // the primary seed, we'll fallback to the - // secondary seed before concluding failure. - soaShim := dnsSeedTuple[1] - addrs, err = d.fallBackSRVLookup( - soaShim, primarySeed, - ) - if err != nil { - return nil, err - } - log.Tracef("Successfully queried fallback DNS seed") + // If the host of the secondary seed is blank, then + // we'll bail here as we can't proceed. + if dnsSeedTuple[1] == "" { + log.Tracef("DNS seed %v has no secondary, "+ + "skipping fallback", primarySeed) + continue } - log.Tracef("Retrieved SRV records from dns seed: %v", - spew.Sdump(addrs)) + // If we get an error when trying to query via the + // primary seed, we'll fallback to the secondary seed + // before concluding failure. + soaShim := dnsSeedTuple[1] + addrs, err = d.fallBackSRVLookup( + soaShim, primarySeed, + ) + if err != nil { + log.Tracef("Unable to query fall "+ + "back dns seed (%v): %v", soaShim, err) + continue + } - // Next, we'll need to issue an A record request for - // each of the nodes, skipping it if nothing comes - // back. - for _, nodeSrv := range addrs { - if uint32(len(netAddrs)) >= numAddrs { - break search - } + log.Tracef("Successfully queried fallback DNS seed") + } - // With the SRV target obtained, we'll now - // perform another query to obtain the IP - // address for the matching bech32 encoded node - // key. We use the lndLookup function for this - // task. - bechNodeHost := nodeSrv.Target - addrs, err := d.net.LookupHost(bechNodeHost) - if err != nil { - return nil, err - } + log.Tracef("Retrieved SRV records from dns seed: %v", + newLogClosure(func() string { + return spew.Sdump(addrs) + }), + ) - if len(addrs) == 0 { - log.Tracef("No addresses for %v, skipping", - bechNodeHost) + // Next, we'll need to issue an A record request for each of + // the nodes, skipping it if nothing comes back. + for _, nodeSrv := range addrs { + if uint32(len(netAddrs)) >= numAddrs { + break search + } + + // With the SRV target obtained, we'll now perform + // another query to obtain the IP address for the + // matching bech32 encoded node key. We use the + // lndLookup function for this task. + bechNodeHost := nodeSrv.Target + addrs, err := d.net.LookupHost(bechNodeHost) + if err != nil { + return nil, err + } + + if len(addrs) == 0 { + log.Tracef("No addresses for %v, skipping", + bechNodeHost) + continue + } + + log.Tracef("Attempting to convert: %v", bechNodeHost) + + // If the host isn't correctly formatted, then we'll + // skip it. + if len(bechNodeHost) == 0 || + !strings.Contains(bechNodeHost, ".") { + + continue + } + + // If we have a set of valid addresses, then we'll need + // to parse the public key from the original bech32 + // encoded string. + bechNode := strings.Split(bechNodeHost, ".") + _, nodeBytes5Bits, err := bech32.Decode(bechNode[0]) + if err != nil { + return nil, err + } + + // Once we have the bech32 decoded pubkey, we'll need + // to convert the 5-bit word grouping into our regular + // 8-bit word grouping so we can convert it into a + // public key. + nodeBytes, err := bech32.ConvertBits( + nodeBytes5Bits, 5, 8, false, + ) + if err != nil { + return nil, err + } + nodeKey, err := btcec.ParsePubKey( + nodeBytes, btcec.S256(), + ) + if err != nil { + return nil, err + } + + // If we have an ignore list, and this node is in the + // ignore list, then we'll go to the next candidate. + if ignore != nil { + nID := autopilot.NewNodeID(nodeKey) + if _, ok := ignore[nID]; ok { continue } - - log.Tracef("Attempting to convert: %v", bechNodeHost) - - // If we have a set of valid addresses, then - // we'll need to parse the public key from the - // original bech32 encoded string. - bechNode := strings.Split(bechNodeHost, ".") - _, nodeBytes5Bits, err := bech32.Decode(bechNode[0]) - if err != nil { - return nil, err - } - - // Once we have the bech32 decoded pubkey, - // we'll need to convert the 5-bit word - // grouping into our regular 8-bit word - // grouping so we can convert it into a public - // key. - nodeBytes, err := bech32.ConvertBits( - nodeBytes5Bits, 5, 8, false, - ) - if err != nil { - return nil, err - } - nodeKey, err := btcec.ParsePubKey( - nodeBytes, btcec.S256(), - ) - if err != nil { - return nil, err - } - - // If we have an ignore list, and this node is - // in the ignore list, then we'll go to the - // next candidate. - if ignore != nil { - nID := autopilot.NewNodeID(nodeKey) - if _, ok := ignore[nID]; ok { - continue - } - } - - // Finally we'll convert the host:port peer to - // a proper TCP address to use within the - // lnwire.NetAddress. We don't need to use - // the lndResolveTCP function here because we - // already have the host:port peer. - addr := net.JoinHostPort(addrs[0], - strconv.FormatUint(uint64(nodeSrv.Port), 10)) - tcpAddr, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - return nil, err - } - - // Finally, with all the information parsed, - // we'll return this fully valid address as a - // connection attempt. - lnAddr := &lnwire.NetAddress{ - IdentityKey: nodeKey, - Address: tcpAddr, - } - - log.Tracef("Obtained %v as valid reachable "+ - "node", lnAddr) - - netAddrs = append(netAddrs, lnAddr) } + + // Finally we'll convert the host:port peer to a proper + // TCP address to use within the lnwire.NetAddress. We + // don't need to use the lndResolveTCP function here + // because we already have the host:port peer. + addr := net.JoinHostPort( + addrs[0], + strconv.FormatUint(uint64(nodeSrv.Port), 10), + ) + tcpAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, err + } + + // Finally, with all the information parsed, we'll + // return this fully valid address as a connection + // attempt. + lnAddr := &lnwire.NetAddress{ + IdentityKey: nodeKey, + Address: tcpAddr, + } + + log.Tracef("Obtained %v as valid reachable "+ + "node", lnAddr) + + netAddrs = append(netAddrs, lnAddr) } } From abe73ca6c116212381c3866f88fb627871787000 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Jun 2019 18:49:48 -0700 Subject: [PATCH 3/6] server: don't re-use existing wait group for loops within initialPeerBootstrap --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 900e6d62..28499f05 100644 --- a/server.go +++ b/server.go @@ -1719,7 +1719,6 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, numTargetPeers uint32, bootstrappers []discovery.NetworkPeerBootstrapper) { - var wg sync.WaitGroup for { // Check if the server has been requested to shut down in order @@ -1752,6 +1751,7 @@ func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, // Then, we'll attempt to establish a connection to the // different peer addresses retrieved by our bootstrappers. + var wg sync.WaitGroup for _, bootstrapAddr := range bootstrapAddrs { wg.Add(1) go func(addr *lnwire.NetAddress) { From efab9cb584eeef1274425a951fe79d60acd7962c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Jun 2019 18:50:28 -0700 Subject: [PATCH 4/6] server: extract backOffCeiling into bootstrapBackOffCeiling as constant --- server.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index 28499f05..e2e77a8f 100644 --- a/server.go +++ b/server.go @@ -1600,7 +1600,6 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, // // We'll use a 15 second backoff, and double the time every time an // epoch fails up to a ceiling. - const backOffCeiling = time.Minute * 5 backOff := time.Second * 15 // We'll create a new ticker to wake us up every 15 seconds so we can @@ -1643,8 +1642,8 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, sampleTicker.Stop() backOff *= 2 - if backOff > backOffCeiling { - backOff = backOffCeiling + if backOff > bootstrapBackOffCeiling { + backOff = bootstrapBackOffCeiling } srvrLog.Debugf("Backing off peer bootstrapper to "+ @@ -1713,6 +1712,11 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, } } +// bootstrapBackOffCeiling is the maximum amount of time we'll wait between +// failed attempts to locate a set of bootstrap peers. We'll slowly double our +// query back off each time we encounter a failure. +const bootstrapBackOffCeiling = time.Minute * 5 + // initialPeerBootstrap attempts to continuously connect to peers on startup // until the target number of peers has been reached. This ensures that nodes // receive an up to date network view as soon as possible. From 0470d2760308b4b4a2cfa027bebc3af2b361520f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Jun 2019 19:04:11 -0700 Subject: [PATCH 5/6] server: add an exponential back off to initialPeerBootstrap In this commit we add exponential back off to the `initialPeerBootstrap` method. Before this change, if the DNS seed was down, we would hammer it in an attempt to get our initial set of peers. This makes this section a bit less aggressive, but saves log spam and also will hit the DNS servers less frequently. --- server.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index e2e77a8f..ed168188 100644 --- a/server.go +++ b/server.go @@ -1723,8 +1723,16 @@ const bootstrapBackOffCeiling = time.Minute * 5 func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, numTargetPeers uint32, bootstrappers []discovery.NetworkPeerBootstrapper) { + // We'll start off by waiting 2 seconds between failed attempts, then + // double each time we fail until we hit the bootstrapBackOffCeiling. + var delaySignal <-chan time.Time + delayTime := time.Second * 2 - for { + // As want to be more aggressive, we'll use a lower back off celling + // then the main peer bootstrap logic. + backOffCeiling := bootstrapBackOffCeiling / 5 + + for attempts := 0; ; attempts++ { // Check if the server has been requested to shut down in order // to prevent blocking. if s.Stopped() { @@ -1741,8 +1749,31 @@ func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, return } - // Otherwise, we'll request for the remaining number of peers in - // order to reach our target. + if attempts > 0 { + srvrLog.Debugf("Waiting %v before trying to locate "+ + "bootstrap peers (attempt #%v)", delayTime, + attempts) + + // We've completed at least one iterating and haven't + // finished, so we'll start to insert a delay period + // between each attempt. + delaySignal = time.After(delayTime) + select { + case <-delaySignal: + case <-s.quit: + return + } + + // After our delay, we'll double the time we wait up to + // the max back off period. + delayTime *= 2 + if delayTime > backOffCeiling { + delayTime = backOffCeiling + } + } + + // Otherwise, we'll request for the remaining number of peers + // in order to reach our target. peersNeeded := numTargetPeers - numActivePeers bootstrapAddrs, err := discovery.MultiSourceBootstrap( ignore, peersNeeded, bootstrappers..., From 831afdc91904586d1e47f0cd094e6848982c30f4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 27 Jun 2019 19:06:34 -0700 Subject: [PATCH 6/6] lnd: add secondary DNS seed for mainnet --- chainregistry.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chainregistry.go b/chainregistry.go index 4962ee02..529fcb10 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -576,6 +576,9 @@ var ( "nodes.lightning.directory", "soa.nodes.lightning.directory", }, + { + "lseed.bitcoinstats.com", + }, }, bitcoinTestnetGenesis: {