github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_multi-hop_test.go (about) 1 package itest 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/decred/dcrd/chaincfg/chainhash" 9 "github.com/decred/dcrd/dcrutil/v4" 10 "github.com/decred/dcrd/wire" 11 "github.com/decred/dcrlnd/lnrpc" 12 "github.com/decred/dcrlnd/lnrpc/invoicesrpc" 13 "github.com/decred/dcrlnd/lntest" 14 "github.com/decred/dcrlnd/lntypes" 15 "github.com/stretchr/testify/require" 16 "matheusd.com/testctx" 17 ) 18 19 func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { 20 21 type testCase struct { 22 name string 23 test func(net *lntest.NetworkHarness, t *harnessTest, alice, 24 bob *lntest.HarnessNode, c lnrpc.CommitmentType) 25 } 26 27 subTests := []testCase{ 28 { 29 // bob: outgoing our commit timeout 30 // carol: incoming their commit watch and see timeout 31 name: "local force close immediate expiry", 32 test: testMultiHopHtlcLocalTimeout, 33 }, 34 { 35 // bob: outgoing watch and see, they sweep on chain 36 // carol: incoming our commit, know preimage 37 name: "receiver chain claim", 38 test: testMultiHopReceiverChainClaim, 39 }, 40 { 41 // bob: outgoing our commit watch and see timeout 42 // carol: incoming their commit watch and see timeout 43 name: "local force close on-chain htlc timeout", 44 test: testMultiHopLocalForceCloseOnChainHtlcTimeout, 45 }, 46 { 47 // bob: outgoing their commit watch and see timeout 48 // carol: incoming our commit watch and see timeout 49 name: "remote force close on-chain htlc timeout", 50 test: testMultiHopRemoteForceCloseOnChainHtlcTimeout, 51 }, 52 { 53 // bob: outgoing our commit watch and see, they sweep on chain 54 // bob: incoming our commit watch and learn preimage 55 // carol: incoming their commit know preimage 56 name: "local chain claim", 57 test: testMultiHopHtlcLocalChainClaim, 58 }, 59 { 60 // bob: outgoing their commit watch and see, they sweep on chain 61 // bob: incoming their commit watch and learn preimage 62 // carol: incoming our commit know preimage 63 name: "remote chain claim", 64 test: testMultiHopHtlcRemoteChainClaim, 65 }, 66 { 67 // bob: outgoing and incoming, sweep all on chain 68 name: "local htlc aggregation", 69 test: testMultiHopHtlcAggregation, 70 }, 71 } 72 73 commitTypes := []lnrpc.CommitmentType{ 74 lnrpc.CommitmentType_LEGACY, 75 lnrpc.CommitmentType_ANCHORS, 76 lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE, 77 } 78 79 for _, commitType := range commitTypes { 80 commitType := commitType 81 testName := fmt.Sprintf("committype=%v", commitType.String()) 82 83 success := t.t.Run(testName, func(t *testing.T) { 84 ht := newHarnessTest(t, net) 85 86 args := nodeArgsForCommitType(commitType) 87 alice := net.NewNode(t, "Alice", args) 88 defer shutdownAndAssert(net, ht, alice) 89 90 bob := net.NewNode(t, "Bob", args) 91 defer shutdownAndAssert(net, ht, bob) 92 93 net.ConnectNodes(t, alice, bob) 94 95 for _, subTest := range subTests { 96 subTest := subTest 97 98 logLine := fmt.Sprintf( 99 "---- multi-hop htlc subtest "+ 100 "%s/%s ----\n", 101 testName, subTest.name, 102 ) 103 alice.AddToLog(logLine) 104 bob.AddToLog(logLine) 105 106 success := ht.t.Run(subTest.name, func(t *testing.T) { 107 ht := newHarnessTest(t, net) 108 109 // Start each test with the default 110 // static fee estimate. 111 net.SetFeeEstimate(10000) 112 113 subTest.test(net, ht, alice, bob, commitType) 114 assertCleanStateAliceBob(ht, alice, bob, ht.lndHarness) 115 }) 116 if !success { 117 return 118 } 119 } 120 }) 121 if !success { 122 return 123 } 124 } 125 } 126 127 // waitForInvoiceAccepted waits until the specified invoice moved to the 128 // accepted state by the node. 129 func waitForInvoiceAccepted(t *harnessTest, node *lntest.HarnessNode, 130 payHash lntypes.Hash) { 131 132 ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) 133 defer cancel() 134 invoiceUpdates, err := node.SubscribeSingleInvoice(ctx, 135 &invoicesrpc.SubscribeSingleInvoiceRequest{ 136 RHash: payHash[:], 137 }, 138 ) 139 if err != nil { 140 t.Fatalf("subscribe single invoice: %v", err) 141 } 142 143 for { 144 update, err := invoiceUpdates.Recv() 145 if err != nil { 146 t.Fatalf("invoice update err: %v", err) 147 } 148 if update.State == lnrpc.Invoice_ACCEPTED { 149 break 150 } 151 } 152 } 153 154 // checkPaymentStatus asserts that the given node list a payment with the given 155 // preimage has the expected status. 156 func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage, 157 status lnrpc.Payment_PaymentStatus) error { 158 159 ctxb := context.Background() 160 ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) 161 defer cancel() 162 163 req := &lnrpc.ListPaymentsRequest{ 164 IncludeIncomplete: true, 165 } 166 paymentsResp, err := node.ListPayments(ctxt, req) 167 if err != nil { 168 return fmt.Errorf("error when obtaining Alice payments: %v", 169 err) 170 } 171 172 payHash := preimage.Hash() 173 var found bool 174 for _, p := range paymentsResp.Payments { 175 if p.PaymentHash != payHash.String() { 176 continue 177 } 178 179 found = true 180 if p.Status != status { 181 return fmt.Errorf("expected payment status "+ 182 "%v, got %v", status, p.Status) 183 } 184 185 switch status { 186 187 // If this expected status is SUCCEEDED, we expect the final preimage. 188 case lnrpc.Payment_SUCCEEDED: 189 if p.PaymentPreimage != preimage.String() { 190 return fmt.Errorf("preimage doesn't match: %v vs %v", 191 p.PaymentPreimage, preimage.String()) 192 } 193 194 // Otherwise we expect an all-zero preimage. 195 default: 196 if p.PaymentPreimage != (lntypes.Preimage{}).String() { 197 return fmt.Errorf("expected zero preimage, got %v", 198 p.PaymentPreimage) 199 } 200 } 201 202 } 203 204 if !found { 205 return fmt.Errorf("payment with payment hash %v not found "+ 206 "in response", payHash) 207 } 208 209 return nil 210 } 211 212 func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, 213 alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) ( 214 *lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) { 215 216 net.EnsureConnected(t.t, alice, bob) 217 218 // Make sure there are enough utxos for anchoring. 219 for i := 0; i < 2; i++ { 220 net.SendCoins(t.t, dcrutil.AtomsPerCoin, alice) 221 net.SendCoins(t.t, dcrutil.AtomsPerCoin, bob) 222 } 223 224 // We'll start the test by creating a channel between Alice and Bob, 225 // which will act as the first leg for out multi-hop HTLC. 226 const chanAmt = 1000000 227 var aliceFundingShim *lnrpc.FundingShim 228 var thawHeight uint32 229 if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { 230 _, minerHeight, err := net.Miner.Node.GetBestBlock(testctx.New(t)) 231 require.NoError(t.t, err) 232 thawHeight = uint32(minerHeight + 144) 233 aliceFundingShim, _, _ = deriveFundingShim( 234 net, t, alice, bob, chanAmt, thawHeight, true, 235 ) 236 } 237 aliceChanPoint := openChannelAndAssert( 238 t, net, alice, bob, 239 lntest.OpenChannelParams{ 240 Amt: chanAmt, 241 CommitmentType: c, 242 FundingShim: aliceFundingShim, 243 }, 244 ) 245 246 chanp2str := func(chanp *lnrpc.ChannelPoint) string { 247 txid, _ := lnrpc.GetChanPointFundingTxid(chanp) 248 return fmt.Sprintf("%s:%d", txid, 249 chanp.OutputIndex) 250 } 251 252 err := alice.WaitForNetworkChannelOpen(aliceChanPoint) 253 if err != nil { 254 t.Fatalf("alice didn't report channel %v: %v", chanp2str(aliceChanPoint), err) 255 } 256 257 err = bob.WaitForNetworkChannelOpen(aliceChanPoint) 258 if err != nil { 259 t.Fatalf("bob didn't report channel %v: %v", chanp2str(aliceChanPoint), err) 260 } 261 262 // Next, we'll create a new node "carol" and have Bob connect to her. If 263 // the carolHodl flag is set, we'll make carol always hold onto the 264 // HTLC, this way it'll force Bob to go to chain to resolve the HTLC. 265 carolFlags := nodeArgsForCommitType(c) 266 if carolHodl { 267 carolFlags = append(carolFlags, "--hodl.exit-settle") 268 } 269 carol := net.NewNode(t.t, "Carol", carolFlags) 270 271 net.ConnectNodes(t.t, bob, carol) 272 273 // Make sure Carol has enough utxos for anchoring. Because the anchor by 274 // itself often doesn't meet the dust limit, a utxo from the wallet 275 // needs to be attached as an additional input. This can still lead to a 276 // positively-yielding transaction. 277 for i := 0; i < 2; i++ { 278 net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol) 279 } 280 281 // We'll then create a channel from Bob to Carol. After this channel is 282 // open, our topology looks like: A -> B -> C. 283 var bobFundingShim *lnrpc.FundingShim 284 if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { 285 bobFundingShim, _, _ = deriveFundingShim( 286 net, t, bob, carol, chanAmt, thawHeight, true, 287 ) 288 } 289 bobChanPoint := openChannelAndAssert( 290 t, net, bob, carol, 291 lntest.OpenChannelParams{ 292 Amt: chanAmt, 293 CommitmentType: c, 294 FundingShim: bobFundingShim, 295 }, 296 ) 297 err = bob.WaitForNetworkChannelOpen(bobChanPoint) 298 if err != nil { 299 t.Fatalf("bob didn't report channel %v: %v", chanp2str(bobChanPoint), err) 300 } 301 err = carol.WaitForNetworkChannelOpen(bobChanPoint) 302 if err != nil { 303 t.Fatalf("carol didn't report channel %v: %v", chanp2str(bobChanPoint), err) 304 } 305 err = alice.WaitForNetworkChannelOpen(bobChanPoint) 306 if err != nil { 307 t.Fatalf("alice didn't report channel %v: %v", chanp2str(bobChanPoint), err) 308 } 309 310 return aliceChanPoint, bobChanPoint, carol 311 } 312 313 // assertAllTxesSpendFrom asserts that all txes in the list spend from the given 314 // tx. 315 func assertAllTxesSpendFrom(t *harnessTest, txes []*wire.MsgTx, 316 prevTxid chainhash.Hash) { 317 318 for _, tx := range txes { 319 if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid { 320 t.Fatalf("tx %v did not spend from %v", 321 tx.TxHash(), prevTxid) 322 } 323 } 324 }