|
| 1 | +package itest |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + |
| 7 | + "github.com/lightninglabs/lightning-terminal/litrpc" |
| 8 | + "github.com/lightninglabs/lightning-terminal/subservers" |
| 9 | + "github.com/lightningnetwork/lnd/lnrpc" |
| 10 | + "github.com/lightningnetwork/lnd/lntest/wait" |
| 11 | + "github.com/stretchr/testify/require" |
| 12 | +) |
| 13 | + |
| 14 | +// testLitdSurvivesLndShutdown asserts that when lnd stops in integrated mode, |
| 15 | +// litd keeps running and its status endpoint stays reachable, reporting lnd as |
| 16 | +// stopped rather than cascading to a full litd shutdown. |
| 17 | +func testLitdSurvivesLndShutdown(ctx context.Context, net *NetworkHarness, |
| 18 | + t *harnessTest) { |
| 19 | + |
| 20 | + // Use a dedicated, throwaway node so that stopping its lnd cannot |
| 21 | + // affect any other test. |
| 22 | + node, err := net.NewNode( |
| 23 | + t.t, "lnd-stop", nil, false, true, "--autopilot.disable", |
| 24 | + ) |
| 25 | + require.NoError(t.t, err) |
| 26 | + |
| 27 | + // The harness ShutdownNode stops litd (via its StopDaemon) and waits |
| 28 | + // for the process to exit, so no manual litd shutdown is needed here. |
| 29 | + defer func() { |
| 30 | + _ = net.ShutdownNode(node) |
| 31 | + }() |
| 32 | + |
| 33 | + ctxt, cancel := context.WithTimeout(ctx, defaultTimeout) |
| 34 | + defer cancel() |
| 35 | + |
| 36 | + rawConn, err := connectLitRPC( |
| 37 | + ctxt, node.Cfg.LitAddr(), node.Cfg.LitTLSCertPath, |
| 38 | + node.Cfg.LitMacPath, |
| 39 | + ) |
| 40 | + require.NoError(t.t, err) |
| 41 | + defer func() { _ = rawConn.Close() }() |
| 42 | + |
| 43 | + statusClient := litrpc.NewStatusClient(rawConn) |
| 44 | + |
| 45 | + // lnd should be running before we stop it. |
| 46 | + resp, err := statusClient.SubServerStatus( |
| 47 | + ctxt, &litrpc.SubServerStatusReq{}, |
| 48 | + ) |
| 49 | + require.NoError(t.t, err) |
| 50 | + |
| 51 | + lndStatus, ok := resp.SubServers[subservers.LND] |
| 52 | + require.True(t.t, ok, "expected lnd sub-server status") |
| 53 | + require.True(t.t, lndStatus.Running, "lnd should be running initially") |
| 54 | + |
| 55 | + // Stop lnd (but not litd) via the lnrpc StopDaemon. The call may return |
| 56 | + // an error as the connection is torn down, which is expected. |
| 57 | + _, _ = node.LightningClient.StopDaemon(ctxt, &lnrpc.StopRequest{}) |
| 58 | + |
| 59 | + // litd's status endpoint must remain reachable and must report lnd as |
| 60 | + // no longer running, with an error explaining why. We poll because lnd |
| 61 | + // shutdown is asynchronous. |
| 62 | + err = wait.NoError(func() error { |
| 63 | + ctxs, cancels := context.WithTimeout( |
| 64 | + context.Background(), defaultTimeout, |
| 65 | + ) |
| 66 | + defer cancels() |
| 67 | + |
| 68 | + resp, err := statusClient.SubServerStatus( |
| 69 | + ctxs, &litrpc.SubServerStatusReq{}, |
| 70 | + ) |
| 71 | + if err != nil { |
| 72 | + return fmt.Errorf("litd status endpoint unreachable "+ |
| 73 | + "after lnd stopped: %w", err) |
| 74 | + } |
| 75 | + |
| 76 | + lndStatus, ok := resp.SubServers[subservers.LND] |
| 77 | + if !ok { |
| 78 | + return fmt.Errorf("no lnd sub-server status returned") |
| 79 | + } |
| 80 | + |
| 81 | + if lndStatus.Running { |
| 82 | + return fmt.Errorf("lnd still reported as running") |
| 83 | + } |
| 84 | + |
| 85 | + if lndStatus.Error == "" { |
| 86 | + return fmt.Errorf("lnd error message not yet set") |
| 87 | + } |
| 88 | + |
| 89 | + return nil |
| 90 | + }, defaultTimeout) |
| 91 | + require.NoError(t.t, err) |
| 92 | + |
| 93 | + // litd's process itself must keep running after lnd has stopped: its |
| 94 | + // status endpoint stays reachable. The LIT sub-server is reported as no |
| 95 | + // longer running, since litd cannot function without lnd, but the |
| 96 | + // endpoint continuing to answer at all is what proves litd did not |
| 97 | + // cascade down with lnd. Check this holds over time. |
| 98 | + err = wait.InvariantNoError(func() error { |
| 99 | + ctxs, cancels := context.WithTimeout( |
| 100 | + context.Background(), defaultTimeout, |
| 101 | + ) |
| 102 | + defer cancels() |
| 103 | + |
| 104 | + resp, err := statusClient.SubServerStatus( |
| 105 | + ctxs, &litrpc.SubServerStatusReq{}, |
| 106 | + ) |
| 107 | + if err != nil { |
| 108 | + return fmt.Errorf("litd status endpoint must stay "+ |
| 109 | + "reachable after lnd stopped: %w", err) |
| 110 | + } |
| 111 | + |
| 112 | + litStatus, ok := resp.SubServers[subservers.LIT] |
| 113 | + if !ok { |
| 114 | + return fmt.Errorf("no lit sub-server status returned") |
| 115 | + } |
| 116 | + |
| 117 | + if litStatus.Running { |
| 118 | + return fmt.Errorf("lit sub-server should be reported " + |
| 119 | + "as stopped once lnd has stopped") |
| 120 | + } |
| 121 | + |
| 122 | + if litStatus.Error == "" { |
| 123 | + return fmt.Errorf("lit sub-server error message not set") |
| 124 | + } |
| 125 | + |
| 126 | + return nil |
| 127 | + }, defaultTimeout) |
| 128 | + require.NoError(t.t, err) |
| 129 | +} |
0 commit comments