@@ -878,3 +878,176 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
878878 ht .CloseChannel (testCase .carol , testCase .channels [2 ])
879879 testCase .cancel ()
880880}
881+
882+ // testMPPToSingleBlindedPath tests that a two-shard MPP payment can be sent
883+ // over a single blinded path.
884+ // The following graph is created where Dave is the destination node, and he
885+ // will choose Carol as the introduction node. The channel capacities are set in
886+ // such a way that Alice will have to split the payment to dave over both the
887+ // A->B->C-D and A->E->C->D routes.
888+ //
889+ // ---- Bob ---
890+ // / \
891+ // Alice Carol --- Dave
892+ // \ /
893+ // ---- Eve ---
894+ func testMPPToSingleBlindedPath (ht * lntest.HarnessTest ) {
895+ // Create a five-node context consisting of Alice, Bob and three new
896+ // nodes.
897+ alice , bob := ht .Alice , ht .Bob
898+
899+ // Restrict Dave so that he only ever chooses the Carol->Dave path for
900+ // a blinded route.
901+ dave := ht .NewNode ("dave" , []string {
902+ "--invoices.blinding.min-num-real-hops=1" ,
903+ "--invoices.blinding.num-hops=1" ,
904+ })
905+ carol := ht .NewNode ("carol" , nil )
906+ eve := ht .NewNode ("eve" , nil )
907+
908+ // Connect nodes to ensure propagation of channels.
909+ ht .EnsureConnected (alice , bob )
910+ ht .EnsureConnected (alice , eve )
911+ ht .EnsureConnected (carol , bob )
912+ ht .EnsureConnected (carol , eve )
913+ ht .EnsureConnected (carol , dave )
914+
915+ // Send coins to the nodes and mine 1 blocks to confirm them.
916+ for i := 0 ; i < 2 ; i ++ {
917+ ht .FundCoinsUnconfirmed (btcutil .SatoshiPerBitcoin , carol )
918+ ht .FundCoinsUnconfirmed (btcutil .SatoshiPerBitcoin , dave )
919+ ht .FundCoinsUnconfirmed (btcutil .SatoshiPerBitcoin , eve )
920+ ht .MineBlocksAndAssertNumTxes (1 , 3 )
921+ }
922+
923+ const paymentAmt = btcutil .Amount (300000 )
924+
925+ nodes := []* node.HarnessNode {alice , bob , carol , dave , eve }
926+ reqs := []* lntest.OpenChannelRequest {
927+ {
928+ Local : alice ,
929+ Remote : bob ,
930+ Param : lntest.OpenChannelParams {
931+ Amt : paymentAmt * 2 / 3 ,
932+ },
933+ },
934+ {
935+ Local : alice ,
936+ Remote : eve ,
937+ Param : lntest.OpenChannelParams {
938+ Amt : paymentAmt * 2 / 3 ,
939+ },
940+ },
941+ {
942+ Local : bob ,
943+ Remote : carol ,
944+ Param : lntest.OpenChannelParams {
945+ Amt : paymentAmt * 2 ,
946+ },
947+ },
948+ {
949+ Local : eve ,
950+ Remote : carol ,
951+ Param : lntest.OpenChannelParams {
952+ Amt : paymentAmt * 2 ,
953+ },
954+ },
955+ {
956+ Local : carol ,
957+ Remote : dave ,
958+ Param : lntest.OpenChannelParams {
959+ Amt : paymentAmt * 2 ,
960+ },
961+ },
962+ }
963+
964+ channelPoints := ht .OpenMultiChannelsAsync (reqs )
965+
966+ // Make sure every node has heard about every channel.
967+ for _ , hn := range nodes {
968+ for _ , cp := range channelPoints {
969+ ht .AssertTopologyChannelOpen (hn , cp )
970+ }
971+
972+ // Each node should have exactly 5 edges.
973+ ht .AssertNumEdges (hn , len (channelPoints ), false )
974+ }
975+
976+ // Make Dave create an invoice with a blinded path for Alice to pay.
977+ invoice := & lnrpc.Invoice {
978+ Memo : "test" ,
979+ Value : int64 (paymentAmt ),
980+ Blind : true ,
981+ }
982+ invoiceResp := dave .RPC .AddInvoice (invoice )
983+
984+ sendReq := & routerrpc.SendPaymentRequest {
985+ PaymentRequest : invoiceResp .PaymentRequest ,
986+ MaxParts : 10 ,
987+ TimeoutSeconds : 60 ,
988+ FeeLimitMsat : noFeeLimitMsat ,
989+ }
990+ payment := ht .SendPaymentAssertSettled (alice , sendReq )
991+
992+ preimageBytes , err := hex .DecodeString (payment .PaymentPreimage )
993+ require .NoError (ht , err )
994+
995+ preimage , err := lntypes .MakePreimage (preimageBytes )
996+ require .NoError (ht , err )
997+
998+ hash , err := lntypes .MakeHash (invoiceResp .RHash )
999+ require .NoError (ht , err )
1000+
1001+ // Make sure we got the preimage.
1002+ require .True (ht , preimage .Matches (hash ), "preimage doesn't match" )
1003+
1004+ // Check that Alice split the payment in at least two shards. Because
1005+ // the hand-off of the htlc to the link is asynchronous (via a mailbox),
1006+ // there is some non-determinism in the process. Depending on whether
1007+ // the new pathfinding round is started before or after the htlc is
1008+ // locked into the channel, different sharding may occur. Therefore, we
1009+ // can only check if the number of shards isn't below the theoretical
1010+ // minimum.
1011+ succeeded := 0
1012+ for _ , htlc := range payment .Htlcs {
1013+ if htlc .Status == lnrpc .HTLCAttempt_SUCCEEDED {
1014+ succeeded ++
1015+ }
1016+ }
1017+
1018+ const minExpectedShards = 2
1019+ require .GreaterOrEqual (ht , succeeded , minExpectedShards ,
1020+ "expected shards not reached" )
1021+
1022+ // Make sure Dave show the invoice as settled for the full amount.
1023+ inv := dave .RPC .LookupInvoice (invoiceResp .RHash )
1024+
1025+ require .EqualValues (ht , paymentAmt , inv .AmtPaidSat ,
1026+ "incorrect payment amt" )
1027+
1028+ require .Equal (ht , lnrpc .Invoice_SETTLED , inv .State ,
1029+ "Invoice not settled" )
1030+
1031+ settled := 0
1032+ for _ , htlc := range inv .Htlcs {
1033+ if htlc .State == lnrpc .InvoiceHTLCState_SETTLED {
1034+ settled ++
1035+ }
1036+ }
1037+ require .Equal (ht , succeeded , settled , "num of HTLCs wrong" )
1038+
1039+ // Close all channels without mining the closing transactions.
1040+ ht .CloseChannelAssertPending (alice , channelPoints [0 ], false )
1041+ ht .CloseChannelAssertPending (alice , channelPoints [1 ], false )
1042+ ht .CloseChannelAssertPending (bob , channelPoints [2 ], false )
1043+ ht .CloseChannelAssertPending (eve , channelPoints [3 ], false )
1044+ ht .CloseChannelAssertPending (carol , channelPoints [4 ], false )
1045+
1046+ // Now mine a block to include all the closing transactions.
1047+ ht .MineBlocksAndAssertNumTxes (1 , 5 )
1048+
1049+ // Assert that the channels are closed.
1050+ for _ , hn := range nodes {
1051+ ht .AssertNumWaitingClose (hn , 0 )
1052+ }
1053+ }
0 commit comments