You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
8.1 KiB
235 lines
8.1 KiB
package itest |
|
|
|
import ( |
|
"context" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/lightningnetwork/lnd/lncfg" |
|
"github.com/lightningnetwork/lnd/lnrpc" |
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" |
|
"github.com/lightningnetwork/lnd/lntest" |
|
"github.com/lightningnetwork/lnd/lntest/wait" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
// testMultiHopHtlcLocalTimeout tests that in a multi-hop HTLC scenario, if the |
|
// outgoing HTLC is about to time out, then we'll go to chain in order to claim |
|
// it using the HTLC timeout transaction. Any dust HTLC's should be immediately |
|
// canceled backwards. Once the timeout has been reached, then we should sweep |
|
// it on-chain, and cancel the HTLC backwards. |
|
func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, |
|
alice, bob *lntest.HarnessNode, c commitType) { |
|
|
|
ctxb := context.Background() |
|
|
|
// First, we'll create a three hop network: Alice -> Bob -> Carol, with |
|
// Carol refusing to actually settle or directly cancel any HTLC's |
|
// self. |
|
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork( |
|
t, net, alice, bob, true, c, |
|
) |
|
|
|
// Clean up carol's node when the test finishes. |
|
defer shutdownAndAssert(net, t, carol) |
|
|
|
time.Sleep(time.Second * 1) |
|
|
|
// Now that our channels are set up, we'll send two HTLC's from Alice |
|
// to Carol. The first HTLC will be universally considered "dust", |
|
// while the second will be a proper fully valued HTLC. |
|
const ( |
|
dustHtlcAmt = btcutil.Amount(100) |
|
htlcAmt = btcutil.Amount(300_000) |
|
finalCltvDelta = 40 |
|
) |
|
|
|
ctx, cancel := context.WithCancel(ctxb) |
|
defer cancel() |
|
|
|
// We'll create two random payment hashes unknown to carol, then send |
|
// each of them by manually specifying the HTLC details. |
|
carolPubKey := carol.PubKey[:] |
|
dustPayHash := makeFakePayHash(t) |
|
payHash := makeFakePayHash(t) |
|
|
|
_, err := alice.RouterClient.SendPaymentV2( |
|
ctx, &routerrpc.SendPaymentRequest{ |
|
Dest: carolPubKey, |
|
Amt: int64(dustHtlcAmt), |
|
PaymentHash: dustPayHash, |
|
FinalCltvDelta: finalCltvDelta, |
|
TimeoutSeconds: 60, |
|
FeeLimitMsat: noFeeLimitMsat, |
|
}, |
|
) |
|
require.NoError(t.t, err) |
|
|
|
_, err = alice.RouterClient.SendPaymentV2( |
|
ctx, &routerrpc.SendPaymentRequest{ |
|
Dest: carolPubKey, |
|
Amt: int64(htlcAmt), |
|
PaymentHash: payHash, |
|
FinalCltvDelta: finalCltvDelta, |
|
TimeoutSeconds: 60, |
|
FeeLimitMsat: noFeeLimitMsat, |
|
}, |
|
) |
|
require.NoError(t.t, err) |
|
|
|
// Verify that all nodes in the path now have two HTLC's with the |
|
// proper parameters. |
|
nodes := []*lntest.HarnessNode{alice, bob, carol} |
|
err = wait.NoError(func() error { |
|
return assertActiveHtlcs(nodes, dustPayHash, payHash) |
|
}, defaultTimeout) |
|
require.NoError(t.t, err) |
|
|
|
// Increase the fee estimate so that the following force close tx will |
|
// be cpfp'ed. |
|
net.SetFeeEstimate(30000) |
|
|
|
// We'll now mine enough blocks to trigger Bob's broadcast of his |
|
// commitment transaction due to the fact that the HTLC is about to |
|
// timeout. With the default outgoing broadcast delta of zero, this will |
|
// be the same height as the htlc expiry height. |
|
numBlocks := padCLTV( |
|
uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), |
|
) |
|
_, err = net.Miner.Client.Generate(numBlocks) |
|
require.NoError(t.t, err) |
|
|
|
// Bob's force close transaction should now be found in the mempool. If |
|
// there are anchors, we also expect Bob's anchor sweep. |
|
expectedTxes := 1 |
|
if c == commitTypeAnchors { |
|
expectedTxes = 2 |
|
} |
|
|
|
bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) |
|
require.NoError(t.t, err) |
|
_, err = waitForNTxsInMempool( |
|
net.Miner.Client, expectedTxes, minerMempoolTimeout, |
|
) |
|
require.NoError(t.t, err) |
|
closeTx := getSpendingTxInMempool( |
|
t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{ |
|
Hash: *bobFundingTxid, |
|
Index: bobChanPoint.OutputIndex, |
|
}, |
|
) |
|
closeTxid := closeTx.TxHash() |
|
|
|
// Mine a block to confirm the closing transaction. |
|
mineBlocks(t, net, 1, expectedTxes) |
|
|
|
// At this point, Bob should have canceled backwards the dust HTLC |
|
// that we sent earlier. This means Alice should now only have a single |
|
// HTLC on her channel. |
|
nodes = []*lntest.HarnessNode{alice} |
|
err = wait.NoError(func() error { |
|
return assertActiveHtlcs(nodes, payHash) |
|
}, defaultTimeout) |
|
require.NoError(t.t, err) |
|
|
|
// With the closing transaction confirmed, we should expect Bob's HTLC |
|
// timeout transaction to be broadcast due to the expiry being reached. |
|
// If there are anchors, we also expect Carol's anchor sweep now. |
|
txes, err := getNTxsFromMempool( |
|
net.Miner.Client, expectedTxes, minerMempoolTimeout, |
|
) |
|
require.NoError(t.t, err) |
|
|
|
// Lookup the timeout transaction that is expected to spend from the |
|
// closing tx. We distinguish it from a possibly anchor sweep by value. |
|
var htlcTimeout *chainhash.Hash |
|
for _, tx := range txes { |
|
prevOp := tx.TxIn[0].PreviousOutPoint |
|
require.Equal(t.t, closeTxid, prevOp.Hash) |
|
|
|
// Assume that the timeout tx doesn't spend an output of exactly |
|
// the size of the anchor. |
|
if closeTx.TxOut[prevOp.Index].Value != anchorSize { |
|
hash := tx.TxHash() |
|
htlcTimeout = &hash |
|
} |
|
} |
|
require.NotNil(t.t, htlcTimeout) |
|
|
|
// We'll mine the remaining blocks in order to generate the sweep |
|
// transaction of Bob's commitment output. The commitment was just |
|
// mined at the current tip and the sweep will be broadcast so it can |
|
// be mined at the tip+defaultCSV'th block, so mine one less to be able |
|
// to make mempool assertions. |
|
mineBlocks(t, net, defaultCSV-1, expectedTxes) |
|
|
|
// Check that the sweep spends from the mined commitment. |
|
txes, err = getNTxsFromMempool(net.Miner.Client, 1, minerMempoolTimeout) |
|
require.NoError(t.t, err) |
|
assertAllTxesSpendFrom(t, txes, closeTxid) |
|
|
|
// Bob's pending channel report should show that he has a commitment |
|
// output awaiting sweeping, and also that there's an outgoing HTLC |
|
// output pending. |
|
pendingChansRequest := &lnrpc.PendingChannelsRequest{} |
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) |
|
pendingChanResp, err := bob.PendingChannels(ctxt, pendingChansRequest) |
|
require.NoError(t.t, err) |
|
|
|
require.NotZero(t.t, len(pendingChanResp.PendingForceClosingChannels)) |
|
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0] |
|
require.NotZero(t.t, forceCloseChan.LimboBalance) |
|
require.NotZero(t.t, len(forceCloseChan.PendingHtlcs)) |
|
|
|
// Mine a block to confirm Bob's commit sweep tx and assert it was in |
|
// fact mined. |
|
block := mineBlocks(t, net, 1, 1)[0] |
|
commitSweepTxid := txes[0].TxHash() |
|
assertTxInBlock(t, block, &commitSweepTxid) |
|
|
|
// Mine an additional block to prompt Bob to broadcast their second |
|
// layer sweep due to the CSV on the HTLC timeout output. |
|
mineBlocks(t, net, 1, 0) |
|
assertSpendingTxInMempool( |
|
t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{ |
|
Hash: *htlcTimeout, |
|
Index: 0, |
|
}, |
|
) |
|
|
|
// The block should have confirmed Bob's HTLC timeout transaction. |
|
// Therefore, at this point, there should be no active HTLC's on the |
|
// commitment transaction from Alice -> Bob. |
|
nodes = []*lntest.HarnessNode{alice} |
|
err = wait.NoError(func() error { |
|
return assertNumActiveHtlcs(nodes, 0) |
|
}, defaultTimeout) |
|
require.NoError(t.t, err) |
|
|
|
// At this point, Bob should show that the pending HTLC has advanced to |
|
// the second stage and is to be swept. |
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
|
pendingChanResp, err = bob.PendingChannels(ctxt, pendingChansRequest) |
|
require.NoError(t.t, err) |
|
forceCloseChan = pendingChanResp.PendingForceClosingChannels[0] |
|
require.Equal(t.t, uint32(2), forceCloseChan.PendingHtlcs[0].Stage) |
|
|
|
// Next, we'll mine a final block that should confirm the second-layer |
|
// sweeping transaction. |
|
_, err = net.Miner.Client.Generate(1) |
|
require.NoError(t.t, err) |
|
|
|
// Once this transaction has been confirmed, Bob should detect that he |
|
// no longer has any pending channels. |
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
|
err = waitForNumChannelPendingForceClose(ctxt, bob, 0, nil) |
|
require.NoError(t.t, err) |
|
|
|
// Coop close channel, expect no anchors. |
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) |
|
closeChannelAndAssertType( |
|
ctxt, t, net, alice, aliceChanPoint, false, false, |
|
) |
|
}
|
|
|