|
| 1 | +package itest |
| 2 | + |
| 3 | +import ( |
| 4 | + "time" |
| 5 | + |
| 6 | + "github.com/btcsuite/btcd/btcutil" |
| 7 | + "github.com/lightningnetwork/lnd/chainreg" |
| 8 | + "github.com/lightningnetwork/lnd/lnrpc" |
| 9 | + "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" |
| 10 | + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" |
| 11 | + "github.com/lightningnetwork/lnd/lntest" |
| 12 | + "github.com/lightningnetwork/lnd/lntest/node" |
| 13 | + "github.com/lightningnetwork/lnd/lntypes" |
| 14 | + "github.com/lightningnetwork/lnd/routing/route" |
| 15 | + "github.com/stretchr/testify/require" |
| 16 | +) |
| 17 | + |
| 18 | +// testInvoiceAcceptorBasic tests the basic functionality of the invoice |
| 19 | +// acceptor RPC server. |
| 20 | +func testInvoiceAcceptorBasic(ht *lntest.HarnessTest) { |
| 21 | + ts := newAcceptorTestScenario(ht) |
| 22 | + |
| 23 | + alice, bob, carol := ts.alice, ts.bob, ts.carol |
| 24 | + |
| 25 | + // Open and wait for channels. |
| 26 | + const chanAmt = btcutil.Amount(300000) |
| 27 | + p := lntest.OpenChannelParams{Amt: chanAmt} |
| 28 | + reqs := []*lntest.OpenChannelRequest{ |
| 29 | + {Local: alice, Remote: bob, Param: p}, |
| 30 | + {Local: bob, Remote: carol, Param: p}, |
| 31 | + } |
| 32 | + resp := ht.OpenMultiChannelsAsync(reqs) |
| 33 | + cpAB, cpBC := resp[0], resp[1] |
| 34 | + |
| 35 | + // Make sure Alice is aware of channel Bob=>Carol. |
| 36 | + ht.AssertTopologyChannelOpen(alice, cpBC) |
| 37 | + |
| 38 | + // Initiate Carol's invoice acceptor. |
| 39 | + invoiceAcceptor, cancelInvoiceAcceptor := carol.RPC.InvoiceAcceptor() |
| 40 | + |
| 41 | + // Prepare the test cases. |
| 42 | + testCases := ts.prepareTestCases() |
| 43 | + |
| 44 | + for tcIdx, tc := range testCases { |
| 45 | + ht.Logf("Running test case: %d", tcIdx) |
| 46 | + |
| 47 | + // Initiate a payment from Alice to Carol in a separate |
| 48 | + // goroutine. We use a separate goroutine to avoid blocking the |
| 49 | + // main goroutine where we will make use of the invoice |
| 50 | + // acceptor. |
| 51 | + sendPaymentDone := make(chan struct{}) |
| 52 | + go func() { |
| 53 | + // Signal that all the payments have been sent. |
| 54 | + defer close(sendPaymentDone) |
| 55 | + |
| 56 | + _ = ts.sendPayment(tc) |
| 57 | + }() |
| 58 | + |
| 59 | + acceptorRequest := ht.ReceiveInvoiceAcceptor(invoiceAcceptor) |
| 60 | + |
| 61 | + // Sanity check the acceptor request. |
| 62 | + require.EqualValues(ht, tc.invoiceAmountMsat, |
| 63 | + acceptorRequest.Invoice.ValueMsat) |
| 64 | + require.EqualValues(ht, tc.sendAmountMsat, |
| 65 | + acceptorRequest.ExitHtlcAmt) |
| 66 | + |
| 67 | + preimage, err := lntypes.MakePreimage(tc.invoice.RPreimage) |
| 68 | + require.NoError(ht, err, "failed to parse invoice preimage") |
| 69 | + |
| 70 | + // For all other packets we resolve according to the test case. |
| 71 | + err = invoiceAcceptor.Send( |
| 72 | + &invoicesrpc.InvoiceAcceptorResponse{ |
| 73 | + Preimage: preimage[:], |
| 74 | + SkipAmountCheck: tc.skipAmtCheck, |
| 75 | + }, |
| 76 | + ) |
| 77 | + require.NoError(ht, err, "failed to send request") |
| 78 | + |
| 79 | + ht.Log("Waiting for payment send to complete") |
| 80 | + select { |
| 81 | + case <-sendPaymentDone: |
| 82 | + ht.Log("Payment send attempt complete") |
| 83 | + case <-time.After(defaultTimeout): |
| 84 | + require.Fail(ht, "timeout waiting for payment send") |
| 85 | + } |
| 86 | + |
| 87 | + ht.Log("Ensure invoice status is settled") |
| 88 | + require.Eventually(ht, func() bool { |
| 89 | + updatedInvoice := carol.RPC.LookupInvoice( |
| 90 | + tc.invoice.RHash, |
| 91 | + ) |
| 92 | + |
| 93 | + return updatedInvoice.State == tc.finalInvoiceState |
| 94 | + }, defaultTimeout, 1*time.Second) |
| 95 | + } |
| 96 | + |
| 97 | + cancelInvoiceAcceptor() |
| 98 | + |
| 99 | + // Finally, close channels. |
| 100 | + ht.CloseChannel(alice, cpAB) |
| 101 | + ht.CloseChannel(bob, cpBC) |
| 102 | +} |
| 103 | + |
| 104 | +// acceptorTestCase is a helper struct to hold test case data. |
| 105 | +type acceptorTestCase struct { |
| 106 | + // invoiceAmountMsat is the amount of the invoice. |
| 107 | + invoiceAmountMsat int64 |
| 108 | + |
| 109 | + // sendAmountMsat is the amount that will be sent in the payment. |
| 110 | + sendAmountMsat int64 |
| 111 | + |
| 112 | + // skipAmtCheck is a flag that indicates whether the amount checks |
| 113 | + // should be skipped during the invoice settlement process. |
| 114 | + skipAmtCheck bool |
| 115 | + |
| 116 | + // finalInvoiceState is the expected eventual final state of the |
| 117 | + // invoice. |
| 118 | + finalInvoiceState lnrpc.Invoice_InvoiceState |
| 119 | + |
| 120 | + // payAddr is the payment address of the invoice. |
| 121 | + payAddr []byte |
| 122 | + |
| 123 | + // invoice is the invoice that will be paid. |
| 124 | + invoice *lnrpc.Invoice |
| 125 | +} |
| 126 | + |
| 127 | +// acceptorTestScenario is a helper struct to hold the test context and provides |
| 128 | +// helpful functionality. |
| 129 | +type acceptorTestScenario struct { |
| 130 | + ht *lntest.HarnessTest |
| 131 | + alice, bob, carol *node.HarnessNode |
| 132 | +} |
| 133 | + |
| 134 | +// newAcceptorTestScenario initializes a new test scenario with three nodes and |
| 135 | +// connects them to have the following topology, |
| 136 | +// |
| 137 | +// Alice --> Bob --> Carol |
| 138 | +// |
| 139 | +// Among them, Alice and Bob are standby nodes and Carol is a new node. |
| 140 | +func newAcceptorTestScenario( |
| 141 | + ht *lntest.HarnessTest) *acceptorTestScenario { |
| 142 | + |
| 143 | + alice, bob := ht.Alice, ht.Bob |
| 144 | + carol := ht.NewNode("carol", nil) |
| 145 | + |
| 146 | + ht.EnsureConnected(alice, bob) |
| 147 | + ht.EnsureConnected(bob, carol) |
| 148 | + |
| 149 | + return &acceptorTestScenario{ |
| 150 | + ht: ht, |
| 151 | + alice: alice, |
| 152 | + bob: bob, |
| 153 | + carol: carol, |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +// prepareTestCases prepares test cases. |
| 158 | +func (c *acceptorTestScenario) prepareTestCases() []*acceptorTestCase { |
| 159 | + cases := []*acceptorTestCase{ |
| 160 | + // Send a payment with amount less than the invoice amount. |
| 161 | + // Amount checking is skipped during the invoice settlement |
| 162 | + // process. The sent payment should eventually result in the |
| 163 | + // invoice being settled. |
| 164 | + { |
| 165 | + invoiceAmountMsat: 9000, |
| 166 | + sendAmountMsat: 1000, |
| 167 | + skipAmtCheck: true, |
| 168 | + finalInvoiceState: lnrpc.Invoice_SETTLED, |
| 169 | + }, |
| 170 | + } |
| 171 | + |
| 172 | + for _, t := range cases { |
| 173 | + inv := &lnrpc.Invoice{ValueMsat: t.invoiceAmountMsat} |
| 174 | + addResponse := c.carol.RPC.AddInvoice(inv) |
| 175 | + invoice := c.carol.RPC.LookupInvoice(addResponse.RHash) |
| 176 | + |
| 177 | + // We'll need to also decode the returned invoice so we can |
| 178 | + // grab the payment address which is now required for ALL |
| 179 | + // payments. |
| 180 | + payReq := c.carol.RPC.DecodePayReq(invoice.PaymentRequest) |
| 181 | + |
| 182 | + t.invoice = invoice |
| 183 | + t.payAddr = payReq.PaymentAddr |
| 184 | + } |
| 185 | + |
| 186 | + return cases |
| 187 | +} |
| 188 | + |
| 189 | +// buildRoute is a helper function to build a route with given hops. |
| 190 | +func (c *acceptorTestScenario) buildRoute(amtMsat int64, |
| 191 | + hops []*node.HarnessNode, payAddr []byte) *lnrpc.Route { |
| 192 | + |
| 193 | + rpcHops := make([][]byte, 0, len(hops)) |
| 194 | + for _, hop := range hops { |
| 195 | + k := hop.PubKeyStr |
| 196 | + pubkey, err := route.NewVertexFromStr(k) |
| 197 | + require.NoErrorf(c.ht, err, "error parsing %v: %v", k, err) |
| 198 | + rpcHops = append(rpcHops, pubkey[:]) |
| 199 | + } |
| 200 | + |
| 201 | + req := &routerrpc.BuildRouteRequest{ |
| 202 | + AmtMsat: amtMsat, |
| 203 | + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, |
| 204 | + HopPubkeys: rpcHops, |
| 205 | + PaymentAddr: payAddr, |
| 206 | + } |
| 207 | + |
| 208 | + routeResp := c.alice.RPC.BuildRoute(req) |
| 209 | + |
| 210 | + return routeResp.Route |
| 211 | +} |
| 212 | + |
| 213 | +// sendPaymentAndAssertAction sends a payment from alice to carol. |
| 214 | +func (c *acceptorTestScenario) sendPayment( |
| 215 | + tc *acceptorTestCase) *lnrpc.HTLCAttempt { |
| 216 | + |
| 217 | + // Build a route from alice to carol. |
| 218 | + aliceBobCarolRoute := c.buildRoute( |
| 219 | + tc.sendAmountMsat, []*node.HarnessNode{c.bob, c.carol}, |
| 220 | + tc.payAddr, |
| 221 | + ) |
| 222 | + |
| 223 | + // Send the payment. |
| 224 | + sendReq := &routerrpc.SendToRouteRequest{ |
| 225 | + PaymentHash: tc.invoice.RHash, |
| 226 | + Route: aliceBobCarolRoute, |
| 227 | + } |
| 228 | + |
| 229 | + return c.alice.RPC.SendToRouteV2(sendReq) |
| 230 | +} |
0 commit comments