diff --git a/lntemp/harness.go b/lntemp/harness.go index 35699a6f3..700ab68cb 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "fmt" + "sync" "testing" "github.com/btcsuite/btcd/btcutil" @@ -12,6 +13,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntemp/rpc" "github.com/lightningnetwork/lnd/lntest" @@ -542,11 +544,12 @@ type OpenChannelParams struct { CommitmentType lnrpc.CommitmentType } -// openChannel attempts to open a channel between srcNode and destNode with the -// passed channel funding parameters. Once the `OpenChannel` is called, it will -// consume the first event it receives from the open channel client and asserts -// it's a channel pending event. -func (h *HarnessTest) openChannel(srcNode, destNode *node.HarnessNode, +// OpenChannelAssertPending attempts to open a channel between srcNode and +// destNode with the passed channel funding parameters. Once the `OpenChannel` +// is called, it will consume the first event it receives from the open channel +// client and asserts it's a channel pending event. +func (h *HarnessTest) OpenChannelAssertPending( + srcNode, destNode *node.HarnessNode, p OpenChannelParams) rpc.OpenChanClient { // Specify the minimal confirmations of the UTXOs used for channel @@ -601,7 +604,7 @@ func (h *HarnessTest) OpenChannel(alice, bob *node.HarnessNode, h.WaitForBlockchainSync(alice) h.WaitForBlockchainSync(bob) - chanOpenUpdate := h.openChannel(alice, bob, p) + chanOpenUpdate := h.OpenChannelAssertPending(alice, bob, p) // Mine 6 blocks, then wait for Alice's node to notify us that the // channel has been opened. The funding transaction should be found @@ -649,7 +652,9 @@ func (h *HarnessTest) closeChannel(hn *node.HarnessNode, cp *lnrpc.ChannelPoint, // Consume the "channel close" update in order to wait for the closing // transaction to be broadcast, then wait for the closing tx to be seen // within the network. - event := h.ReceiveCloseChannelUpdate(stream) + event, err := h.ReceiveCloseChannelUpdate(stream) + require.NoError(h, err) + pendingClose, ok := event.Update.(*lnrpc.CloseStatusUpdate_ClosePending) require.Truef(h, ok, "expected channel close update, instead got %v", pendingClose) @@ -681,6 +686,26 @@ func (h *HarnessTest) CloseChannel(hn *node.HarnessNode, return h.assertChannelClosed(hn, cp, false, stream) } +// CloseChannelAssertErr closes the given channel and asserts an error +// returned. +func (h *HarnessTest) CloseChannelAssertErr(hn *node.HarnessNode, + cp *lnrpc.ChannelPoint, force bool) { + + // Calls the rpc to close the channel. + closeReq := &lnrpc.CloseChannelRequest{ + ChannelPoint: cp, + Force: force, + } + stream := hn.RPC.CloseChannel(closeReq) + + // Consume the "channel close" update in order to wait for the closing + // transaction to be broadcast, then wait for the closing tx to be seen + // within the network. + _, err := h.ReceiveCloseChannelUpdate(stream) + require.Errorf(h, err, "%s: expect close channel to return an error", + hn.Name()) +} + // IsNeutrinoBackend returns a bool indicating whether the node is using a // neutrino as its backend. This is useful when we want to skip certain tests // which cannot be done with a neutrino backend. @@ -758,3 +783,84 @@ func (h *HarnessTest) fundCoins(amt btcutil.Amount, target *node.HarnessNode, func (h *HarnessTest) FundCoins(amt btcutil.Amount, hn *node.HarnessNode) { h.fundCoins(amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true) } + +// CompletePaymentRequests sends payments from a node to complete all payment +// requests. This function does not return until all payments successfully +// complete without errors. +func (h *HarnessTest) CompletePaymentRequests(hn *node.HarnessNode, + paymentRequests []string) { + + var wg sync.WaitGroup + + // send sends a payment and asserts if it doesn't succeeded. + send := func(payReq string) { + defer wg.Done() + + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: defaultPaymentTimeout, + FeeLimitMsat: noFeeLimitMsat, + } + stream := hn.RPC.SendPayment(req) + h.AssertPaymentStatusFromStream(stream, lnrpc.Payment_SUCCEEDED) + } + + // Launch all payments simultaneously. + for _, payReq := range paymentRequests { + payReqCopy := payReq + wg.Add(1) + go send(payReqCopy) + } + + // Wait for all payments to report success. + wg.Wait() +} + +// CompletePaymentRequestsNoWait sends payments from a node to complete all +// payment requests without waiting for the results. Instead, it checks the +// number of updates in the specified channel has increased. +func (h *HarnessTest) CompletePaymentRequestsNoWait(hn *node.HarnessNode, + paymentRequests []string, chanPoint *lnrpc.ChannelPoint) { + + // We start by getting the current state of the client's channels. This + // is needed to ensure the payments actually have been committed before + // we return. + oldResp := h.GetChannelByChanPoint(hn, chanPoint) + + // send sends a payment and asserts if it doesn't succeeded. + send := func(payReq string) { + req := &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: defaultPaymentTimeout, + FeeLimitMsat: noFeeLimitMsat, + } + hn.RPC.SendPayment(req) + } + + // Launch all payments simultaneously. + for _, payReq := range paymentRequests { + payReqCopy := payReq + go send(payReqCopy) + } + + // We are not waiting for feedback in the form of a response, but we + // should still wait long enough for the server to receive and handle + // the send before cancelling the request. We wait for the number of + // updates to one of our channels has increased before we return. + err := wait.NoError(func() error { + newResp := h.GetChannelByChanPoint(hn, chanPoint) + + // If this channel has an increased number of updates, we + // assume the payments are committed, and we can return. + if newResp.NumUpdates > oldResp.NumUpdates { + return nil + } + + // Otherwise return an error as the NumUpdates are not + // increased. + return fmt.Errorf("%s: channel:%v not updated after sending "+ + "payments, old updates: %v, new updates: %v", hn.Name(), + chanPoint, oldResp.NumUpdates, newResp.NumUpdates) + }, DefaultTimeout) + require.NoError(h, err, "timeout while checking for channel updates") +} diff --git a/lntemp/harness_assertion.go b/lntemp/harness_assertion.go index e714b22ce..381074160 100644 --- a/lntemp/harness_assertion.go +++ b/lntemp/harness_assertion.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntemp/rpc" @@ -300,10 +301,10 @@ func (h *HarnessTest) findChannel(hn *node.HarnessNode, return nil, fmt.Errorf("channel not found using %s", chanPoint) } -// ReceiveCloseChannelUpdate waits until a message is received on the subscribe -// channel close stream or the timeout is reached. +// ReceiveCloseChannelUpdate waits until a message or an error is received on +// the subscribe channel close stream or the timeout is reached. func (h *HarnessTest) ReceiveCloseChannelUpdate( - stream rpc.CloseChanClient) *lnrpc.CloseStatusUpdate { + stream rpc.CloseChanClient) (*lnrpc.CloseStatusUpdate, error) { chanMsg := make(chan *lnrpc.CloseStatusUpdate) errChan := make(chan error) @@ -322,16 +323,15 @@ func (h *HarnessTest) ReceiveCloseChannelUpdate( case <-time.After(DefaultTimeout): require.Fail(h, "timeout", "timeout waiting for close channel "+ "update sent") + return nil, nil case err := <-errChan: - require.Failf(h, "close channel stream", - "received err from close channel stream: %v", err) + return nil, fmt.Errorf("received err from close channel "+ + "stream: %v", err) case updateMsg := <-chanMsg: - return updateMsg + return updateMsg, nil } - - return nil } type WaitingCloseChannel *lnrpc.PendingChannelsResponse_WaitingCloseChannel @@ -382,7 +382,8 @@ func (h HarnessTest) WaitForChannelCloseEvent( stream rpc.CloseChanClient) *chainhash.Hash { // Consume one event. - event := h.ReceiveCloseChannelUpdate(stream) + event, err := h.ReceiveCloseChannelUpdate(stream) + require.NoError(h, err) resp, ok := event.Update.(*lnrpc.CloseStatusUpdate_ChanClose) require.Truef(h, ok, "expected channel open update, instead got %v", @@ -687,3 +688,137 @@ func (h *HarnessTest) GetChannelCommitType(hn *node.HarnessNode, return c.CommitmentType } + +// AssertNumPendingOpenChannels asserts that a given node have the expected +// number of pending open channels. +func (h *HarnessTest) AssertNumPendingOpenChannels(hn *node.HarnessNode, + expected int) []*lnrpc.PendingChannelsResponse_PendingOpenChannel { + + var channels []*lnrpc.PendingChannelsResponse_PendingOpenChannel + + oldNum := hn.State.OpenChannel.Pending + + err := wait.NoError(func() error { + resp := hn.RPC.PendingChannels() + channels = resp.PendingOpenChannels + total := len(channels) + + numChans := total - oldNum + + if numChans != expected { + return errNumNotMatched(hn.Name(), + "pending open channels", expected, + numChans, total, oldNum) + } + + return nil + }, DefaultTimeout) + + require.NoError(h, err, "num of pending open channels not match") + + return channels +} + +// AssertNodesNumPendingOpenChannels asserts that both of the nodes have the +// expected number of pending open channels. +func (h *HarnessTest) AssertNodesNumPendingOpenChannels(a, b *node.HarnessNode, + expected int) { + + h.AssertNumPendingOpenChannels(a, expected) + h.AssertNumPendingOpenChannels(b, expected) +} + +// AssertPaymentStatusFromStream takes a client stream and asserts the payment +// is in desired status before default timeout. The payment found is returned +// once succeeded. +func (h *HarnessTest) AssertPaymentStatusFromStream(stream rpc.PaymentClient, + status lnrpc.Payment_PaymentStatus) *lnrpc.Payment { + + return h.assertPaymentStatusWithTimeout(stream, status, DefaultTimeout) +} + +// assertPaymentStatusWithTimeout takes a client stream and asserts the payment +// is in desired status before the specified timeout. The payment found is +// returned once succeeded. +func (h *HarnessTest) assertPaymentStatusWithTimeout(stream rpc.PaymentClient, + status lnrpc.Payment_PaymentStatus, + timeout time.Duration) *lnrpc.Payment { + + var target *lnrpc.Payment + err := wait.NoError(func() error { + // Consume one message. This will raise an error if the message + // is not received within DefaultTimeout. + payment := h.ReceivePaymentUpdate(stream) + + // Return if the desired payment state is reached. + if payment.Status == status { + target = payment + + return nil + } + + // Return the err so that it can be used for debugging when + // timeout is reached. + return fmt.Errorf("payment status, got %v, want %v", + payment.Status, status) + }, timeout) + + require.NoError(h, err, "timeout while waiting payment") + + return target +} + +// ReceivePaymentUpdate waits until a message is received on the payment client +// stream or the timeout is reached. +func (h *HarnessTest) ReceivePaymentUpdate( + stream rpc.PaymentClient) *lnrpc.Payment { + + chanMsg := make(chan *lnrpc.Payment, 1) + errChan := make(chan error, 1) + go func() { + // Consume one message. This will block until the message is + // received. + resp, err := stream.Recv() + if err != nil { + errChan <- err + + return + } + chanMsg <- resp + }() + + select { + case <-time.After(DefaultTimeout): + require.Fail(h, "timeout", "timeout waiting for payment update") + + case err := <-errChan: + require.Failf(h, "payment stream", + "received err from payment stream: %v", err) + + case updateMsg := <-chanMsg: + return updateMsg + } + + return nil +} + +// AssertInvoiceSettled asserts a given invoice specified by its payment +// address is settled. +func (h *HarnessTest) AssertInvoiceSettled(hn *node.HarnessNode, addr []byte) { + msg := &invoicesrpc.LookupInvoiceMsg{ + InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{ + PaymentAddr: addr, + }, + } + + err := wait.NoError(func() error { + invoice := hn.RPC.LookupInvoiceV2(msg) + if invoice.State == lnrpc.Invoice_SETTLED { + return nil + } + + return fmt.Errorf("%s: invoice with payment address %x not "+ + "settled", hn.Name(), addr) + }, DefaultTimeout) + require.NoError(h, err, "timeout waiting for invoice settled state") +} diff --git a/lntemp/rpc/invoices.go b/lntemp/rpc/invoices.go index 0e6d6fe10..e2cc10286 100644 --- a/lntemp/rpc/invoices.go +++ b/lntemp/rpc/invoices.go @@ -1,5 +1,26 @@ package rpc +import ( + "context" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" +) + // ===================== // InvoiceClient related RPCs. // ===================== + +// LookupInvoiceV2 queries the node's invoices using the invoice client's +// LookupInvoiceV2. +func (h *HarnessRPC) LookupInvoiceV2( + req *invoicesrpc.LookupInvoiceMsg) *lnrpc.Invoice { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.Invoice.LookupInvoiceV2(ctxt, req) + h.NoError(err, "LookupInvoiceV2") + + return resp +} diff --git a/lntemp/rpc/lnd.go b/lntemp/rpc/lnd.go index 3df276d26..b25e92ee1 100644 --- a/lntemp/rpc/lnd.go +++ b/lntemp/rpc/lnd.go @@ -238,3 +238,27 @@ func (h *HarnessRPC) FundingStateStepAssertErr(m *lnrpc.FundingTransitionMsg) { _, err := h.LN.FundingStateStep(ctxt, m) require.Error(h, err, "expected an error from FundingStateStep") } + +// AddInvoice adds a invoice for the given node and asserts. +func (h *HarnessRPC) AddInvoice(req *lnrpc.Invoice) *lnrpc.AddInvoiceResponse { + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + invoice, err := h.LN.AddInvoice(ctxt, req) + h.NoError(err, "AddInvoice") + + return invoice +} + +// AbandonChannel makes a RPC call to AbandonChannel and asserts. +func (h *HarnessRPC) AbandonChannel( + req *lnrpc.AbandonChannelRequest) *lnrpc.AbandonChannelResponse { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + resp, err := h.LN.AbandonChannel(ctxt, req) + h.NoError(err, "AbandonChannel") + + return resp +} diff --git a/lntemp/rpc/router.go b/lntemp/rpc/router.go index d1ad9323d..77c2d8f01 100644 --- a/lntemp/rpc/router.go +++ b/lntemp/rpc/router.go @@ -23,3 +23,19 @@ func (h *HarnessRPC) UpdateChanStatus( return resp } + +type PaymentClient routerrpc.Router_SendPaymentV2Client + +// SendPayment sends a payment using the given node and payment request. It +// also asserts the payment being sent successfully. +func (h *HarnessRPC) SendPayment( + req *routerrpc.SendPaymentRequest) PaymentClient { + + // SendPayment needs to have the context alive for the entire test case + // as the router relies on the context to propagate HTLCs. Thus we use + // runCtx here instead of a timeout context. + stream, err := h.Router.SendPaymentV2(h.runCtx, req) + h.NoError(err, "SendPaymentV2") + + return stream +} diff --git a/lntemp/utils.go b/lntemp/utils.go index b06ebe059..e8f9171e5 100644 --- a/lntemp/utils.go +++ b/lntemp/utils.go @@ -3,6 +3,7 @@ package lntemp import ( "fmt" "io" + "math" "os" "github.com/lightningnetwork/lnd/lntest" @@ -14,6 +15,14 @@ const ( // TODO(yy): delete. DefaultTimeout = lntest.DefaultTimeout + + // noFeeLimitMsat is used to specify we will put no requirements on fee + // charged when choosing a route path. + noFeeLimitMsat = math.MaxInt64 + + // defaultPaymentTimeout specifies the default timeout in seconds when + // sending a payment. + defaultPaymentTimeout = 60 ) // CopyFile copies the file src to dest.