Skip to content

Commit 3dbb15b

Browse files
committed
itest: stop litd nodes via litd's StopDaemon
1 parent 745f70f commit 3dbb15b

1 file changed

Lines changed: 149 additions & 41 deletions

File tree

itest/litd_node.go

Lines changed: 149 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import (
4545
"github.com/lightningnetwork/lnd/lntest/wait"
4646
"github.com/lightningnetwork/lnd/macaroons"
4747
"google.golang.org/grpc"
48+
"google.golang.org/grpc/codes"
4849
"google.golang.org/grpc/credentials"
50+
"google.golang.org/grpc/metadata"
51+
"google.golang.org/grpc/status"
4952
"gopkg.in/macaroon.v2"
5053
)
5154

@@ -384,6 +387,13 @@ type HarnessNode struct {
384387
// litConn is the underlying connection to Lit's grpc endpoint.
385388
litConn *grpc.ClientConn
386389

390+
// litMacPath, when set, is the macaroon file used to authenticate
391+
// litConn. It is set for stateless init nodes, which have no lit
392+
// macaroon on disk: we bake a super macaroon from the in-memory admin
393+
// macaroon and point this at it so the node can still be stopped via
394+
// litd's own StopDaemon.
395+
litMacPath string
396+
387397
// RouterClient, WalletKitClient, WatchtowerClient cannot be embedded,
388398
// because a name collision would occur with LightningClient.
389399
RouterClient routerrpc.RouterClient
@@ -776,31 +786,9 @@ func (hn *HarnessNode) Start(litdBinary string, litdError chan<- error,
776786
return nil
777787
}
778788

779-
err = hn.initLightningClient(conn)
780-
if err != nil {
781-
return fmt.Errorf("could not init Lightning Client: %w", err)
782-
}
783-
784-
// Also connect to Lit's RPC port for any Litd specific calls.
785-
litConn, err := connectLitRPC(
786-
context.Background(), hn.Cfg.LitAddr(), hn.Cfg.LitTLSCertPath,
787-
hn.Cfg.LitMacPath,
788-
)
789-
if err != nil {
790-
return fmt.Errorf("could not connect to Lit RPC: %w", err)
791-
}
792-
hn.litConn = litConn
793-
794-
ctxt, cancel := context.WithTimeout(
795-
context.Background(), lntest.DefaultTimeout,
796-
)
797-
defer cancel()
798-
return wait.NoError(func() error {
799-
litConn := litrpc.NewProxyClient(hn.litConn)
800-
801-
_, err = litConn.GetInfo(ctxt, &litrpc.GetInfoRequest{})
802-
return err
803-
}, lntest.DefaultTimeout)
789+
// initLightningClient also connects to Lit's RPC port and sets
790+
// hn.litConn so the node can be stopped via litd's own StopDaemon.
791+
return hn.initLightningClient(conn)
804792
}
805793

806794
// WaitForLNDWalletReady waits until the wallet state flips from
@@ -1087,6 +1075,60 @@ func (hn *HarnessNode) initClientWhenReady(timeout time.Duration) error {
10871075
return hn.initLightningClient(conn)
10881076
}
10891077

1078+
// bakeLitSuperMacaroon bakes a lit super macaroon from the given admin macaroon
1079+
// (returned in-memory during stateless init, where no lit macaroon is written
1080+
// to disk), writes it next to the node's lit config, and records its path so
1081+
// litConn can authenticate against it. This lets the harness stop a stateless
1082+
// node via litd's own StopDaemon.
1083+
func (hn *HarnessNode) bakeLitSuperMacaroon(adminMac []byte) error {
1084+
ctxt, cancel := context.WithTimeout(
1085+
context.Background(), lntest.DefaultTimeout,
1086+
)
1087+
defer cancel()
1088+
1089+
rawConn, err := connectLitRPC(
1090+
ctxt, hn.Cfg.LitAddr(), hn.Cfg.LitTLSCertPath, "",
1091+
)
1092+
if err != nil {
1093+
return err
1094+
}
1095+
defer rawConn.Close()
1096+
1097+
// Attach the admin macaroon to the request context so litd authorizes
1098+
// the bake call.
1099+
macCtx := metadata.NewOutgoingContext(ctxt, metadata.MD{
1100+
"macaroon": []string{hex.EncodeToString(adminMac)},
1101+
})
1102+
1103+
litConn := litrpc.NewProxyClient(rawConn)
1104+
resp, err := litConn.BakeSuperMacaroon(
1105+
macCtx, &litrpc.BakeSuperMacaroonRequest{
1106+
RootKeyIdSuffix: 0,
1107+
ReadOnly: false,
1108+
},
1109+
)
1110+
if err != nil {
1111+
return err
1112+
}
1113+
1114+
// BakeSuperMacaroon hex encodes the returned macaroon.
1115+
superMacBytes, err := hex.DecodeString(resp.Macaroon)
1116+
if err != nil {
1117+
return err
1118+
}
1119+
1120+
macPath := filepath.Join(
1121+
filepath.Dir(hn.Cfg.LitMacPath), "lit-super.macaroon",
1122+
)
1123+
if err := os.WriteFile(macPath, superMacBytes, 0644); err != nil {
1124+
return err
1125+
}
1126+
1127+
hn.litMacPath = macPath
1128+
1129+
return nil
1130+
}
1131+
10901132
// Init initializes a harness node by passing the init request via rpc. After
10911133
// the request is submitted, this method will block until a
10921134
// macaroon-authenticated RPC connection can be established to the harness node.
@@ -1126,6 +1168,17 @@ func (hn *HarnessNode) Init(ctx context.Context,
11261168
return nil, err
11271169
}
11281170

