diff --git a/lntest/node.go b/lntest/node.go index c41897e2..5c54a3e6 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -98,6 +98,8 @@ type nodeConfig struct { ReadMacPath string InvoiceMacPath string + HasSeed bool + P2PPort int RPCPort int RESTPort int @@ -136,7 +138,6 @@ func (cfg nodeConfig) genArgs() []string { encodedCert := hex.EncodeToString(cfg.RPCConfig.Certificates) args = append(args, "--bitcoin.active") args = append(args, "--nobootstrap") - args = append(args, "--noencryptwallet") args = append(args, "--debuglevel=debug") args = append(args, "--bitcoin.defaultchanconfs=1") args = append(args, "--bitcoin.defaultremotedelay=4") @@ -159,6 +160,10 @@ func (cfg nodeConfig) genArgs() []string { args = append(args, fmt.Sprintf("--externalip=%s", cfg.P2PAddr())) args = append(args, fmt.Sprintf("--trickledelay=%v", trickleDelay)) + if !cfg.HasSeed { + args = append(args, "--noencryptwallet") + } + if cfg.ExtraArgs != nil { args = append(args, cfg.ExtraArgs...) } @@ -195,10 +200,13 @@ type HarnessNode struct { wg sync.WaitGroup lnrpc.LightningClient + + lnrpc.WalletUnlockerClient } // Assert *HarnessNode implements the lnrpc.LightningClient interface. var _ lnrpc.LightningClient = (*HarnessNode)(nil) +var _ lnrpc.WalletUnlockerClient = (*HarnessNode)(nil) // newNode creates a new test lightning node instance from the passed config. func newNode(cfg nodeConfig) (*HarnessNode, error) { @@ -301,13 +309,78 @@ func (hn *HarnessNode) start(lndError chan<- error) error { // Since Stop uses the LightningClient to stop the node, if we fail to get a // connected client, we have to kill the process. - conn, err := hn.connectRPC() + useMacaroons := !hn.cfg.HasSeed + conn, err := hn.ConnectRPC(useMacaroons) if err != nil { hn.cmd.Process.Kill() return err } + + // If the node was created with a seed, we will need to perform an + // additional step to unlock the wallet. The connection returned will + // only use the TLS certs, and can only perform operations necessary to + // unlock the daemon. + if hn.cfg.HasSeed { + hn.WalletUnlockerClient = lnrpc.NewWalletUnlockerClient(conn) + return nil + } + + return hn.initLightningClient(conn) +} + +// Init initializes a harness node by passing the init request via rpc. After +// the request is submitted, this method will block until an +// macaroon-authenticated rpc connection can be established to the harness node. +// Once established, the new connection is used to initialize the +// LightningClient and subscribes the HarnessNode to topology changes. +func (hn *HarnessNode) Init(ctx context.Context, + initReq *lnrpc.InitWalletRequest) error { + + timeout := time.Duration(time.Second * 15) + ctxt, _ := context.WithTimeout(ctx, timeout) + _, err := hn.InitWallet(ctxt, initReq) + if err != nil { + return err + } + + // Wait for the wallet to finish unlocking, such that we can connect to + // it via a macaroon-authenticated rpc connection. + var conn *grpc.ClientConn + if err = WaitPredicate(func() bool { + conn, err = hn.ConnectRPC(true) + return err == nil + }, 5*time.Second); err != nil { + return err + } + + return hn.initLightningClient(conn) +} + +// initLightningClient constructs the grpc LightningClient from the given client +// connection and subscribes the harness node to graph topology updates. +func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { + // Construct the LightningClient that will allow us to use the + // HarnessNode directly for normal rpc operations. hn.LightningClient = lnrpc.NewLightningClient(conn) + // Set the harness node's pubkey to what the node claims in GetInfo. + err := hn.FetchNodeInfo() + if err != nil { + return err + } + + // Launch the watcher that will hook into graph related topology change + // from the PoV of this node. + hn.wg.Add(1) + go hn.lightningNetworkWatcher() + + return nil +} + +// FetchNodeInfo queries an unlocked node to retrieve its public key. This +// method also spawns a lightning network watcher for this node, which watches +// for topology changes. +func (hn *HarnessNode) FetchNodeInfo() error { // Obtain the lnid of this node for quick identification purposes. ctxb := context.Background() info, err := hn.GetInfo(ctxb, &lnrpc.GetInfoRequest{}) @@ -323,11 +396,6 @@ func (hn *HarnessNode) start(lndError chan<- error) error { } copy(hn.PubKey[:], pubkey) - // Launch the watcher that will hook into graph related topology change - // from the PoV of this node. - hn.wg.Add(1) - go hn.lightningNetworkWatcher() - return nil } @@ -365,24 +433,45 @@ func (hn *HarnessNode) writePidFile() error { // connectRPC uses the TLS certificate and admin macaroon files written by the // lnd node to create a gRPC client connection. -func (hn *HarnessNode) connectRPC() (*grpc.ClientConn, error) { +func (hn *HarnessNode) ConnectRPC(useMacs bool) (*grpc.ClientConn, error) { // Wait until TLS certificate and admin macaroon are created before // using them, up to 20 sec. tlsTimeout := time.After(30 * time.Second) - for !fileExists(hn.cfg.TLSCertPath) || !fileExists(hn.cfg.AdminMacPath) { + for !fileExists(hn.cfg.TLSCertPath) { select { case <-tlsTimeout: - return nil, fmt.Errorf("timeout waiting for TLS cert file " + - "and admin macaroon file to be created after " + - "20 seconds") + return nil, fmt.Errorf("timeout waiting for TLS cert " + + "file to be created after 30 seconds") case <-time.After(100 * time.Millisecond): } } + opts := []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithTimeout(time.Second * 20), + } + tlsCreds, err := credentials.NewClientTLSFromFile(hn.cfg.TLSCertPath, "") if err != nil { return nil, err } + + opts = append(opts, grpc.WithTransportCredentials(tlsCreds)) + + if !useMacs { + return grpc.Dial(hn.cfg.RPCAddr(), opts...) + } + + macTimeout := time.After(30 * time.Second) + for !fileExists(hn.cfg.AdminMacPath) { + select { + case <-macTimeout: + return nil, fmt.Errorf("timeout waiting for admin " + + "macaroon file to be created after 30 seconds") + case <-time.After(100 * time.Millisecond): + } + } + macBytes, err := ioutil.ReadFile(hn.cfg.AdminMacPath) if err != nil { return nil, err @@ -391,12 +480,10 @@ func (hn *HarnessNode) connectRPC() (*grpc.ClientConn, error) { if err = mac.UnmarshalBinary(macBytes); err != nil { return nil, err } - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(tlsCreds), - grpc.WithPerRPCCredentials(macaroons.NewMacaroonCredential(mac)), - grpc.WithBlock(), - grpc.WithTimeout(time.Second * 20), - } + + macCred := macaroons.NewMacaroonCredential(mac) + opts = append(opts, grpc.WithPerRPCCredentials(macCred)) + return grpc.Dial(hn.cfg.RPCAddr(), opts...) } @@ -441,6 +528,7 @@ func (hn *HarnessNode) stop() error { hn.quit = nil hn.processExit = nil hn.LightningClient = nil + hn.WalletUnlockerClient = nil return nil }