diff --git a/docs/release-notes/release-notes-0.16.0.md b/docs/release-notes/release-notes-0.16.0.md index ea4a25f290..d70f5c90ab 100644 --- a/docs/release-notes/release-notes-0.16.0.md +++ b/docs/release-notes/release-notes-0.16.0.md @@ -130,6 +130,8 @@ crash](https://github.com/lightningnetwork/lnd/pull/7019). * [test: fix loop variables being accessed in closures](https://github.com/lightningnetwork/lnd/pull/7032). +* [Do not delay session negotiation with first candidate tower]() + ### Tooling and documentation * [The `golangci-lint` tool was updated to diff --git a/watchtower/wtclient/candidate_iterator.go b/watchtower/wtclient/candidate_iterator.go index 5b48a68ef3..d3bdb5d885 100644 --- a/watchtower/wtclient/candidate_iterator.go +++ b/watchtower/wtclient/candidate_iterator.go @@ -25,6 +25,10 @@ type TowerCandidateIterator interface { // iterator. IsActive(wtdb.TowerID) bool + // IsEmpty determines whether the iterator has any candidate towers + // with which we could potentially establish a session. + IsEmpty() bool + // Reset clears any internal iterator state, making previously taken // candidates available as long as they remain in the set. Reset() error @@ -162,4 +166,13 @@ func (t *towerListIterator) IsActive(tower wtdb.TowerID) bool { return ok } +// IsEmpty indicates whether the iterator has any candidate towers +// with which we could potentially establish a session. +func (t *towerListIterator) IsEmpty() bool { + t.mu.Lock() + defer t.mu.Unlock() + + return len(t.candidates) == 0 +} + // TODO(conner): implement graph-backed candidate iterator for public towers. diff --git a/watchtower/wtclient/candidate_iterator_test.go b/watchtower/wtclient/candidate_iterator_test.go index 99547d794b..6cc2ccd744 100644 --- a/watchtower/wtclient/candidate_iterator_test.go +++ b/watchtower/wtclient/candidate_iterator_test.go @@ -100,6 +100,12 @@ func TestTowerCandidateIterator(t *testing.T) { } towerIterator := newTowerListIterator(towerCopies...) + // The iterator has towers and should report as non-empty. + empty := towerIterator.IsEmpty() + if empty { + t.Fatal("iterator with towers incorrectly reports as empty") + } + // We should expect to see all of our candidates in the order that they // were added. for _, expTower := range towers { @@ -152,4 +158,11 @@ func TestTowerCandidateIterator(t *testing.T) { towerIterator.AddCandidate(secondTower) assertActiveCandidate(t, towerIterator, secondTower, true) assertNextCandidate(t, towerIterator, secondTower) + + // An empty iterator should correctly report so. + emptyIterator := newTowerListIterator() + empty = emptyIterator.IsEmpty() + if !empty { + t.Fatal("empty iterator incorrectly reports as non-empty") + } } diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go index 35c0ef22b2..0cc02be069 100644 --- a/watchtower/wtclient/client.go +++ b/watchtower/wtclient/client.go @@ -760,8 +760,52 @@ func (c *TowerClient) backupDispatcher() { for { switch { - // No active session queue and no additional sessions. + // No active session queue and no additional sessions, + // so we'll need to request a new one. case c.sessionQueue == nil && len(c.candidateSessions) == 0: + + // Wait until we have at least one candidate tower. + // with which to request a session. + if c.candidateTowers.IsEmpty() { + c.log.Debug("No candidate towers. Waiting " + + "for tower before requesting session.") + + var towerID []byte + + awaitTower: + select { + // A candidate tower has been added. + case msg := <-c.newTowers: + msg.errChan <- c.handleNewTower(msg) + + towerID = msg.addr.IdentityKey. + SerializeCompressed()[:10] + + case msg := <-c.staleTowers: + msg.errChan <- errors.New("there are " + + "no towers to remove") + goto awaitTower + + case <-c.statTicker.C: + c.log.Infof("Client stats: %s", c.stats) + goto awaitTower + + case <-c.forceQuit: + return + } + + // If we have any sessions from previous + // negotiations with this tower then + // we'll use those. + if len(c.candidateSessions) != 0 { + c.log.Debugf("Using previously "+ + "negotiated session(s) for "+ + "tower: %x", towerID) + + continue + } + } + c.log.Infof("Requesting new session.") // Immediately request a new session.