Skip to content

Commit 75d4a4c

Browse files
committed
itest: add coverage for blinded error resolution from on-chain failure
This itest adds a test that we still propagate blinded errors back properly after a restart with an on-chain resolution. The test also updates our sendpayment timeout to longer so that there's time to resolve the on chain claim.
1 parent 2140f19 commit 75d4a4c

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ var allTestCases = []*lntest.TestCase{
582582
Name: "disable introduction node",
583583
TestFunc: testDisableIntroductionNode,
584584
},
585+
{
586+
Name: "on chain to blinded",
587+
TestFunc: testErrorHandlingOnChainFailure,
588+
},
585589
{
586590
Name: "removetx",
587591
TestFunc: testRemoveTx,

itest/lnd_route_blinding_test.go

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,16 @@ func (b *blindedForwardTest) setup(
355355
ctx context.Context) *routing.BlindedPayment {
356356

357357
b.carol = b.ht.NewNode("Carol", []string{
358-
"requireinterceptor",
358+
"--requireinterceptor", "--bitcoin.timelockdelta=18",
359359
})
360360

361361
var err error
362362
b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx)
363363
require.NoError(b.ht, err, "interceptor")
364364

365-
b.dave = b.ht.NewNode("Dave", nil)
365+
b.dave = b.ht.NewNode("Dave", []string{
366+
"--bitcoin.timelockdelta=18",
367+
})
366368

367369
b.channels = setupFourHopNetwork(b.ht, b.carol, b.dave)
368370

@@ -951,3 +953,106 @@ func testDisableIntroductionNode(ht *lntest.HarnessTest) {
951953
// if Bob _doesn't_ fail the HTLC back as expected.
952954
sendAndResumeBlindedPayment(ctx, ht, testCase, route, false)
953955
}
956+
957+
// testErrorHandlingOnChainFailure tests handling of blinded errors when we're
958+
// resolving from an on-chain resolution. This test also tests that we're able
959+
// to resolve blinded HTLCs on chain between restarts, as we've got all the
960+
// infrastructure in place already for error testing.
961+
func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
962+
// Setup a test case, note that we don't use its built in clean up
963+
// because we're going to close a channel so we'll close out the
964+
// rest manually.
965+
ctx, testCase := newBlindedForwardTest(ht)
966+
967+
// Note that we send a larger amount here do it'll be worthwhile for
968+
// the sweeper to claim.
969+
route := testCase.setup(ctx)
970+
blindedRoute := testCase.createRouteToBlinded(50_000_000, route)
971+
972+
// Once our interceptor is set up, we can send the blinded payment.
973+
cancelPmt := testCase.sendBlindedPayment(ctx, blindedRoute)
974+
defer cancelPmt()
975+
976+
// Wait for the HTLC to be active on Alice and Bob's channels.
977+
hash := sha256.Sum256(testCase.preimage[:])
978+
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
979+
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
980+
981+
// Intercept the forward on Carol's link, but do not take any action
982+
// so that we have the chance to force close with this HTLC in flight.
983+
carolHTLC, err := testCase.carolInterceptor.Recv()
984+
require.NoError(ht, err)
985+
986+
// Force close Bob <-> Carol.
987+
closeStream, _ := ht.CloseChannelAssertPending(
988+
ht.Bob, testCase.channels[1], true,
989+
)
990+
991+
ht.AssertStreamChannelForceClosed(
992+
ht.Bob, testCase.channels[1], false, closeStream,
993+
)
994+
995+
// SuspendCarol so that she can't interfere with the resolution of the
996+
// HTLC from now on.
997+
restartCarol := ht.SuspendNode(testCase.carol)
998+
999+
// Mine blocks so that Bob will claim his CSV delayed local commitment,
1000+
// we've already mined 1 block so we need one less than our CSV.
1001+
ht.MineBlocks(node.DefaultCSV - 1)
1002+
ht.AssertNumPendingSweeps(ht.Bob, 1)
1003+
ht.MineEmptyBlocks(1)
1004+
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
1005+
1006+
// Restart bob so that we can test that he's able to recover everything
1007+
// he needs to claim a blinded HTLC.
1008+
ht.RestartNode(ht.Bob)
1009+
1010+
// Mine enough blocks for Bob to trigger timeout of his outgoing HTLC.
1011+
// Carol's incoming expiry height is Bob's outgoing so we can use this
1012+
// value.
1013+
info := ht.Bob.RPC.GetInfo()
1014+
target := carolHTLC.IncomingExpiry - info.BlockHeight
1015+
ht.MineBlocks(target)
1016+
1017+
// Wait for Bob's timeout transaction in the mempool, since we've
1018+
// suspended Carol we don't need to account for her commitment output
1019+
// claim.
1020+
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
1021+
1022+
// Assert that the HTLC has cleared.
1023+
ht.AssertHTLCNotActive(ht.Alice, testCase.channels[0], hash[:])
1024+
ht.AssertHTLCNotActive(ht.Bob, testCase.channels[0], hash[:])
1025+
1026+
// Wait for the HTLC to reflect as failed for Alice.
1027+
paymentStream := ht.Alice.RPC.TrackPaymentV2(hash[:])
1028+
htlcs := ht.ReceiveTrackPayment(paymentStream).Htlcs
1029+
require.Len(ht, htlcs, 1)
1030+
require.NotNil(ht, htlcs[0].Failure)
1031+
require.Equal(
1032+
ht, htlcs[0].Failure.Code,
1033+
lnrpc.Failure_INVALID_ONION_BLINDING,
1034+
)
1035+
1036+
// Clean up the rest of our force close: mine blocks so that Bob's CSV
1037+
// expires plus one block to trigger his sweep and then mine it.
1038+
ht.MineBlocks(node.DefaultCSV + 1)
1039+
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
1040+
1041+
// Bring carol back up so that we can close out the rest of our
1042+
// channels cooperatively. She requires an interceptor to start up
1043+
// so we just re-register our interceptor.
1044+
require.NoError(ht, restartCarol())
1045+
_, err = testCase.carol.RPC.Router.HtlcInterceptor(ctx)
1046+
require.NoError(ht, err, "interceptor")
1047+
1048+
// Assert that Carol has started up and reconnected to dave so that
1049+
// we can close out channels cooperatively.
1050+
ht.EnsureConnected(testCase.carol, testCase.dave)
1051+
1052+
// Manually close out the rest of our channels and cancel (don't use
1053+
// built in cleanup which will try close the already-force-closed
1054+
// channel).
1055+
ht.CloseChannel(ht.Alice, testCase.channels[0])
1056+
ht.CloseChannel(testCase.carol, testCase.channels[2])
1057+
testCase.cancel()
1058+
}

0 commit comments

Comments
 (0)