Skip to content

Commit 66765de

Browse files
committed
itest: add route blinding dummy hops test
Add an itest that tests the addition of dummy hops to a blinded path. By testing that invoices containing such a path can be paid, it also tests the peeling of dummy hops by the receiver.
1 parent f0558ba commit 66765de

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,10 @@ var allTestCases = []*lntest.TestCase{
590590
Name: "mpp to single blinded path",
591591
TestFunc: testMPPToSingleBlindedPath,
592592
},
593+
{
594+
Name: "route blinding dummy hops",
595+
TestFunc: testBlindedRouteDummyHops,
596+
},
593597
{
594598
Name: "removetx",
595599
TestFunc: testRemoveTx,

itest/lnd_route_blinding_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,3 +1051,171 @@ func testMPPToSingleBlindedPath(ht *lntest.HarnessTest) {
10511051
ht.AssertNumWaitingClose(hn, 0)
10521052
}
10531053
}
1054+
1055+
// testBlindedRouteDummyHops tests that the route blinding flow works as
1056+
// expected in the cases where the recipient chooses to pad the blinded path
1057+
// with dummy hops.
1058+
//
1059+
// We will set up the following network were Dave will always be the recipient
1060+
// and Alice is always the payer. Bob will not support route blinding and so
1061+
// will never be chosen as the introduction node.
1062+
//
1063+
// Alice -- Bob -- Carol -- Dave
1064+
//
1065+
// First we will start Carol _without_ route blinding support. Then we will
1066+
// configure Dave such that any blinded route he constructs must be 2 hops long.
1067+
// Since Carol cannot be chosen as an introduction node, Dave chooses himself
1068+
// as an introduction node and appends two dummy hops.
1069+
// Next, we will restart Carol with route blinding support and repeat the test
1070+
// but this time we force Dave to construct a path with a minimum distance of 1
1071+
// between him and the introduction node. So we expect that Carol is chosen as
1072+
// the intro node and that one dummy hops is appended.
1073+
func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
1074+
alice, bob := ht.Alice, ht.Bob
1075+
1076+
// Disable route blinding for Bob so that he is never chosen as the
1077+
// introduction node.
1078+
ht.RestartNodeWithExtraArgs(bob, []string{
1079+
"--protocol.no-route-blinding",
1080+
})
1081+
1082+
// Start carol without route blinding so that initially Carol is also
1083+
// not chosen.
1084+
carol := ht.NewNode("carol", []string{
1085+
"--protocol.no-route-blinding",
1086+
})
1087+
1088+
// Configure Dave so that all blinded paths always contain 2 hops and
1089+
// so that there is no minimum number of real hops.
1090+
dave := ht.NewNode("dave", []string{
1091+
"--invoices.blinding.min-num-real-hops=0",
1092+
"--invoices.blinding.num-hops=2",
1093+
})
1094+
1095+
ht.EnsureConnected(alice, bob)
1096+
ht.EnsureConnected(bob, carol)
1097+
ht.EnsureConnected(carol, dave)
1098+
1099+
// Send coins to carol and mine 1 blocks to confirm them.
1100+
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
1101+
ht.MineBlocksAndAssertNumTxes(1, 1)
1102+
1103+
const paymentAmt = btcutil.Amount(300000)
1104+
1105+
nodes := []*node.HarnessNode{alice, bob, carol, dave}
1106+
reqs := []*lntest.OpenChannelRequest{
1107+
{
1108+
Local: alice,
1109+
Remote: bob,
1110+
Param: lntest.OpenChannelParams{
1111+
Amt: paymentAmt * 3,
1112+
},
1113+
},
1114+
{
1115+
Local: bob,
1116+
Remote: carol,
1117+
Param: lntest.OpenChannelParams{
1118+
Amt: paymentAmt * 3,
1119+
},
1120+
},
1121+
{
1122+
Local: carol,
1123+
Remote: dave,
1124+
Param: lntest.OpenChannelParams{
1125+
Amt: paymentAmt * 3,
1126+
},
1127+
},
1128+
}
1129+
1130+
channelPoints := ht.OpenMultiChannelsAsync(reqs)
1131+
1132+
// Make sure every node has heard about every channel.
1133+
for _, hn := range nodes {
1134+
for _, cp := range channelPoints {
1135+
ht.AssertTopologyChannelOpen(hn, cp)
1136+
}
1137+
1138+
// Each node should have exactly 5 edges.
1139+
ht.AssertNumEdges(hn, len(channelPoints), false)
1140+
}
1141+
1142+
// Make Dave create an invoice with a blinded path for Alice to pay.
1143+
invoice := &lnrpc.Invoice{
1144+
Memo: "test",
1145+
Value: int64(paymentAmt),
1146+
Blind: true,
1147+
}
1148+
invoiceResp := dave.RPC.AddInvoice(invoice)
1149+
1150+
// Assert that it contains a single blinded path and that the
1151+
// introduction node is dave.
1152+
payReq := dave.RPC.DecodePayReq(invoiceResp.PaymentRequest)
1153+
require.Len(ht, payReq.BlindedPaths, 1)
1154+
1155+
// The total number of hop payloads is 3: one for the introduction node
1156+
// and then one for each dummy hop.
1157+
path := payReq.BlindedPaths[0].BlindedPath
1158+
require.Len(ht, path.BlindedHops, 3)
1159+
require.EqualValues(ht, path.IntroductionNode, dave.PubKey[:])
1160+
1161+
// Now let Alice pay the invoice.
1162+
ht.CompletePaymentRequests(
1163+
ht.Alice, []string{invoiceResp.PaymentRequest},
1164+
)
1165+
1166+
// Make sure Dave show the invoice as settled.
1167+
inv := dave.RPC.LookupInvoice(invoiceResp.RHash)
1168+
require.Equal(ht, lnrpc.Invoice_SETTLED, inv.State)
1169+
1170+
// Let's also test the case where Dave is not the introduction node.
1171+
// We restart Carol so that she supports route blinding. We also restart
1172+
// Dave and force a minimum of 1 real blinded hop. We keep the number
1173+
// of hops to 2 meaning that one dummy hop should be added.
1174+
ht.RestartNodeWithExtraArgs(carol, nil)
1175+
ht.RestartNodeWithExtraArgs(dave, []string{
1176+
"--invoices.blinding.min-num-real-hops=1",
1177+
"--invoices.blinding.num-hops=2",
1178+
})
1179+
ht.EnsureConnected(bob, carol)
1180+
ht.EnsureConnected(carol, dave)
1181+
1182+
// Make Dave create an invoice with a blinded path for Alice to pay.
1183+
invoiceResp = dave.RPC.AddInvoice(invoice)
1184+
1185+
// Assert that it contains a single blinded path and that the
1186+
// introduction node is Carol.
1187+
payReq = dave.RPC.DecodePayReq(invoiceResp.PaymentRequest)
1188+
for _, path := range payReq.BlindedPaths {
1189+
ht.Logf("intro node: %x", path.BlindedPath.IntroductionNode)
1190+
}
1191+
1192+
require.Len(ht, payReq.BlindedPaths, 1)
1193+
1194+
// The total number of hop payloads is 3: one for the introduction node
1195+
// and then one for each dummy hop.
1196+
path = payReq.BlindedPaths[0].BlindedPath
1197+
require.Len(ht, path.BlindedHops, 3)
1198+
require.EqualValues(ht, path.IntroductionNode, carol.PubKey[:])
1199+
1200+
// Now let Alice pay the invoice.
1201+
ht.CompletePaymentRequests(
1202+
ht.Alice, []string{invoiceResp.PaymentRequest},
1203+
)
1204+
1205+
// Make sure Dave show the invoice as settled.
1206+
inv = dave.RPC.LookupInvoice(invoiceResp.RHash)
1207+
require.Equal(ht, lnrpc.Invoice_SETTLED, inv.State)
1208+
1209+
// Close all channels without mining the closing transactions.
1210+
ht.CloseChannelAssertPending(alice, channelPoints[0], false)
1211+
ht.CloseChannelAssertPending(bob, channelPoints[1], false)
1212+
ht.CloseChannelAssertPending(carol, channelPoints[2], false)
1213+
1214+
// Now mine a block to include all the closing transactions.
1215+
ht.MineBlocksAndAssertNumTxes(1, 3)
1216+
1217+
// Assert that the channels are closed.
1218+
for _, hn := range nodes {
1219+
ht.AssertNumWaitingClose(hn, 0)
1220+
}
1221+
}

0 commit comments

Comments
 (0)