diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index a52bb9204..d93dd04e7 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -678,6 +678,8 @@ func (w *WalletKit) BumpFee(ctx context.Context, return nil, err } + log.Debugf("Attempting to CPFP outpoint %s", op) + // Since we're unable to perform a bump through RBF, we'll assume the // user is attempting to bump an unconfirmed transaction's fee rate by // sweeping an output within it under control of the wallet with a diff --git a/lntest/harness.go b/lntest/harness.go index eb75d0fe3..89a4141ba 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -1128,7 +1128,8 @@ func (n *NetworkHarness) CloseChannel(ctx context.Context, // within the network. closeResp, err := closeRespStream.Recv() if err != nil { - errChan <- err + errChan <- fmt.Errorf("unable to recv() from close "+ + "stream: %v", err) return } pendingClose, ok := closeResp.Update.(*lnrpc.CloseStatusUpdate_ClosePending) @@ -1140,11 +1141,13 @@ func (n *NetworkHarness) CloseChannel(ctx context.Context, closeTxid, err := chainhash.NewHash(pendingClose.ClosePending.Txid) if err != nil { - errChan <- err + errChan <- fmt.Errorf("unable to decode closeTxid: "+ + "%v", err) return } if err := n.WaitForTxBroadcast(ctx, *closeTxid); err != nil { - errChan <- err + errChan <- fmt.Errorf("error while waiting for "+ + "broadcast tx: %v", err) return } fin <- closeTxid @@ -1153,9 +1156,6 @@ func (n *NetworkHarness) CloseChannel(ctx context.Context, // Wait until either the deadline for the context expires, an error // occurs, or the channel close update is received. select { - case <-ctx.Done(): - return nil, nil, fmt.Errorf("timeout reached before channel close " + - "initiated") case err := <-errChan: return nil, nil, err case closeTxid := <-fin: diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 687483ab7..ee51f47b5 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -155,6 +155,39 @@ func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) t.Fatalf("tx was not included in block") } +func assertWalletUnspent(t *harnessTest, node *lntest.HarnessNode, out *lnrpc.OutPoint) { + t.t.Helper() + + err := wait.NoError(func() error { + ctxt, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + unspent, err := node.ListUnspent(ctxt, &lnrpc.ListUnspentRequest{}) + if err != nil { + return err + } + + err = errors.New("tx with wanted txhash never found") + for _, utxo := range unspent.Utxos { + if !bytes.Equal(utxo.Outpoint.TxidBytes, out.TxidBytes) { + continue + } + + err = errors.New("wanted output is not a wallet utxo") + if utxo.Outpoint.OutputIndex != out.OutputIndex { + continue + } + + return nil + } + + return err + }, defaultTimeout) + if err != nil { + t.Fatalf("outpoint %s not unspent by %s's wallet: %v", out, + node.Name(), err) + } +} + func rpcPointToWirePoint(t *harnessTest, chanPoint *lnrpc.ChannelPoint) wire.OutPoint { txid, err := lnd.GetChanPointFundingTxid(chanPoint) if err != nil { @@ -9055,6 +9088,29 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, davePreSweepBalance := daveBalResp.ConfirmedBalance + // Wait until the backup has been accepted by the watchtower before + // shutting down Dave. + err = wait.NoError(func() error { + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + bkpStats, err := dave.WatchtowerClient.Stats(ctxt, &wtclientrpc.StatsRequest{}) + if err != nil { + return err + + } + if bkpStats == nil { + return errors.New("no active backup sessions") + } + if bkpStats.NumBackups == 0 { + return errors.New("no backups accepted") + } + + return nil + }, defaultTimeout) + if err != nil { + t.Fatalf("unable to verify backup task completed: %v", err) + } + // Shutdown Dave to simulate going offline for an extended period of // time. Once he's not watching, Carol will try to breach the channel. restart, err := net.SuspendNode(dave) @@ -9083,9 +9139,6 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, t.Fatalf("db copy failed: %v", carolChan.NumUpdates) } - // TODO(conner): add hook for backup completion - time.Sleep(3 * time.Second) - // Now force Carol to execute a *force* channel closure by unilaterally // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon @@ -13367,7 +13420,7 @@ func testChannelBackupUpdates(net *lntest.NetworkHarness, t *harnessTest) { numChans := 2 chanAmt := btcutil.Amount(1000000) for i := 0; i < numChans; i++ { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, carol, lntest.OpenChannelParams{ @@ -13501,7 +13554,7 @@ func testExportChannelBackup(net *lntest.NetworkHarness, t *harnessTest) { numChans := 2 chanAmt := btcutil.Amount(1000000) for i := 0; i < numChans; i++ { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, carol, lntest.OpenChannelParams{ diff --git a/lntest/itest/onchain.go b/lntest/itest/onchain.go index 22b50e3c5..7cc4df11e 100644 --- a/lntest/itest/onchain.go +++ b/lntest/itest/onchain.go @@ -86,12 +86,15 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("bob's output was not found within the transaction") } - // We'll attempt to bump the fee of this transaction by performing a - // CPFP from Alice's point of view. + // Wait until bob has seen the tx and considers it as owned. op := &lnrpc.OutPoint{ TxidBytes: txid[:], OutputIndex: uint32(bobOutputIdx), } + assertWalletUnspent(t, net.Bob, op) + + // We'll attempt to bump the fee of this transaction by performing a + // CPFP from Alice's point of view. bumpFeeReq := &walletrpc.BumpFeeRequest{ Outpoint: op, SatPerByte: uint32(sweep.DefaultMaxFeeRate.FeePerKVByte() / 2000), diff --git a/lntest/node.go b/lntest/node.go index 0f864f2a8..83d23b4ca 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -625,9 +625,10 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { // Launch the watcher that will hook into graph related topology change // from the PoV of this node. hn.wg.Add(1) - go hn.lightningNetworkWatcher() + subscribed := make(chan error) + go hn.lightningNetworkWatcher(subscribed) - return nil + return <-subscribed } // FetchNodeInfo queries an unlocked node to retrieve its public key. @@ -688,28 +689,26 @@ func (hn *HarnessNode) writePidFile() error { func (hn *HarnessNode) ReadMacaroon(macPath string, timeout time.Duration) ( *macaroon.Macaroon, error) { - // Wait until macaroon file is created before using it. - macTimeout := time.After(timeout) - for !fileExists(macPath) { - select { - case <-macTimeout: - return nil, fmt.Errorf("timeout waiting for macaroon "+ - "file %s to be created after %d seconds", - macPath, timeout/time.Second) - case <-time.After(100 * time.Millisecond): + // Wait until macaroon file is created and has valid content before + // using it. + var mac *macaroon.Macaroon + err := wait.NoError(func() error { + macBytes, err := ioutil.ReadFile(macPath) + if err != nil { + return fmt.Errorf("error reading macaroon file: %v", err) } - } - // Now that we know the file exists, read it and return the macaroon. - macBytes, err := ioutil.ReadFile(macPath) - if err != nil { - return nil, err - } - mac := &macaroon.Macaroon{} - if err = mac.UnmarshalBinary(macBytes); err != nil { - return nil, err - } - return mac, nil + newMac := &macaroon.Macaroon{} + if err = newMac.UnmarshalBinary(macBytes); err != nil { + return fmt.Errorf("error unmarshalling macaroon "+ + "file: %v", err) + } + mac = newMac + + return nil + }, timeout) + + return mac, err } // ConnectRPCWithMacaroon uses the TLS certificate and given macaroon to @@ -717,34 +716,34 @@ func (hn *HarnessNode) ReadMacaroon(macPath string, timeout time.Duration) ( func (hn *HarnessNode) ConnectRPCWithMacaroon(mac *macaroon.Macaroon) ( *grpc.ClientConn, error) { - // Wait until TLS certificate is created before using it, up to 30 sec. - tlsTimeout := time.After(DefaultTimeout) - for !fileExists(hn.Cfg.TLSCertPath) { - select { - case <-tlsTimeout: - return nil, fmt.Errorf("timeout waiting for TLS cert " + - "file to be created") - case <-time.After(100 * time.Millisecond): - } + // Wait until TLS certificate is created and has valid content before + // using it, up to 30 sec. + var tlsCreds credentials.TransportCredentials + err := wait.NoError(func() error { + var err error + tlsCreds, err = credentials.NewClientTLSFromFile( + hn.Cfg.TLSCertPath, "", + ) + return err + }, DefaultTimeout) + if err != nil { + return nil, fmt.Errorf("error reading TLS cert: %v", err) } - opts := []grpc.DialOption{grpc.WithBlock()} - tlsCreds, err := credentials.NewClientTLSFromFile( - hn.Cfg.TLSCertPath, "", - ) - if err != nil { - return nil, err + opts := []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithTransportCredentials(tlsCreds), } - opts = append(opts, grpc.WithTransportCredentials(tlsCreds)) + + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() if mac == nil { - return grpc.Dial(hn.Cfg.RPCAddr(), opts...) + return grpc.DialContext(ctx, 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...) } @@ -873,7 +872,7 @@ func getChanPointFundingTxid(chanPoint *lnrpc.ChannelPoint) ([]byte, error) { // closed or opened within the network. In order to dispatch these // notifications, the GraphTopologySubscription client exposed as part of the // gRPC interface is used. -func (hn *HarnessNode) lightningNetworkWatcher() { +func (hn *HarnessNode) lightningNetworkWatcher(subscribed chan error) { defer hn.wg.Done() graphUpdates := make(chan *lnrpc.GraphTopologyUpdate) @@ -883,16 +882,16 @@ func (hn *HarnessNode) lightningNetworkWatcher() { req := &lnrpc.GraphTopologySubscription{} ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() topologyClient, err := hn.SubscribeChannelGraph(ctx, req) if err != nil { - // We panic here in case of an error as failure to - // create the topology client will cause all subsequent - // tests to fail. - panic(fmt.Errorf("unable to create topology "+ - "client: %v", err)) + msg := fmt.Sprintf("%s(%d): unable to create topology "+ + "client: %v (%s)", hn.Name(), hn.NodeID, err, + time.Now().String()) + subscribed <- fmt.Errorf(msg) + return } - - defer cancelFunc() + close(subscribed) for { update, err := topologyClient.Recv() @@ -1157,14 +1156,3 @@ func (hn *HarnessNode) WaitForBalance(expectedBalance btcutil.Amount, confirmed return nil } - -// fileExists reports whether the named file or directory exists. -// This function is taken from https://github.com/btcsuite/btcd -func fileExists(name string) bool { - if _, err := os.Stat(name); err != nil { - if os.IsNotExist(err) { - return false - } - } - return true -}