1171+
// In stateless init mode no lit macaroon is written to disk, so bake a
1172+
// super macaroon from the admin macaroon we just received. The harness
1173+
// uses it to stop the node via litd's own StopDaemon.
1174+
if initReq.StatelessInit {
1175+
err := hn.bakeLitSuperMacaroon(response.AdminMacaroon)
1176+
if err != nil {
1177+
return nil, fmt.Errorf("could not bake lit super "+
1178+
"macaroon: %w", err)
1179+
}
1180+
}
1181+
11291182
return response, hn.initLightningClient(conn)
11301183
}
11311184

@@ -1169,6 +1222,17 @@ func (hn *HarnessNode) InitChangePassword(ctx context.Context,
11691222
return nil, err
11701223
}
11711224

1225+
// In stateless init mode no lit macaroon is written to disk, so bake a
1226+
// super macaroon from the admin macaroon we just received. The harness
1227+
// uses it to stop the node via litd's own StopDaemon.
1228+
if chngPwReq.StatelessInit {
1229+
err := hn.bakeLitSuperMacaroon(response.AdminMacaroon)
1230+
if err != nil {
1231+
return nil, fmt.Errorf("could not bake lit super "+
1232+
"macaroon: %w", err)
1233+
}
1234+
}
1235+
11721236
return response, hn.initLightningClient(conn)
11731237
}
11741238

@@ -1252,6 +1316,41 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error {
12521316
return err
12531317
}
12541318

1319+
// Also connect to Lit's RPC port for any litd specific calls. We do
1320+
// this here, rather than only on the no-seed path in start, so that
1321+
// seed based nodes (which reach this method via Init or Unlock) get a
1322+
// litConn too. Stop relies on it to shut the node down via litd's own
1323+
// StopDaemon, which is the only way to bring litd down now that its
1324+
// lifecycle is decoupled from lnd's. For stateless nodes litMacPath
1325+
// points at a baked super macaroon; otherwise the lit macaroon on disk
1326+
// is used.
1327+
macPath := hn.Cfg.LitMacPath
1328+
if hn.litMacPath != "" {
1329+
macPath = hn.litMacPath
1330+
}
1331+
litConn, err := connectLitRPC(
1332+
context.Background(), hn.Cfg.LitAddr(), hn.Cfg.LitTLSCertPath,
1333+
macPath,
1334+
)
1335+
if err != nil {
1336+
return fmt.Errorf("could not connect to Lit RPC: %w", err)
1337+
}
1338+
hn.litConn = litConn
1339+
1340+
ctxt, cancel := context.WithTimeout(
1341+
context.Background(), lntest.DefaultTimeout,
1342+
)
1343+
defer cancel()
1344+
err = wait.NoError(func() error {
1345+
litClient := litrpc.NewProxyClient(hn.litConn)
1346+
1347+
_, err := litClient.GetInfo(ctxt, &litrpc.GetInfoRequest{})
1348+
return err
1349+
}, lntest.DefaultTimeout)
1350+
if err != nil {
1351+
return err
1352+
}
1353+
12551354
// Launch the watcher that will hook into graph related topology change
12561355
// from the PoV of this node.
12571356
hn.wg.Add(1)
@@ -1423,9 +1522,31 @@ func (hn *HarnessNode) Stop() error {
14231522
return nil
14241523
}
14251524

1426-
// If start() failed before creating a client, we will just wait for the
1427-
// child process to die.
1428-
if !hn.Cfg.RemoteMode && hn.LightningClient != nil {
1525+
// litd owns its own lifecycle, so we ask it to stop via its own
1526+
// StopDaemon RPC. In integrated mode this also brings down the embedded
1527+
// lnd.
1528+
switch {
1529+
case hn.litConn != nil:
1530+
ctx, cancel := context.WithTimeout(
1531+
context.Background(), lntest.DefaultTimeout,
1532+
)
1533+
litConn := litrpc.NewProxyClient(hn.litConn)
1534+
1535+
// litd normally answers before it shuts down, but the
1536+
// connection can also be torn down first as litd begins
1537+
// shutting down. That surfaces as an Unavailable status and
1538+
// is expected. Any other error is not, so surface it. We
1539+
// confirm the process actually exits via processExit below.
1540+
_, err := litConn.StopDaemon(ctx, &litrpc.StopDaemonRequest{})
1541+
cancel()
1542+
if err != nil && status.Code(err) != codes.Unavailable {
1543+
return err
1544+
}
1545+
1546+
// If start() failed before the lit connection was created, fall back to
1547+
// stopping the integrated lnd directly and just wait for the child
1548+
// process to die.
1549+
case !hn.Cfg.RemoteMode && hn.LightningClient != nil:
14291550
// Don't watch for error because sometimes the RPC connection
14301551
// gets closed before a response is returned.
14311552
req := lnrpc.StopRequest{}
@@ -1450,19 +1571,6 @@ func (hn *HarnessNode) Stop() error {
14501571
if err != nil {
14511572
return err
14521573
}
1453-
} else if hn.Cfg.RemoteMode {
1454-
// If lit is running in remote mode, then calling LNDs
1455-
// StopDaemon method will not shut down Lit, and so we need to
1456-
// explicitly request lit to shut down.
1457-
ctx, cancel := context.WithTimeout(
1458-
context.Background(), lntest.DefaultTimeout,
1459-
)
1460-
litConn := litrpc.NewProxyClient(hn.litConn)
1461-
_, err := litConn.StopDaemon(ctx, &litrpc.StopDaemonRequest{})
1462-
cancel()
1463-
if err != nil {
1464-
return err
1465-
}
14661574
}
14671575

14681576
// Wait for litd process and other goroutines to exit.

0 commit comments

Comments
 (0)