@@ -19,6 +19,7 @@ import (
1919 "github.com/lightningnetwork/lnd/lntest/rpc"
2020 "github.com/lightningnetwork/lnd/lntest/wait"
2121 "github.com/lightningnetwork/lnd/lntypes"
22+ "github.com/lightningnetwork/lnd/lnwire"
2223 "github.com/stretchr/testify/require"
2324)
2425
@@ -1211,3 +1212,219 @@ func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
12111212 // Cancel the context, which will disconnect the above interceptor.
12121213 cancelInterceptor ()
12131214}
1215+
1216+ // testSendToRouteFailHTLCTimeout is similar to
1217+ // testPaymentFailedHTLCLocalSwept. The only difference is the `SendPayment` is
1218+ // replaced with `SendToRouteV2`. It checks that when an outgoing HTLC is timed
1219+ // out and claimed onchain via the timeout path, the payment will be marked as
1220+ // failed. This test creates a topology from Alice -> Bob, and let Alice send
1221+ // payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
1222+ // time out. Alice will also be restarted to make sure resumed payments are
1223+ // also marked as failed.
1224+ func testSendToRouteFailHTLCTimeout (ht * lntest.HarnessTest ) {
1225+ success := ht .Run ("fail payment" , func (t * testing.T ) {
1226+ st := ht .Subtest (t )
1227+ runSendToRouteFailHTLCTimeout (st , false )
1228+ })
1229+ if ! success {
1230+ return
1231+ }
1232+
1233+ ht .Run ("fail resumed payment" , func (t * testing.T ) {
1234+ st := ht .Subtest (t )
1235+ runTestPaymentHTLCTimeout (st , true )
1236+ })
1237+ }
1238+
1239+ // runSendToRouteFailHTLCTimeout is the helper function that actually runs the
1240+ // testSendToRouteFailHTLCTimeout.
1241+ func runSendToRouteFailHTLCTimeout (ht * lntest.HarnessTest , restartAlice bool ) {
1242+ // Set the feerate to be 10 sat/vb.
1243+ ht .SetFeeEstimate (2500 )
1244+
1245+ // Open a channel with 100k satoshis between Alice and Bob with Alice
1246+ // being the sole funder of the channel.
1247+ chanAmt := btcutil .Amount (100_000 )
1248+ openChannelParams := lntest.OpenChannelParams {
1249+ Amt : chanAmt ,
1250+ }
1251+
1252+ // Create a two hop network: Alice -> Bob.
1253+ chanPoints , nodes := createSimpleNetwork (ht , nil , 2 , openChannelParams )
1254+ chanPoint := chanPoints [0 ]
1255+ alice , bob := nodes [0 ], nodes [1 ]
1256+
1257+ // We now create two payments, one above dust and the other below dust,
1258+ // and we should see different behavior in terms of when the payment
1259+ // will be marked as failed due to the HTLC timeout.
1260+ //
1261+ // First, create random preimages.
1262+ preimage := ht .RandomPreimage ()
1263+ dustPreimage := ht .RandomPreimage ()
1264+
1265+ // Get the preimage hashes.
1266+ payHash := preimage .Hash ()
1267+ dustPayHash := dustPreimage .Hash ()
1268+
1269+ // Create an hold invoice for Bob which expects a payment of 10k
1270+ // satoshis from Alice.
1271+ const paymentAmt = 20_000
1272+ req := & invoicesrpc.AddHoldInvoiceRequest {
1273+ Value : paymentAmt ,
1274+ Hash : payHash [:],
1275+ // Use a small CLTV value so we can mine fewer blocks.
1276+ CltvExpiry : finalCltvDelta ,
1277+ }
1278+ invoice := bob .RPC .AddHoldInvoice (req )
1279+
1280+ // Create another hold invoice for Bob which expects a payment of 1k
1281+ // satoshis from Alice.
1282+ const dustAmt = 1000
1283+ req = & invoicesrpc.AddHoldInvoiceRequest {
1284+ Value : dustAmt ,
1285+ Hash : dustPayHash [:],
1286+ // Use a small CLTV value so we can mine fewer blocks.
1287+ CltvExpiry : finalCltvDelta ,
1288+ }
1289+ dustInvoice := bob .RPC .AddHoldInvoice (req )
1290+
1291+ // Construct a route to send the non-dust payment.
1292+ go func () {
1293+ // Query the route to send the payment.
1294+ routesReq := & lnrpc.QueryRoutesRequest {
1295+ PubKey : bob .PubKeyStr ,
1296+ Amt : paymentAmt ,
1297+ FinalCltvDelta : finalCltvDelta ,
1298+ }
1299+ routes := alice .RPC .QueryRoutes (routesReq )
1300+ require .Len (ht , routes .Routes , 1 )
1301+
1302+ route := routes .Routes [0 ]
1303+ require .Len (ht , route .Hops , 1 )
1304+
1305+ // Modify the hop to include MPP info.
1306+ route .Hops [0 ].MppRecord = & lnrpc.MPPRecord {
1307+ PaymentAddr : invoice .PaymentAddr ,
1308+ TotalAmtMsat : int64 (
1309+ lnwire .NewMSatFromSatoshis (paymentAmt ),
1310+ ),
1311+ }
1312+
1313+ // Send the payment with the modified value.
1314+ req := & routerrpc.SendToRouteRequest {
1315+ PaymentHash : payHash [:],
1316+ Route : route ,
1317+ }
1318+
1319+ // Send the payment and expect no error.
1320+ attempt := alice .RPC .SendToRouteV2 (req )
1321+ require .Equal (ht , lnrpc .HTLCAttempt_FAILED , attempt .Status )
1322+ }()
1323+
1324+ // Check that the payment is in-flight.
1325+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
1326+
1327+ // Construct a route to send the dust payment.
1328+ go func () {
1329+ // Query the route to send the payment.
1330+ routesReq := & lnrpc.QueryRoutesRequest {
1331+ PubKey : bob .PubKeyStr ,
1332+ Amt : dustAmt ,
1333+ FinalCltvDelta : finalCltvDelta ,
1334+ }
1335+ routes := alice .RPC .QueryRoutes (routesReq )
1336+ require .Len (ht , routes .Routes , 1 )
1337+
1338+ route := routes .Routes [0 ]
1339+ require .Len (ht , route .Hops , 1 )
1340+
1341+ // Modify the hop to include MPP info.
1342+ route .Hops [0 ].MppRecord = & lnrpc.MPPRecord {
1343+ PaymentAddr : dustInvoice .PaymentAddr ,
1344+ TotalAmtMsat : int64 (
1345+ lnwire .NewMSatFromSatoshis (dustAmt ),
1346+ ),
1347+ }
1348+
1349+ // Send the payment with the modified value.
1350+ req := & routerrpc.SendToRouteRequest {
1351+ PaymentHash : dustPayHash [:],
1352+ Route : route ,
1353+ }
1354+
1355+ // Send the payment and expect no error.
1356+ attempt := alice .RPC .SendToRouteV2 (req )
1357+ require .Equal (ht , lnrpc .HTLCAttempt_FAILED , attempt .Status )
1358+ }()
1359+
1360+ // Check that the dust payment is in-flight.
1361+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_IN_FLIGHT )
1362+
1363+ // Bob should have two incoming HTLC.
1364+ ht .AssertIncomingHTLCActive (bob , chanPoint , payHash [:])
1365+ ht .AssertIncomingHTLCActive (bob , chanPoint , dustPayHash [:])
1366+
1367+ // Alice should have two outgoing HTLCs.
1368+ ht .AssertOutgoingHTLCActive (alice , chanPoint , payHash [:])
1369+ ht .AssertOutgoingHTLCActive (alice , chanPoint , dustPayHash [:])
1370+
1371+ // Let Bob go offline.
1372+ ht .Shutdown (bob )
1373+
1374+ // We'll now mine enough blocks to trigger Alice to broadcast her
1375+ // commitment transaction due to the fact that the HTLC is about to
1376+ // timeout. With the default outgoing broadcast delta of zero, this
1377+ // will be the same height as the htlc expiry height.
1378+ numBlocks := padCLTV (
1379+ uint32 (req .CltvExpiry - lncfg .DefaultOutgoingBroadcastDelta ),
1380+ )
1381+ ht .MineEmptyBlocks (int (numBlocks - 1 ))
1382+
1383+ // Restart Alice if requested.
1384+ if restartAlice {
1385+ // Restart Alice to test the resumed payment is canceled.
1386+ ht .RestartNode (alice )
1387+ }
1388+
1389+ // We now subscribe to the payment status.
1390+ payStream := alice .RPC .TrackPaymentV2 (payHash [:])
1391+ dustPayStream := alice .RPC .TrackPaymentV2 (dustPayHash [:])
1392+
1393+ // Mine a block to confirm Alice's closing transaction.
1394+ ht .MineBlocksAndAssertNumTxes (1 , 1 )
1395+
1396+ // Now the channel is closed, we expect different behaviors based on
1397+ // whether the HTLC is a dust. For dust payment, it should be failed
1398+ // now as the HTLC won't go onchain. For non-dust payment, it should
1399+ // still be inflight. It won't be marked as failed unless the outgoing
1400+ // HTLC is resolved onchain.
1401+ //
1402+ // Check that the dust payment is failed in both the stream and DB.
1403+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_FAILED )
1404+ ht .AssertPaymentStatusFromStream (dustPayStream , lnrpc .Payment_FAILED )
1405+
1406+ // Check that the non-dust payment is still in-flight.
1407+ //
1408+ // NOTE: we don't check the payment status from the stream here as
1409+ // there's no new status being sent.
1410+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
1411+
1412+ // We now have two possible cases for the non-dust payment:
1413+ // - Bob stays offline, and Alice will sweep her outgoing HTLC, which
1414+ // makes the payment failed.
1415+ // - Bob comes back online, and claims the HTLC on Alice's commitment
1416+ // via direct preimage spend, hence racing against Alice onchain. If
1417+ // he succeeds, Alice should mark the payment as succeeded.
1418+ //
1419+ // TODO(yy): test the second case once we have the RPC to clean
1420+ // mempool.
1421+
1422+ // Since Alice's force close transaction has been confirmed, she should
1423+ // sweep her outgoing HTLC in next block.
1424+ ht .MineBlocksAndAssertNumTxes (2 , 1 )
1425+
1426+ // We expect the non-dust payment to marked as failed in Alice's
1427+ // database and also from her stream.
1428+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_FAILED )
1429+ ht .AssertPaymentStatusFromStream (payStream , lnrpc .Payment_FAILED )
1430+ }
0 commit comments