@@ -1229,3 +1229,166 @@ func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
12291229 ht .AssertNumWaitingClose (hn , 0 )
12301230 }
12311231}
1232+
1233+ // testMPPToMultipleBlindedPaths tests that a two-shard MPP payment can be sent
1234+ // over a multiple blinded paths. The following network is created where Dave
1235+ // is the recipient and Alice the sender. Dave will create an invoice containing
1236+ // two blinded paths: one with Bob at the intro node and one with Carol as the
1237+ // intro node. Channel liquidity will be set up in such a way that Alice will be
1238+ // forced to send one shared via the Bob->Dave route and one over the
1239+ // Carol->Dave route.
1240+ //
1241+ // --- Bob ---
1242+ // / \
1243+ // Alice Dave
1244+ // \ /
1245+ // --- Carol ---
1246+ func testMPPToMultipleBlindedPaths (ht * lntest.HarnessTest ) {
1247+ alice , bob := ht .Alice , ht .Bob
1248+
1249+ // Create a four-node context consisting of Alice, Bob and three new
1250+ // nodes.
1251+ dave := ht .NewNode ("dave" , []string {
1252+ "--routing.blinding.min-num-real-hops=1" ,
1253+ "--routing.blinding.num-hops=1" ,
1254+ })
1255+ carol := ht .NewNode ("carol" , nil )
1256+
1257+ // Connect nodes to ensure propagation of channels.
1258+ ht .EnsureConnected (alice , carol )
1259+ ht .EnsureConnected (alice , bob )
1260+ ht .EnsureConnected (carol , dave )
1261+ ht .EnsureConnected (bob , dave )
1262+
1263+ // Fund the new nodes.
1264+ ht .FundCoinsUnconfirmed (btcutil .SatoshiPerBitcoin , carol )
1265+ ht .FundCoinsUnconfirmed (btcutil .SatoshiPerBitcoin , dave )
1266+ ht .MineBlocksAndAssertNumTxes (1 , 2 )
1267+
1268+ const paymentAmt = btcutil .Amount (300000 )
1269+
1270+ nodes := []* node.HarnessNode {alice , bob , carol , dave }
1271+
1272+ reqs := []* lntest.OpenChannelRequest {
1273+ {
1274+ Local : alice ,
1275+ Remote : bob ,
1276+ Param : lntest.OpenChannelParams {
1277+ Amt : paymentAmt * 2 / 3 ,
1278+ },
1279+ },
1280+ {
1281+ Local : alice ,
1282+ Remote : carol ,
1283+ Param : lntest.OpenChannelParams {
1284+ Amt : paymentAmt * 2 / 3 ,
1285+ },
1286+ },
1287+ {
1288+ Local : bob ,
1289+ Remote : dave ,
1290+ Param : lntest.OpenChannelParams {Amt : paymentAmt * 2 },
1291+ },
1292+ {
1293+ Local : carol ,
1294+ Remote : dave ,
1295+ Param : lntest.OpenChannelParams {Amt : paymentAmt * 2 },
1296+ },
1297+ }
1298+
1299+ channelPoints := ht .OpenMultiChannelsAsync (reqs )
1300+
1301+ // Make sure every node has heard every channel.
1302+ for _ , hn := range nodes {
1303+ for _ , cp := range channelPoints {
1304+ ht .AssertTopologyChannelOpen (hn , cp )
1305+ }
1306+
1307+ // Each node should have exactly 5 edges.
1308+ ht .AssertNumEdges (hn , len (channelPoints ), false )
1309+ }
1310+
1311+ // Ok now make a payment that must be split to succeed.
1312+
1313+ // Make Dave create an invoice for Alice to pay
1314+ invoice := & lnrpc.Invoice {
1315+ Memo : "test" ,
1316+ Value : int64 (paymentAmt ),
1317+ Blind : true ,
1318+ }
1319+ invoiceResp := dave .RPC .AddInvoice (invoice )
1320+
1321+ // Assert that two blinded paths are included in the invoice.
1322+ payReq := dave .RPC .DecodePayReq (invoiceResp .PaymentRequest )
1323+ require .Len (ht , payReq .BlindedPaths , 2 )
1324+
1325+ sendReq := & routerrpc.SendPaymentRequest {
1326+ PaymentRequest : invoiceResp .PaymentRequest ,
1327+ MaxParts : 10 ,
1328+ TimeoutSeconds : 60 ,
1329+ FeeLimitMsat : noFeeLimitMsat ,
1330+ }
1331+ payment := ht .SendPaymentAssertSettled (alice , sendReq )
1332+
1333+ preimageBytes , err := hex .DecodeString (payment .PaymentPreimage )
1334+ require .NoError (ht , err )
1335+
1336+ preimage , err := lntypes .MakePreimage (preimageBytes )
1337+ require .NoError (ht , err )
1338+
1339+ hash , err := lntypes .MakeHash (invoiceResp .RHash )
1340+ require .NoError (ht , err )
1341+
1342+ // Make sure we got the preimage.
1343+ require .True (ht , preimage .Matches (hash ), "preimage doesn't match" )
1344+
1345+ // Check that Alice split the payment in at least two shards. Because
1346+ // the hand-off of the htlc to the link is asynchronous (via a mailbox),
1347+ // there is some non-determinism in the process. Depending on whether
1348+ // the new pathfinding round is started before or after the htlc is
1349+ // locked into the channel, different sharding may occur. Therefore we
1350+ // can only check if the number of shards isn't below the theoretical
1351+ // minimum.
1352+ succeeded := 0
1353+ for _ , htlc := range payment .Htlcs {
1354+ if htlc .Status == lnrpc .HTLCAttempt_SUCCEEDED {
1355+ succeeded ++
1356+ }
1357+ }
1358+
1359+ const minExpectedShards = 2
1360+ require .GreaterOrEqual (ht , succeeded , minExpectedShards ,
1361+ "expected shards not reached" )
1362+
1363+ // Make sure Dave show the invoice as settled for the full amount.
1364+ inv := dave .RPC .LookupInvoice (invoiceResp .RHash )
1365+
1366+ require .EqualValues (ht , paymentAmt , inv .AmtPaidSat ,
1367+ "incorrect payment amt" )
1368+
1369+ require .Equal (ht , lnrpc .Invoice_SETTLED , inv .State ,
1370+ "Invoice not settled" )
1371+
1372+ settled := 0
1373+ for _ , htlc := range inv .Htlcs {
1374+ if htlc .State == lnrpc .InvoiceHTLCState_SETTLED {
1375+ settled ++
1376+ }
1377+ }
1378+ require .Equal (ht , succeeded , settled , "num of HTLCs wrong" )
1379+
1380+ // Close all channels without mining the closing transactions.
1381+ ht .CloseChannelAssertPending (alice , channelPoints [0 ], false )
1382+ ht .CloseChannelAssertPending (alice , channelPoints [1 ], false )
1383+ ht .CloseChannelAssertPending (bob , channelPoints [2 ], false )
1384+ ht .CloseChannelAssertPending (carol , channelPoints [3 ], false )
1385+
1386+ // Now mine a block to include all the closing transactions. (first
1387+ // iteration: no blinded paths)
1388+ ht .MineBlocksAndAssertNumTxes (1 , 4 )
1389+
1390+ // Assert that the channels are closed.
1391+ for _ , hn := range nodes {
1392+ ht .AssertNumWaitingClose (hn , 0 )
1393+ }
1394+ }
0 commit comments