Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ func defaultConfig() *Config {
// loadAndValidateConfig loads the terminal's main configuration and validates
// its content.
func loadAndValidateConfig(ctx context.Context,
interceptor signal.Interceptor) (*Config, error) {
interceptor signal.Interceptor,
litdShutdown *shutdownSource) (*Config, error) {

// Start with the default configuration.
preCfg := defaultConfig()
Expand Down Expand Up @@ -614,7 +615,7 @@ func loadAndValidateConfig(ctx context.Context,
preCfg.Lnd.SubLogMgr = build.NewSubLoggerManager(
build.NewDefaultLogHandlers(logCfg, preCfg.Lnd.LogRotator)...,
)
SetupLoggers(preCfg.Lnd.SubLogMgr, interceptor)
SetupLoggers(preCfg.Lnd.SubLogMgr, interceptor, litdShutdown)

// Load the main configuration file and parse any command line options.
// This function will also set up logging properly.
Expand Down
15 changes: 15 additions & 0 deletions docs/release-notes/release-notes-0.17.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,23 @@

### Functional Changes/Additions

* [Keep litd running after lnd
stops](https://github.com/lightninglabs/lightning-terminal/pull/1329):
In integrated mode, litd no longer shuts down when lnd stops. It now marks lnd
as errored, tears down the lnd dependent sub-servers and keeps running so its
status endpoint stays available to report what happened. Note that calling
lnd's `StopDaemon` (for example `lncli stop`) no longer stops litd; use litd's
own `StopDaemon` to stop everything.

### Technical and Architectural Updates

* [Stop itest litd nodes via litd's own
`StopDaemon`](https://github.com/lightninglabs/lightning-terminal/pull/1356):
The itest harness now sets up a lit connection for every node, seed based or
not, so nodes are stopped through litd's own `StopDaemon`. For stateless init
nodes, which keep no macaroon files on disk, the harness bakes a lit super
macaroon from the admin macaroon to authenticate that call.

* [Report litd's own version for `litd
-V`](https://github.com/lightninglabs/lightning-terminal/pull/1337): The `-V`
flag now prints litd's version instead of the integrated lnd version.
Expand Down
129 changes: 129 additions & 0 deletions itest/litd_lnd_shutdown_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package itest

import (
"context"
"fmt"

"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightninglabs/lightning-terminal/subservers"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)

// testLitdSurvivesLndShutdown asserts that when lnd stops in integrated mode,
// litd keeps running and its status endpoint stays reachable, reporting lnd as
// stopped rather than cascading to a full litd shutdown.
func testLitdSurvivesLndShutdown(ctx context.Context, net *NetworkHarness,
t *harnessTest) {

// Use a dedicated, throwaway node so that stopping its lnd cannot
// affect any other test.
node, err := net.NewNode(
t.t, "lnd-stop", nil, false, true, "--autopilot.disable",
)
Comment on lines +22 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The 5th argument to net.NewNode is remoteMode. Passing true runs the test in remote mode, which does not test the integrated mode lifecycle changes introduced in this PR. This should be set to false to test the integrated mode behavior.

Suggested change
node, err := net.NewNode(
t.t, "lnd-stop", nil, false, true, "--autopilot.disable",
)
node, err := net.NewNode(
t.t, "lnd-stop", nil, false, false, "--autopilot.disable",
)

require.NoError(t.t, err)

// The harness ShutdownNode stops litd (via its StopDaemon) and waits
// for the process to exit, so no manual litd shutdown is needed here.
defer func() {
_ = net.ShutdownNode(node)
}()

ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()

rawConn, err := connectLitRPC(
ctxt, node.Cfg.LitAddr(), node.Cfg.LitTLSCertPath,
node.Cfg.LitMacPath,
)
require.NoError(t.t, err)
defer func() { _ = rawConn.Close() }()

statusClient := litrpc.NewStatusClient(rawConn)

// lnd should be running before we stop it.
resp, err := statusClient.SubServerStatus(
ctxt, &litrpc.SubServerStatusReq{},
)
require.NoError(t.t, err)

lndStatus, ok := resp.SubServers[subservers.LND]
require.True(t.t, ok, "expected lnd sub-server status")
require.True(t.t, lndStatus.Running, "lnd should be running initially")

// Stop lnd (but not litd) via the lnrpc StopDaemon. The call may return
// an error as the connection is torn down, which is expected.
_, _ = node.LightningClient.StopDaemon(ctxt, &lnrpc.StopRequest{})

// litd's status endpoint must remain reachable and must report lnd as
// no longer running, with an error explaining why. We poll because lnd
// shutdown is asynchronous.
err = wait.NoError(func() error {
ctxs, cancels := context.WithTimeout(
context.Background(), defaultTimeout,
)
defer cancels()

resp, err := statusClient.SubServerStatus(
ctxs, &litrpc.SubServerStatusReq{},
)
if err != nil {
return fmt.Errorf("litd status endpoint unreachable "+
"after lnd stopped: %w", err)
}

lndStatus, ok := resp.SubServers[subservers.LND]
if !ok {
return fmt.Errorf("no lnd sub-server status returned")
}

if lndStatus.Running {
return fmt.Errorf("lnd still reported as running")
}

if lndStatus.Error == "" {
return fmt.Errorf("lnd error message not yet set")
}

return nil
}, defaultTimeout)
require.NoError(t.t, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also test here that the lit sub-server remains up and running after this, i.e. that statusClient.SubServerStatus still responds and that the LIT sub-server the has "Running" set to true. Could be worth testing that this is the case for maybe 3 more seconds or something similar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. After lnd is reported stopped, the test now checks that the LIT sub-server stays reachable and Running for 3 seconds.


// litd's process itself must keep running after lnd has stopped: its

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last commit can be squashed with the commit called:
"itest: add test for litd surviving lnd shutdown"

// status endpoint stays reachable. The LIT sub-server is reported as no
// longer running, since litd cannot function without lnd, but the
// endpoint continuing to answer at all is what proves litd did not
// cascade down with lnd. Check this holds over time.
err = wait.InvariantNoError(func() error {
ctxs, cancels := context.WithTimeout(
context.Background(), defaultTimeout,
)
defer cancels()

resp, err := statusClient.SubServerStatus(
ctxs, &litrpc.SubServerStatusReq{},
)
if err != nil {
return fmt.Errorf("litd status endpoint must stay "+
"reachable after lnd stopped: %w", err)
}

litStatus, ok := resp.SubServers[subservers.LIT]
if !ok {
return fmt.Errorf("no lit sub-server status returned")
}

if litStatus.Running {
return fmt.Errorf("lit sub-server should be reported " +
"as stopped once lnd has stopped")
}

if litStatus.Error == "" {
return fmt.Errorf("lit sub-server error message not set")
}

return nil
}, defaultTimeout)
require.NoError(t.t, err)
}
Loading