From b762d441cfd8d533264edd60a19df4137d24f21e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 17 Dec 2019 10:48:17 +0100 Subject: [PATCH 1/3] lntest: expose configuration of harness node When using the lntest package for itests in external projects, it is necessary to access a harness node's configuration, for example to get its data directory on disk. This commit exports that configuration. --- lntest/harness.go | 28 ++++++++++----------- lntest/node.go | 64 +++++++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/lntest/harness.go b/lntest/harness.go index 52417750..4887ba2b 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -343,7 +343,7 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string, func (n *NetworkHarness) newNode(name string, extraArgs []string, hasSeed bool, password []byte) (*HarnessNode, error) { - node, err := newNode(nodeConfig{ + node, err := newNode(NodeConfig{ Name: name, HasSeed: hasSeed, Password: password, @@ -368,7 +368,7 @@ func (n *NetworkHarness) newNode(name string, extraArgs []string, // If this node is to have a seed, it will need to be unlocked or // initialized via rpc. Delay registering it with the network until it // can be driven via an unlocked rpc connection. - if node.cfg.HasSeed { + if node.Cfg.HasSeed { return node, nil } @@ -431,7 +431,7 @@ func (n *NetworkHarness) EnsureConnected(ctx context.Context, a, b *HarnessNode) req := &lnrpc.ConnectPeerRequest{ Addr: &lnrpc.LightningAddress{ Pubkey: bInfo.IdentityPubkey, - Host: b.cfg.P2PAddr(), + Host: b.Cfg.P2PAddr(), }, } @@ -536,7 +536,7 @@ func (n *NetworkHarness) ConnectNodes(ctx context.Context, a, b *HarnessNode) er req := &lnrpc.ConnectPeerRequest{ Addr: &lnrpc.LightningAddress{ Pubkey: bobInfo.IdentityPubkey, - Host: b.cfg.P2PAddr(), + Host: b.Cfg.P2PAddr(), }, } @@ -618,14 +618,14 @@ func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error, // If the node doesn't have a password set, then we can exit here as we // don't need to unlock it. - if len(node.cfg.Password) == 0 { + if len(node.Cfg.Password) == 0 { return nil } // Otherwise, we'll unlock the wallet, then complete the final steps // for the node initialization process. unlockReq := &lnrpc.UnlockWalletRequest{ - WalletPassword: node.cfg.Password, + WalletPassword: node.Cfg.Password, } if len(chanBackups) != 0 { unlockReq.ChannelBackups = chanBackups[0] @@ -687,13 +687,13 @@ func saveProfilesPage(node *HarnessNode) error { resp, err := http.Get( fmt.Sprintf( "http://localhost:%d/debug/pprof/goroutine?debug=1", - node.cfg.ProfilePort, + node.Cfg.ProfilePort, ), ) if err != nil { return fmt.Errorf("Failed to get profile page "+ "(node_id=%d, name=%s): %v\n", - node.NodeID, node.cfg.Name, err) + node.NodeID, node.Cfg.Name, err) } defer resp.Body.Close() @@ -701,11 +701,11 @@ func saveProfilesPage(node *HarnessNode) error { if err != nil { return fmt.Errorf("Failed to read profile page "+ "(node_id=%d, name=%s): %v\n", - node.NodeID, node.cfg.Name, err) + node.NodeID, node.Cfg.Name, err) } fileName := fmt.Sprintf( - "pprof-%d-%s-%s.log", node.NodeID, node.cfg.Name, + "pprof-%d-%s-%s.log", node.NodeID, node.Cfg.Name, hex.EncodeToString(node.PubKey[:logPubKeyBytes]), ) @@ -713,7 +713,7 @@ func saveProfilesPage(node *HarnessNode) error { if err != nil { return fmt.Errorf("Failed to create file for profile page "+ "(node_id=%d, name=%s): %v\n", - node.NodeID, node.cfg.Name, err) + node.NodeID, node.Cfg.Name, err) } defer logFile.Close() @@ -721,7 +721,7 @@ func saveProfilesPage(node *HarnessNode) error { if err != nil { return fmt.Errorf("Failed to save profile page "+ "(node_id=%d, name=%s): %v\n", - node.NodeID, node.cfg.Name, err) + node.NodeID, node.Cfg.Name, err) } return nil } @@ -1222,7 +1222,7 @@ func (n *NetworkHarness) AssertChannelExists(ctx context.Context, // Logs from lightning node being generated with delay - you should // add time.Sleep() in order to get all logs. func (n *NetworkHarness) DumpLogs(node *HarnessNode) (string, error) { - logFile := fmt.Sprintf("%v/simnet/lnd.log", node.cfg.LogDir) + logFile := fmt.Sprintf("%v/simnet/lnd.log", node.Cfg.LogDir) buf, err := ioutil.ReadFile(logFile) if err != nil { @@ -1319,7 +1319,7 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount, err = wait.NoError(func() error { // Since neutrino doesn't support unconfirmed outputs, skip // this check. - if target.cfg.BackendCfg.Name() == "neutrino" { + if target.Cfg.BackendCfg.Name() == "neutrino" { return nil } diff --git a/lntest/node.go b/lntest/node.go index b6636089..6e65d0ba 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -130,7 +130,7 @@ type BackendConfig interface { Name() string } -type nodeConfig struct { +type NodeConfig struct { Name string BackendCfg BackendConfig NetParams *chaincfg.Params @@ -154,24 +154,24 @@ type nodeConfig struct { ProfilePort int } -func (cfg nodeConfig) P2PAddr() string { +func (cfg NodeConfig) P2PAddr() string { return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.P2PPort)) } -func (cfg nodeConfig) RPCAddr() string { +func (cfg NodeConfig) RPCAddr() string { return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.RPCPort)) } -func (cfg nodeConfig) RESTAddr() string { +func (cfg NodeConfig) RESTAddr() string { return net.JoinHostPort("127.0.0.1", strconv.Itoa(cfg.RESTPort)) } -func (cfg nodeConfig) DBPath() string { +func (cfg NodeConfig) DBPath() string { return filepath.Join(cfg.DataDir, "graph", fmt.Sprintf("%v/channel.db", cfg.NetParams.Name)) } -func (cfg nodeConfig) ChanBackupPath() string { +func (cfg NodeConfig) ChanBackupPath() string { return filepath.Join( cfg.DataDir, "chain", "bitcoin", fmt.Sprintf( @@ -183,7 +183,7 @@ func (cfg nodeConfig) ChanBackupPath() string { // genArgs generates a slice of command line arguments from the lightning node // config struct. -func (cfg nodeConfig) genArgs() []string { +func (cfg NodeConfig) genArgs() []string { var args []string switch cfg.NetParams { @@ -232,7 +232,7 @@ func (cfg nodeConfig) genArgs() []string { // harness. Each HarnessNode instance also fully embeds an RPC client in // order to pragmatically drive the node. type HarnessNode struct { - cfg *nodeConfig + Cfg *NodeConfig // NodeID is a unique identifier for the node within a NetworkHarness. NodeID int @@ -286,7 +286,7 @@ var _ lnrpc.WalletUnlockerClient = (*HarnessNode)(nil) var _ invoicesrpc.InvoicesClient = (*HarnessNode)(nil) // newNode creates a new test lightning node instance from the passed config. -func newNode(cfg nodeConfig) (*HarnessNode, error) { +func newNode(cfg NodeConfig) (*HarnessNode, error) { if cfg.BaseDir == "" { var err error cfg.BaseDir, err = ioutil.TempDir("", "lndtest-node") @@ -310,7 +310,7 @@ func newNode(cfg nodeConfig) (*HarnessNode, error) { numActiveNodesMtx.Unlock() return &HarnessNode{ - cfg: &cfg, + Cfg: &cfg, NodeID: nodeNum, chanWatchRequests: make(chan *chanWatchRequest), openChans: make(map[wire.OutPoint]int), @@ -323,44 +323,44 @@ func newNode(cfg nodeConfig) (*HarnessNode, error) { // DBPath returns the filepath to the channeldb database file for this node. func (hn *HarnessNode) DBPath() string { - return hn.cfg.DBPath() + return hn.Cfg.DBPath() } // Name returns the name of this node set during initialization. func (hn *HarnessNode) Name() string { - return hn.cfg.Name + return hn.Cfg.Name } // TLSCertStr returns the path where the TLS certificate is stored. func (hn *HarnessNode) TLSCertStr() string { - return hn.cfg.TLSCertPath + return hn.Cfg.TLSCertPath } // TLSKeyStr returns the path where the TLS key is stored. func (hn *HarnessNode) TLSKeyStr() string { - return hn.cfg.TLSKeyPath + return hn.Cfg.TLSKeyPath } // ChanBackupPath returns the fielpath to the on-disk channels.backup file for // this node. func (hn *HarnessNode) ChanBackupPath() string { - return hn.cfg.ChanBackupPath() + return hn.Cfg.ChanBackupPath() } // AdminMacPath returns the filepath to the admin.macaroon file for this node. func (hn *HarnessNode) AdminMacPath() string { - return hn.cfg.AdminMacPath + return hn.Cfg.AdminMacPath } // ReadMacPath returns the filepath to the readonly.macaroon file for this node. func (hn *HarnessNode) ReadMacPath() string { - return hn.cfg.ReadMacPath + return hn.Cfg.ReadMacPath } // InvoiceMacPath returns the filepath to the invoice.macaroon file for this // node. func (hn *HarnessNode) InvoiceMacPath() string { - return hn.cfg.InvoiceMacPath + return hn.Cfg.InvoiceMacPath } // Start launches a new process running lnd. Additionally, the PID of the @@ -372,7 +372,7 @@ func (hn *HarnessNode) InvoiceMacPath() string { func (hn *HarnessNode) start(lndError chan<- error) error { hn.quit = make(chan struct{}) - args := hn.cfg.genArgs() + args := hn.Cfg.genArgs() hn.cmd = exec.Command("../../lnd-itest", args...) // Redirect stderr output to buffer @@ -391,14 +391,14 @@ func (hn *HarnessNode) start(lndError chan<- error) error { // log files. if *logOutput { fileName := fmt.Sprintf("output-%d-%s-%s.log", hn.NodeID, - hn.cfg.Name, hex.EncodeToString(hn.PubKey[:logPubKeyBytes])) + hn.Cfg.Name, hex.EncodeToString(hn.PubKey[:logPubKeyBytes])) // If the node's PubKey is not yet initialized, create a temporary // file name. Later, after the PubKey has been initialized, the // file can be moved to its final name with the PubKey included. if bytes.Equal(hn.PubKey[:4], []byte{0, 0, 0, 0}) { fileName = fmt.Sprintf("output-%d-%s-tmp__.log", hn.NodeID, - hn.cfg.Name) + hn.Cfg.Name) // Once the node has done its work, the log file can be renamed. finalizeLogfile = func() { @@ -406,7 +406,7 @@ func (hn *HarnessNode) start(lndError chan<- error) error { hn.logFile.Close() newFileName := fmt.Sprintf("output-%d-%s-%s.log", - hn.NodeID, hn.cfg.Name, + hn.NodeID, hn.Cfg.Name, hex.EncodeToString(hn.PubKey[:logPubKeyBytes])) err := os.Rename(fileName, newFileName) if err != nil { @@ -469,7 +469,7 @@ 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. - useMacaroons := !hn.cfg.HasSeed + useMacaroons := !hn.Cfg.HasSeed conn, err := hn.ConnectRPC(useMacaroons) if err != nil { hn.cmd.Process.Kill() @@ -480,7 +480,7 @@ func (hn *HarnessNode) start(lndError chan<- error) error { // 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 { + if hn.Cfg.HasSeed { hn.WalletUnlockerClient = lnrpc.NewWalletUnlockerClient(conn) return nil } @@ -645,7 +645,7 @@ func (hn *HarnessNode) AddToLog(line string) error { // writePidFile writes the process ID of the running lnd process to a .pid file. func (hn *HarnessNode) writePidFile() error { - filePath := filepath.Join(hn.cfg.BaseDir, fmt.Sprintf("%v.pid", hn.NodeID)) + filePath := filepath.Join(hn.Cfg.BaseDir, fmt.Sprintf("%v.pid", hn.NodeID)) pid, err := os.Create(filePath) if err != nil { @@ -699,7 +699,7 @@ func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) ( // Wait until TLS certificate is created before using it, up to 30 sec. tlsTimeout := time.After(DefaultTimeout) - for !fileExists(hn.cfg.TLSCertPath) { + for !fileExists(hn.Cfg.TLSCertPath) { select { case <-tlsTimeout: return nil, fmt.Errorf("timeout waiting for TLS cert " + @@ -710,7 +710,7 @@ func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) ( opts := []grpc.DialOption{grpc.WithBlock()} tlsCreds, err := credentials.NewClientTLSFromFile( - hn.cfg.TLSCertPath, "", + hn.Cfg.TLSCertPath, "", ) if err != nil { return nil, err @@ -718,14 +718,14 @@ func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) ( opts = append(opts, grpc.WithTransportCredentials(tlsCreds)) if mac == nil { - return grpc.Dial(hn.cfg.RPCAddr(), opts...) + return grpc.Dial(hn.Cfg.RPCAddr(), opts...) } macCred := macaroons.NewMacaroonCredential(mac) opts = append(opts, grpc.WithPerRPCCredentials(macCred)) ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) defer cancel() - return grpc.DialContext(ctx, hn.cfg.RPCAddr(), opts...) + return grpc.DialContext(ctx, hn.Cfg.RPCAddr(), opts...) } // ConnectRPC uses the TLS certificate and admin macaroon files written by the @@ -739,7 +739,7 @@ func (hn *HarnessNode) ConnectRPC(useMacs bool) (*grpc.ClientConn, error) { // If we should use a macaroon, always take the admin macaroon as a // default. - mac, err := hn.ReadMacaroon(hn.cfg.AdminMacPath, DefaultTimeout) + mac, err := hn.ReadMacaroon(hn.Cfg.AdminMacPath, DefaultTimeout) if err != nil { return nil, err } @@ -749,12 +749,12 @@ func (hn *HarnessNode) ConnectRPC(useMacs bool) (*grpc.ClientConn, error) { // SetExtraArgs assigns the ExtraArgs field for the node's configuration. The // changes will take effect on restart. func (hn *HarnessNode) SetExtraArgs(extraArgs []string) { - hn.cfg.ExtraArgs = extraArgs + hn.Cfg.ExtraArgs = extraArgs } // cleanup cleans up all the temporary files created by the node's process. func (hn *HarnessNode) cleanup() error { - return os.RemoveAll(hn.cfg.BaseDir) + return os.RemoveAll(hn.Cfg.BaseDir) } // Stop attempts to stop the active lnd process. From ce711a1de7ef589a94ae9f52869dfe775b38ea96 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 17 Dec 2019 15:01:23 +0100 Subject: [PATCH 2/3] lntest: specify lnd binary Integration tests in external projects might not have the same folder structure as lnd does. Therefore we want to allow the path to the lnd itest binary to be configurable. --- lntest/harness.go | 15 +++++++++++---- lntest/itest/lnd_test.go | 5 ++++- lntest/node.go | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lntest/harness.go b/lntest/harness.go index 4887ba2b..1d69b3b1 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -35,6 +35,10 @@ const DefaultCSV = 4 type NetworkHarness struct { netParams *chaincfg.Params + // lndBinary is the full path to the lnd binary that was specifically + // compiled with all required itest flags. + lndBinary string + // Miner is a reference to a running full node that can be used to create // new blocks on the network. Miner *rpctest.Harness @@ -68,7 +72,9 @@ type NetworkHarness struct { // TODO(roasbeef): add option to use golang's build library to a binary of the // current repo. This will save developers from having to manually `go install` // within the repo each time before changes -func NewNetworkHarness(r *rpctest.Harness, b BackendConfig) (*NetworkHarness, error) { +func NewNetworkHarness(r *rpctest.Harness, b BackendConfig, lndBinary string) ( + *NetworkHarness, error) { + n := NetworkHarness{ activeNodes: make(map[int]*HarnessNode), nodesByPub: make(map[string]*HarnessNode), @@ -79,6 +85,7 @@ func NewNetworkHarness(r *rpctest.Harness, b BackendConfig) (*NetworkHarness, er Miner: r, BackendCfg: b, quit: make(chan struct{}), + lndBinary: lndBinary, } go n.networkWatcher() return &n, nil @@ -361,7 +368,7 @@ func (n *NetworkHarness) newNode(name string, extraArgs []string, n.activeNodes[node.NodeID] = node n.mtx.Unlock() - if err := node.start(n.lndErrorChan); err != nil { + if err := node.start(n.lndBinary, n.lndErrorChan); err != nil { return nil, err } @@ -612,7 +619,7 @@ func (n *NetworkHarness) RestartNode(node *HarnessNode, callback func() error, } } - if err := node.start(n.lndErrorChan); err != nil { + if err := node.start(n.lndBinary, n.lndErrorChan); err != nil { return err } @@ -643,7 +650,7 @@ func (n *NetworkHarness) SuspendNode(node *HarnessNode) (func() error, error) { } restart := func() error { - return node.start(n.lndErrorChan) + return node.start(n.lndBinary, n.lndErrorChan) } return restart, nil diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 8cd889b4..503f143c 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -56,6 +56,7 @@ const ( minerMempoolTimeout = lntest.MinerMempoolTimeout channelOpenTimeout = lntest.ChannelOpenTimeout channelCloseTimeout = lntest.ChannelCloseTimeout + itestLndBinary = "../../lnd-itest" ) // harnessTest wraps a regular testing.T providing enhanced error detection @@ -15237,7 +15238,9 @@ func TestLightningNetworkDaemon(t *testing.T) { // Now we can set up our test harness (LND instance), with the chain // backend we just created. - lndHarness, err = lntest.NewNetworkHarness(miner, chainBackend) + lndHarness, err = lntest.NewNetworkHarness( + miner, chainBackend, itestLndBinary, + ) if err != nil { ht.Fatalf("unable to create lightning network harness: %v", err) } diff --git a/lntest/node.go b/lntest/node.go index 6e65d0ba..adb1b405 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -369,11 +369,11 @@ func (hn *HarnessNode) InvoiceMacPath() string { // // This may not clean up properly if an error is returned, so the caller should // call shutdown() regardless of the return value. -func (hn *HarnessNode) start(lndError chan<- error) error { +func (hn *HarnessNode) start(lndBinary string, lndError chan<- error) error { hn.quit = make(chan struct{}) args := hn.Cfg.genArgs() - hn.cmd = exec.Command("../../lnd-itest", args...) + hn.cmd = exec.Command(lndBinary, args...) // Redirect stderr output to buffer var errb bytes.Buffer From ae1f7348f01dcb038f558e86c903532ad0a72927 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 17 Dec 2019 15:01:36 +0100 Subject: [PATCH 3/3] lntest: fix macaroon paths This change makes sure that all macaroons are stored in the same folder. This makes it possible to use the lntest package in external projects that use loop's lndclient library which currently assumes that the admin macaroon and subserver macaroons are in the same sub folder of lnd's data directory. --- lntest/node.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lntest/node.go b/lntest/node.go index adb1b405..22fad610 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -298,9 +298,13 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { cfg.LogDir = filepath.Join(cfg.BaseDir, "log") cfg.TLSCertPath = filepath.Join(cfg.DataDir, "tls.cert") cfg.TLSKeyPath = filepath.Join(cfg.DataDir, "tls.key") - cfg.AdminMacPath = filepath.Join(cfg.DataDir, "admin.macaroon") - cfg.ReadMacPath = filepath.Join(cfg.DataDir, "readonly.macaroon") - cfg.InvoiceMacPath = filepath.Join(cfg.DataDir, "invoice.macaroon") + + networkDir := filepath.Join( + cfg.DataDir, "chain", "bitcoin", cfg.NetParams.Name, + ) + cfg.AdminMacPath = filepath.Join(networkDir, "admin.macaroon") + cfg.ReadMacPath = filepath.Join(networkDir, "readonly.macaroon") + cfg.InvoiceMacPath = filepath.Join(networkDir, "invoice.macaroon") cfg.P2PPort, cfg.RPCPort, cfg.RESTPort, cfg.ProfilePort = generateListeningPorts()