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.
This commit is contained in:
Carla Kirk-Cohen 2024-04-08 11:46:43 -04:00
parent 2140f1940f
commit 75d4a4c295
No known key found for this signature in database
GPG key ID: 4CA7FE54A6213C91
2 changed files with 111 additions and 2 deletions

View file

@ -582,6 +582,10 @@ var allTestCases = []*lntest.TestCase{
Name: "disable introduction node",
TestFunc: testDisableIntroductionNode,
},
{
Name: "on chain to blinded",
TestFunc: testErrorHandlingOnChainFailure,
},
{
Name: "removetx",
TestFunc: testRemoveTx,

View file

@ -355,14 +355,16 @@ func (b *blindedForwardTest) setup(
ctx context.Context) *routing.BlindedPayment {
b.carol = b.ht.NewNode("Carol", []string{
"requireinterceptor",
"--requireinterceptor", "--bitcoin.timelockdelta=18",
})
var err error
b.carolInterceptor, err = b.carol.RPC.Router.HtlcInterceptor(ctx)
require.NoError(b.ht, err, "interceptor")
b.dave = b.ht.NewNode("Dave", nil)
b.dave = b.ht.NewNode("Dave", []string{
"--bitcoin.timelockdelta=18",
})
b.channels = setupFourHopNetwork(b.ht, b.carol, b.dave)
@ -951,3 +953,106 @@ func testDisableIntroductionNode(ht *lntest.HarnessTest) {
// if Bob _doesn't_ fail the HTLC back as expected.
sendAndResumeBlindedPayment(ctx, ht, testCase, route, false)
}
// testErrorHandlingOnChainFailure tests handling of blinded errors when we're
// resolving from an on-chain resolution. This test also tests that we're able
// to resolve blinded HTLCs on chain between restarts, as we've got all the
// infrastructure in place already for error testing.
func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
// Setup a test case, note that we don't use its built in clean up
// because we're going to close a channel so we'll close out the
// rest manually.
ctx, testCase := newBlindedForwardTest(ht)
// Note that we send a larger amount here do it'll be worthwhile for
// the sweeper to claim.
route := testCase.setup(ctx)
blindedRoute := testCase.createRouteToBlinded(50_000_000, route)
// Once our interceptor is set up, we can send the blinded payment.
cancelPmt := testCase.sendBlindedPayment(ctx, blindedRoute)
defer cancelPmt()
// Wait for the HTLC to be active on Alice and Bob's channels.
hash := sha256.Sum256(testCase.preimage[:])
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
// Intercept the forward on Carol's link, but do not take any action
// so that we have the chance to force close with this HTLC in flight.
carolHTLC, err := testCase.carolInterceptor.Recv()
require.NoError(ht, err)
// Force close Bob <-> Carol.
closeStream, _ := ht.CloseChannelAssertPending(
ht.Bob, testCase.channels[1], true,
)
ht.AssertStreamChannelForceClosed(
ht.Bob, testCase.channels[1], false, closeStream,
)
// SuspendCarol so that she can't interfere with the resolution of the
// HTLC from now on.
restartCarol := ht.SuspendNode(testCase.carol)
// Mine blocks so that Bob will claim his CSV delayed local commitment,
// we've already mined 1 block so we need one less than our CSV.
ht.MineBlocks(node.DefaultCSV - 1)
ht.AssertNumPendingSweeps(ht.Bob, 1)
ht.MineEmptyBlocks(1)
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
// Restart bob so that we can test that he's able to recover everything
// he needs to claim a blinded HTLC.
ht.RestartNode(ht.Bob)
// Mine enough blocks for Bob to trigger timeout of his outgoing HTLC.
// Carol's incoming expiry height is Bob's outgoing so we can use this
// value.
info := ht.Bob.RPC.GetInfo()
target := carolHTLC.IncomingExpiry - info.BlockHeight
ht.MineBlocks(target)
// Wait for Bob's timeout transaction in the mempool, since we've
// suspended Carol we don't need to account for her commitment output
// claim.
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
// Assert that the HTLC has cleared.
ht.AssertHTLCNotActive(ht.Alice, testCase.channels[0], hash[:])
ht.AssertHTLCNotActive(ht.Bob, testCase.channels[0], hash[:])
// Wait for the HTLC to reflect as failed for Alice.
paymentStream := ht.Alice.RPC.TrackPaymentV2(hash[:])
htlcs := ht.ReceiveTrackPayment(paymentStream).Htlcs
require.Len(ht, htlcs, 1)
require.NotNil(ht, htlcs[0].Failure)
require.Equal(
ht, htlcs[0].Failure.Code,
lnrpc.Failure_INVALID_ONION_BLINDING,
)
// Clean up the rest of our force close: mine blocks so that Bob's CSV
// expires plus one block to trigger his sweep and then mine it.
ht.MineBlocks(node.DefaultCSV + 1)
ht.Miner.MineBlocksAndAssertNumTxes(1, 1)
// Bring carol back up so that we can close out the rest of our
// channels cooperatively. She requires an interceptor to start up
// so we just re-register our interceptor.
require.NoError(ht, restartCarol())
_, err = testCase.carol.RPC.Router.HtlcInterceptor(ctx)
require.NoError(ht, err, "interceptor")
// Assert that Carol has started up and reconnected to dave so that
// we can close out channels cooperatively.
ht.EnsureConnected(testCase.carol, testCase.dave)
// Manually close out the rest of our channels and cancel (don't use
// built in cleanup which will try close the already-force-closed
// channel).
ht.CloseChannel(ht.Alice, testCase.channels[0])
ht.CloseChannel(testCase.carol, testCase.channels[2])
testCase.cancel()
}