@@ -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