Skip to content

Commit d28d5d7

Browse files
committed
itest: add testSendToRouteFailHTLCTimeout to check SendToRouteV2
1 parent 21112cf commit d28d5d7

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,4 +662,8 @@ var allTestCases = []*lntest.TestCase{
662662
Name: "payment succeeded htlc remote swept",
663663
TestFunc: testPaymentSucceededHTLCRemoteSwept,
664664
},
665+
{
666+
Name: "send to route failed htlc timeout",
667+
TestFunc: testSendToRouteFailHTLCTimeout,
668+
},
665669
}

itest/lnd_payment_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)