diff --git a/lntest/harness.go b/lntest/harness.go index 50376eb7..045fd478 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 @@ -343,7 +350,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, @@ -361,14 +368,14 @@ 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 } // 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 +438,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 +543,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(), }, } @@ -612,20 +619,20 @@ 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 } // 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] @@ -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 @@ -687,13 +694,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 +708,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 +720,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 +728,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 } @@ -1227,7 +1234,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 { @@ -1324,7 +1331,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/itest/lnd_test.go b/lntest/itest/lnd_test.go index 38b43430..5ddf28ab 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -58,6 +58,7 @@ const ( minerMempoolTimeout = lntest.MinerMempoolTimeout channelOpenTimeout = lntest.ChannelOpenTimeout channelCloseTimeout = lntest.ChannelCloseTimeout + itestLndBinary = "../../lnd-itest" ) // harnessTest wraps a regular testing.T providing enhanced error detection @@ -15298,7 +15299,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 cbe8e381..3928bbca 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 @@ -156,24 +156,24 @@ type nodeConfig struct { AcceptKeySend bool } -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( @@ -185,7 +185,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 { @@ -238,7 +238,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 @@ -292,7 +292,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") @@ -304,9 +304,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() @@ -321,7 +325,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), @@ -334,44 +338,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 @@ -380,11 +384,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...) + args := hn.Cfg.genArgs() + hn.cmd = exec.Command(lndBinary, args...) // Redirect stderr output to buffer var errb bytes.Buffer @@ -402,14 +406,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() { @@ -417,7 +421,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 { @@ -480,7 +484,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() @@ -491,7 +495,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 } @@ -656,7 +660,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 { @@ -710,7 +714,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 " + @@ -721,7 +725,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 @@ -729,14 +733,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 @@ -750,7 +754,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 } @@ -760,12 +764,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.