mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
75f6622ccf
Legacy Channel types are not tested anymore via the multi-hop test harness, therefore we can remove unused code.
2699 lines
95 KiB
Go
2699 lines
95 KiB
Go
package itest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/chainreg"
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lntest"
|
|
"github.com/lightningnetwork/lnd/lntest/node"
|
|
"github.com/lightningnetwork/lnd/lntest/rpc"
|
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/routing"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
finalCltvDelta = routing.MinCLTVDelta // 18.
|
|
thawHeightDelta = finalCltvDelta * 2 // 36.
|
|
)
|
|
|
|
var commitWithZeroConf = []struct {
|
|
commitType lnrpc.CommitmentType
|
|
zeroConf bool
|
|
}{
|
|
{
|
|
commitType: lnrpc.CommitmentType_ANCHORS,
|
|
zeroConf: false,
|
|
},
|
|
{
|
|
commitType: lnrpc.CommitmentType_ANCHORS,
|
|
zeroConf: true,
|
|
},
|
|
{
|
|
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
|
zeroConf: false,
|
|
},
|
|
{
|
|
commitType: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
|
zeroConf: true,
|
|
},
|
|
{
|
|
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
zeroConf: false,
|
|
},
|
|
{
|
|
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
zeroConf: true,
|
|
},
|
|
}
|
|
|
|
// makeRouteHints creates a route hints that will allow Carol to be reached
|
|
// using an unadvertised channel created by Bob (Bob -> Carol). If the zeroConf
|
|
// bool is set, then the scid alias of Bob will be used in place.
|
|
func makeRouteHints(bob, carol *node.HarnessNode,
|
|
zeroConf bool) []*lnrpc.RouteHint {
|
|
|
|
carolChans := carol.RPC.ListChannels(
|
|
&lnrpc.ListChannelsRequest{},
|
|
)
|
|
|
|
carolChan := carolChans.Channels[0]
|
|
|
|
hopHint := &lnrpc.HopHint{
|
|
NodeId: carolChan.RemotePubkey,
|
|
ChanId: carolChan.ChanId,
|
|
FeeBaseMsat: uint32(
|
|
chainreg.DefaultBitcoinBaseFeeMSat,
|
|
),
|
|
FeeProportionalMillionths: uint32(
|
|
chainreg.DefaultBitcoinFeeRate,
|
|
),
|
|
CltvExpiryDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
|
}
|
|
|
|
if zeroConf {
|
|
bobChans := bob.RPC.ListChannels(
|
|
&lnrpc.ListChannelsRequest{},
|
|
)
|
|
|
|
// Now that we have Bob's channels, scan for the channel he has
|
|
// open to Carol so we can use the proper scid.
|
|
var found bool
|
|
for _, bobChan := range bobChans.Channels {
|
|
if bobChan.RemotePubkey == carol.PubKeyStr {
|
|
hopHint.ChanId = bobChan.AliasScids[0]
|
|
|
|
found = true
|
|
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
bob.Fatalf("unable to create route hint")
|
|
}
|
|
}
|
|
|
|
return []*lnrpc.RouteHint{
|
|
{
|
|
HopHints: []*lnrpc.HopHint{hopHint},
|
|
},
|
|
}
|
|
}
|
|
|
|
// caseRunner defines a single test case runner.
|
|
type caseRunner func(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
|
|
c lnrpc.CommitmentType, zeroConf bool)
|
|
|
|
// runMultiHopHtlcClaimTest is a helper method to build test cases based on
|
|
// different commitment types and zero-conf config and run them.
|
|
//
|
|
// TODO(yy): flatten this test.
|
|
func runMultiHopHtlcClaimTest(ht *lntest.HarnessTest, tester caseRunner) {
|
|
for _, typeAndConf := range commitWithZeroConf {
|
|
typeAndConf := typeAndConf
|
|
name := fmt.Sprintf("zeroconf=%v/committype=%v",
|
|
typeAndConf.zeroConf, typeAndConf.commitType.String())
|
|
|
|
// Create the nodes here so that separate logs will be created
|
|
// for Alice and Bob.
|
|
args := lntest.NodeArgsForCommitType(typeAndConf.commitType)
|
|
if typeAndConf.zeroConf {
|
|
args = append(
|
|
args, "--protocol.option-scid-alias",
|
|
"--protocol.zero-conf",
|
|
)
|
|
}
|
|
|
|
s := ht.Run(name, func(t1 *testing.T) {
|
|
st := ht.Subtest(t1)
|
|
|
|
alice := st.NewNode("Alice", args)
|
|
bob := st.NewNode("Bob", args)
|
|
st.ConnectNodes(alice, bob)
|
|
|
|
// Start each test with the default static fee estimate.
|
|
st.SetFeeEstimate(12500)
|
|
|
|
// Add test name to the logs.
|
|
alice.AddToLogf("Running test case: %s", name)
|
|
bob.AddToLogf("Running test case: %s", name)
|
|
|
|
tester(
|
|
st, alice, bob,
|
|
typeAndConf.commitType, typeAndConf.zeroConf,
|
|
)
|
|
})
|
|
if !s {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalTimeout)
|
|
}
|
|
|
|
func runMultiHopHtlcLocalTimeout(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, true, c, zeroConf,
|
|
)
|
|
|
|
// For neutrino backend, we need to fund one more UTXO for Bob so he
|
|
// can sweep his outputs.
|
|
if ht.IsNeutrinoBackend() {
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
|
}
|
|
|
|
// 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)
|
|
)
|
|
|
|
// 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 := ht.Random32Bytes()
|
|
payHash := ht.Random32Bytes()
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
|
Dest: carolPubKey,
|
|
Amt: int64(dustHtlcAmt),
|
|
PaymentHash: dustPayHash,
|
|
FinalCltvDelta: finalCltvDelta,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
RouteHints: routeHints,
|
|
})
|
|
|
|
alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
|
Dest: carolPubKey,
|
|
Amt: int64(htlcAmt),
|
|
PaymentHash: payHash,
|
|
FinalCltvDelta: finalCltvDelta,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
RouteHints: routeHints,
|
|
})
|
|
|
|
// Verify that all nodes in the path now have two HTLC's with the
|
|
// proper parameters.
|
|
ht.AssertActiveHtlcs(alice, dustPayHash, payHash)
|
|
ht.AssertActiveHtlcs(bob, dustPayHash, payHash)
|
|
ht.AssertActiveHtlcs(carol, dustPayHash, payHash)
|
|
|
|
// Increase the fee estimate so that the following force close tx will
|
|
// be cpfp'ed.
|
|
ht.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),
|
|
)
|
|
ht.MineBlocks(int(numBlocks))
|
|
|
|
// Bob's force close transaction should now be found in the mempool.
|
|
ht.AssertNumTxsInMempool(1)
|
|
op := ht.OutPointFromChannelPoint(bobChanPoint)
|
|
closeTx := ht.AssertOutpointInMempool(op)
|
|
|
|
// Bob's anchor output should be offered to his sweep since Bob has
|
|
// time-sensitive HTLCs - we expect both anchors are offered.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
// Mine a block to confirm the closing transaction.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// 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.
|
|
ht.AssertActiveHtlcs(alice, payHash)
|
|
|
|
// With the closing transaction confirmed, we should expect Bob's HTLC
|
|
// timeout transaction to be offered to the sweeper due to the expiry
|
|
// being reached. we also expect Bon and Carol's anchor sweeps.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Mine a block to trigger Bob's sweeper to sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// The above mined block would trigger Bob and Carol's sweepers to take
|
|
// action. We now expect two txns:
|
|
// 1. Bob's sweeping tx anchor sweep should now be found in the mempool.
|
|
// 2. Bob's HTLC timeout tx sweep should now be found in the mempool.
|
|
// Carol's anchor sweep should be failed due to output being dust.
|
|
ht.AssertNumTxsInMempool(2)
|
|
|
|
htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 2}
|
|
commitOutpoint := wire.OutPoint{Hash: closeTx.TxHash(), Index: 3}
|
|
htlcTimeoutTxid := ht.AssertOutpointInMempool(
|
|
htlcOutpoint,
|
|
).TxHash()
|
|
|
|
// Mine a block to confirm the above two sweeping txns.
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
|
|
// With Bob's HTLC timeout transaction confirmed, there should be no
|
|
// active HTLC's on the commitment transaction from Alice -> Bob.
|
|
ht.AssertNumActiveHtlcs(alice, 0)
|
|
|
|
// At this point, Bob should show that the pending HTLC has advanced to
|
|
// the second stage and is ready to be swept once the timelock is up.
|
|
pendingChanResp := bob.RPC.PendingChannels()
|
|
require.Equal(ht, 1, len(pendingChanResp.PendingForceClosingChannels))
|
|
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
|
|
require.NotZero(ht, forceCloseChan.LimboBalance)
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
require.Equal(ht, 1, len(forceCloseChan.PendingHtlcs))
|
|
require.Equal(ht, uint32(2), forceCloseChan.PendingHtlcs[0].Stage)
|
|
|
|
ht.Logf("Bob's timelock on commit=%v, timelock on htlc=%v",
|
|
forceCloseChan.BlocksTilMaturity,
|
|
forceCloseChan.PendingHtlcs[0].BlocksTilMaturity)
|
|
|
|
htlcTimeoutOutpoint := wire.OutPoint{Hash: htlcTimeoutTxid, Index: 0}
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// Since Bob is the initiator of the script-enforced leased
|
|
// channel between him and Carol, he will incur an additional
|
|
// CLTV on top of the usual CSV delay on any outputs that he
|
|
// can sweep back to his wallet.
|
|
blocksTilMaturity := int(forceCloseChan.BlocksTilMaturity)
|
|
|
|
// We now mine enough blocks to trigger the sweep of the HTLC
|
|
// timeout tx.
|
|
ht.MineEmptyBlocks(blocksTilMaturity - 1)
|
|
|
|
// Check that Bob has one pending sweeping tx - the HTLC
|
|
// timeout tx.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine one more blocks, then his commit output will mature.
|
|
// This will also trigger the sweeper to sweep his HTLC timeout
|
|
// tx.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Check that Bob has two pending sweeping txns.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
// Assert that the HTLC timeout tx is now in the mempool.
|
|
ht.AssertOutpointInMempool(htlcTimeoutOutpoint)
|
|
|
|
// We now wait for 30 seconds to overcome the flake - there's a
|
|
// block race between contractcourt and sweeper, causing the
|
|
// sweep to be broadcast earlier.
|
|
//
|
|
// TODO(yy): remove this once `blockbeat` is in place.
|
|
numExpected := 1
|
|
err := wait.NoError(func() error {
|
|
mem := ht.GetRawMempool()
|
|
if len(mem) == 2 {
|
|
numExpected = 2
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("want %d, got %v in mempool: %v",
|
|
numExpected, len(mem), mem)
|
|
}, wait.DefaultTimeout)
|
|
ht.Logf("Checking mempool got: %v", err)
|
|
|
|
// Mine a block to trigger the sweep of his commit output and
|
|
// confirm his HTLC timeout sweep.
|
|
ht.MineBlocksAndAssertNumTxes(1, numExpected)
|
|
|
|
// For leased channels, we need to mine one more block to
|
|
// confirm Bob's commit output sweep.
|
|
//
|
|
// NOTE: we mine this block conditionally, as the commit output
|
|
// may have already been swept one block earlier due to the
|
|
// race in block consumption among subsystems.
|
|
pendingChanResp := bob.RPC.PendingChannels()
|
|
if len(pendingChanResp.PendingForceClosingChannels) != 0 {
|
|
// Check that the sweep spends the expected inputs.
|
|
ht.AssertOutpointInMempool(commitOutpoint)
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
}
|
|
} else {
|
|
// Since Bob force closed the channel between him and Carol, he
|
|
// will incur the usual CSV delay on any outputs that he can
|
|
// sweep back to his wallet. We'll subtract one block from our
|
|
// current maturity period to assert on the mempool.
|
|
numBlocks := int(forceCloseChan.BlocksTilMaturity - 1)
|
|
ht.MineEmptyBlocks(numBlocks)
|
|
|
|
// Check that Bob has a pending sweeping tx.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block the trigger the sweeping behavior.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Check that the sweep spends from the mined commitment.
|
|
ht.AssertOutpointInMempool(commitOutpoint)
|
|
|
|
// Mine one more block to trigger the timeout path.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Bob's sweeper should now broadcast his second layer sweep
|
|
// due to the CSV on the HTLC timeout output.
|
|
ht.AssertOutpointInMempool(htlcTimeoutOutpoint)
|
|
|
|
// Next, we'll mine a final block that should confirm the
|
|
// sweeping transactions left.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
}
|
|
|
|
// Once this transaction has been confirmed, Bob should detect that he
|
|
// no longer has any pending channels.
|
|
ht.AssertNumPendingForceClose(bob, 0)
|
|
|
|
// Coop close channel, expect no anchors.
|
|
ht.CloseChannel(alice, aliceChanPoint)
|
|
}
|
|
|
|
// testMultiHopReceiverChainClaim tests that in the multi-hop setting, if the
|
|
// receiver of an HTLC knows the preimage, but wasn't able to settle the HTLC
|
|
// off-chain, then it goes on chain to claim the HTLC uing the HTLC success
|
|
// transaction. In this scenario, the node that sent the outgoing HTLC should
|
|
// extract the preimage from the sweep transaction, and finish settling the
|
|
// HTLC backwards into the route.
|
|
func testMultiHopReceiverChainClaim(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runMultiHopReceiverChainClaim)
|
|
}
|
|
|
|
func runMultiHopReceiverChainClaim(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
// For neutrino backend, we need to fund one more UTXO for Carol so she
|
|
// can sweep her outputs.
|
|
if ht.IsNeutrinoBackend() {
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
}
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
// won't send out the outgoing htlc.
|
|
const invoiceAmt = 100000
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: invoiceAmt,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
// will not immediately settle the payment.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// At this point, all 3 nodes should now have an active channel with
|
|
// the created HTLC pending on all of them.
|
|
ht.AssertActiveHtlcs(alice, payHash[:])
|
|
ht.AssertActiveHtlcs(bob, payHash[:])
|
|
ht.AssertActiveHtlcs(carol, payHash[:])
|
|
|
|
// Wait for carol to mark invoice as accepted. There is a small gap to
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
// hop logic.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Settle invoice. This will just mark the invoice as settled, as there
|
|
// is no link anymore to remove the htlc from the commitment tx. For
|
|
// this test, it is important to actually settle and not leave the
|
|
// invoice in the accepted state, because without a known preimage, the
|
|
// channel arbitrator won't go to chain.
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
// Increase the fee estimate so that the following force close tx will
|
|
// be cpfp'ed.
|
|
ht.SetFeeEstimate(30000)
|
|
|
|
// We now advance the block height to the point where Carol will force
|
|
// close her channel with Bob, broadcast the closing tx but keep it
|
|
// unconfirmed.
|
|
numBlocks := padCLTV(uint32(
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
|
))
|
|
|
|
// Now we'll mine enough blocks to prompt carol to actually go to the
|
|
// chain in order to sweep her HTLC since the value is high enough.
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// At this point, Carol should broadcast her active commitment
|
|
// transaction in order to go to the chain and sweep her HTLC.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
closingTx := ht.AssertOutpointInMempool(
|
|
ht.OutPointFromChannelPoint(bobChanPoint),
|
|
)
|
|
closingTxid := closingTx.TxHash()
|
|
|
|
// Carol's anchor should have been offered to her sweeper as she has
|
|
// time-sensitive HTLCs. Assert that we have two anchors - one for the
|
|
// anchor on the local commitment and the other for the anchor on the
|
|
// remote commitment (invalid).
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Confirm the commitment.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// The above mined block will trigger Carol's sweeper to publish the
|
|
// anchor sweeping tx.
|
|
//
|
|
// TODO(yy): should instead cancel the broadcast of the anchor sweeping
|
|
// tx to save fees since we know the force close tx has been confirmed?
|
|
// This is very difficult as it introduces more complicated RBF
|
|
// scenarios, as we are using a wallet utxo, which means any txns using
|
|
// that wallet utxo must pay more fees. On the other hand, there's no
|
|
// way to remove that anchor-CPFP tx from the mempool.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// After the force close transaction is mined, Carol should offer her
|
|
// second level HTLC tx to the sweeper, which means we should see two
|
|
// pending inputs now - the anchor and the htlc.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Restart bob again.
|
|
require.NoError(ht, restartBob())
|
|
|
|
var expectedTxes int
|
|
|
|
// After the force close transaction is mined, a series of transactions
|
|
// should be broadcast by Bob and Carol. When Bob notices Carol's
|
|
// second level transaction in the mempool, he will extract the
|
|
// preimage and settle the HTLC back off-chain.
|
|
switch c {
|
|
// We expect to see three txns in the mempool:
|
|
// 1. Carol should broadcast her second level HTLC tx.
|
|
// 2. Carol should broadcast her anchor sweeping tx.
|
|
// 3. Bob should broadcast a sweep tx to sweep his output in the
|
|
// channel with Carol, and in the same sweep tx to sweep his anchor
|
|
// output.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
expectedTxes = 3
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
// We expect to see two txns in the mempool:
|
|
// 1. Carol should broadcast her second level HTLC tx.
|
|
// 2. Carol should broadcast her anchor sweeping tx.
|
|
// Bob would offer his anchor output to his sweeper, but it cannot be
|
|
// swept due to it being uneconomical. Bob's commit output can't be
|
|
// swept yet as he's incurring an additional CLTV from being the
|
|
// channel initiator of a script-enforced leased channel.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
expectedTxes = 2
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Mine one block to trigger the sweeper to sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// All transactions should be spending from the commitment transaction.
|
|
txes := ht.GetNumTxsFromMempool(expectedTxes)
|
|
ht.AssertAllTxesSpendFrom(txes, closingTxid)
|
|
|
|
// We'll now mine an additional block which should confirm both the
|
|
// second layer transactions.
|
|
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
|
|
// Carol's pending channel report should now show two outputs under
|
|
// limbo: her commitment output, as well as the second-layer claim
|
|
// output, and the pending HTLC should also now be in stage 2.
|
|
ht.AssertNumHTLCsAndStage(carol, bobChanPoint, 1, 2)
|
|
|
|
// Once the second-level transaction confirmed, Bob should have
|
|
// extracted the preimage from the chain, and sent it back to Alice,
|
|
// clearing the HTLC off-chain.
|
|
ht.AssertNumActiveHtlcs(alice, 0)
|
|
|
|
// If we mine 4 additional blocks, then Carol can sweep the second
|
|
// level HTLC output once the CSV expires.
|
|
ht.MineEmptyBlocks(defaultCSV - 1)
|
|
|
|
// Assert Carol has the pending HTLC sweep.
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Mine one block to trigger the sweeper to sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// We should have a new transaction in the mempool.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// Finally, if we mine an additional block to confirm Carol's second
|
|
// level success transaction. Carol should not show a pending channel
|
|
// in her report afterwards.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
ht.AssertNumPendingForceClose(carol, 0)
|
|
|
|
// The invoice should show as settled for Carol, indicating that it was
|
|
// swept on-chain.
|
|
ht.AssertInvoiceSettled(carol, carolInvoice.PaymentAddr)
|
|
|
|
// Finally, check that the Alice's payment is correctly marked
|
|
// succeeded.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// Bob still has his commit output to sweep to since he
|
|
// incurred an additional CLTV from being the channel initiator
|
|
// of a script-enforced leased channel, regardless of whether
|
|
// he forced closed the channel or not.
|
|
pendingChanResp := bob.RPC.PendingChannels()
|
|
|
|
require.Len(ht, pendingChanResp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
|
|
require.Positive(ht, forceCloseChan.LimboBalance)
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
|
|
// TODO: Bob still shows a pending HTLC at this point when he
|
|
// shouldn't, as he already extracted the preimage from Carol's
|
|
// claim.
|
|
// require.Len(t.t, forceCloseChan.PendingHtlcs, 0)
|
|
|
|
// Mine enough blocks for Bob's commit output's CLTV to expire
|
|
// and sweep it.
|
|
numBlocks := int(forceCloseChan.BlocksTilMaturity)
|
|
ht.MineEmptyBlocks(numBlocks)
|
|
|
|
// Bob should have two pending inputs to be swept, the commit
|
|
// output and the anchor output.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
|
|
ht.AssertOutpointInMempool(commitOutpoint)
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
}
|
|
|
|
ht.AssertNumPendingForceClose(bob, 0)
|
|
|
|
// We'll close out the channel between Alice and Bob, then shutdown
|
|
// carol to conclude the test.
|
|
ht.CloseChannel(alice, aliceChanPoint)
|
|
}
|
|
|
|
// testMultiHopLocalForceCloseOnChainHtlcTimeout tests that in a multi-hop HTLC
|
|
// scenario, if the node that extended the HTLC to the final node closes their
|
|
// commitment on-chain early, then it eventually recognizes this HTLC as one
|
|
// that's timed out. At this point, the node should timeout the HTLC using the
|
|
// HTLC timeout transaction, then cancel it backwards as normal.
|
|
func testMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(
|
|
ht, runMultiHopLocalForceCloseOnChainHtlcTimeout,
|
|
)
|
|
}
|
|
|
|
func runMultiHopLocalForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, true, c, zeroConf,
|
|
)
|
|
|
|
// With our channels set up, we'll then send a single HTLC from Alice
|
|
// to Carol. As Carol is in hodl mode, she won't settle this HTLC which
|
|
// opens up the base for out tests.
|
|
const htlcAmt = btcutil.Amount(300_000)
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// We'll now send a single HTLC across our multi-hop network.
|
|
carolPubKey := carol.PubKey[:]
|
|
payHash := ht.Random32Bytes()
|
|
req := &routerrpc.SendPaymentRequest{
|
|
Dest: carolPubKey,
|
|
Amt: int64(htlcAmt),
|
|
PaymentHash: payHash,
|
|
FinalCltvDelta: finalCltvDelta,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
RouteHints: routeHints,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// Once the HTLC has cleared, all channels in our mini network should
|
|
// have the it locked in.
|
|
ht.AssertActiveHtlcs(alice, payHash)
|
|
ht.AssertActiveHtlcs(bob, payHash)
|
|
ht.AssertActiveHtlcs(carol, payHash)
|
|
|
|
// blocksMined records how many blocks have mined after the creation of
|
|
// the invoice so it can be used to calculate how many more blocks need
|
|
// to be mined to trigger a force close later on.
|
|
var blocksMined uint32
|
|
|
|
// Now that all parties have the HTLC locked in, we'll immediately
|
|
// force close the Bob -> Carol channel. This should trigger contract
|
|
// resolution mode for both of them.
|
|
stream, _ := ht.CloseChannelAssertPending(bob, bobChanPoint, true)
|
|
closeTx := ht.AssertStreamChannelForceClosed(
|
|
bob, bobChanPoint, true, stream,
|
|
)
|
|
|
|
// Increase the blocks mined. At the step
|
|
// AssertStreamChannelForceClosed mines one block.
|
|
blocksMined++
|
|
|
|
// The channel close has anchors, we should expect to see both Bob and
|
|
// Carol has a pending sweep request for the anchor sweep.
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to confirm Bob's anchor sweep - Carol's anchor sweep
|
|
// won't succeed because it's not used for CPFP, so there's no wallet
|
|
// utxo used, resulting it to be uneconomical.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
blocksMined++
|
|
|
|
htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 2}
|
|
bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3}
|
|
|
|
// Before the HTLC times out, we'll need to assert that Bob broadcasts
|
|
// a sweep transaction for his commit output. Note that if the channel
|
|
// has a script-enforced lease, then Bob will have to wait for an
|
|
// additional CLTV before sweeping it.
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// The sweep is broadcast on the block immediately before the
|
|
// CSV expires and the commitment was already mined inside
|
|
// AssertStreamChannelForceClosed(), so mine one block less
|
|
// than defaultCSV in order to perform mempool assertions.
|
|
ht.MineEmptyBlocks(int(defaultCSV - blocksMined))
|
|
blocksMined = defaultCSV
|
|
|
|
// Assert Bob has the sweep and trigger it.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
ht.MineEmptyBlocks(1)
|
|
blocksMined++
|
|
|
|
commitSweepTx := ht.AssertOutpointInMempool(
|
|
bobCommitOutpoint,
|
|
)
|
|
txid := commitSweepTx.TxHash()
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &txid)
|
|
|
|
blocksMined++
|
|
}
|
|
|
|
// We'll now mine enough blocks for the HTLC to expire. After this, Bob
|
|
// should hand off the now expired HTLC output to the utxo nursery.
|
|
numBlocks := padCLTV(uint32(finalCltvDelta) -
|
|
lncfg.DefaultOutgoingBroadcastDelta)
|
|
ht.MineEmptyBlocks(int(numBlocks - blocksMined))
|
|
|
|
// Bob's pending channel report should show that he has a single HTLC
|
|
// that's now in stage one.
|
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 1)
|
|
|
|
// Bob should have a pending sweep request.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine one block to trigger Bob's sweeper to sweep it.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// We should also now find a transaction in the mempool, as Bob should
|
|
// have broadcast his second layer timeout transaction.
|
|
timeoutTx := ht.AssertOutpointInMempool(htlcOutpoint).TxHash()
|
|
|
|
// Next, we'll mine an additional block. This should serve to confirm
|
|
// the second layer timeout transaction.
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &timeoutTx)
|
|
|
|
// With the second layer timeout transaction confirmed, Bob should have
|
|
// canceled backwards the HTLC that carol sent.
|
|
ht.AssertNumActiveHtlcs(bob, 0)
|
|
|
|
// Additionally, Bob should now show that HTLC as being advanced to the
|
|
// second stage.
|
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2)
|
|
|
|
// Bob should now broadcast a transaction that sweeps certain inputs
|
|
// depending on the commitment type. We'll need to mine some blocks
|
|
// before the broadcast is possible.
|
|
resp := bob.RPC.PendingChannels()
|
|
|
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
require.Len(ht, forceCloseChan.PendingHtlcs, 1)
|
|
pendingHtlc := forceCloseChan.PendingHtlcs[0]
|
|
require.Positive(ht, pendingHtlc.BlocksTilMaturity)
|
|
numBlocks = uint32(pendingHtlc.BlocksTilMaturity)
|
|
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
var numExpected int
|
|
|
|
// Now that the CSV/CLTV timelock has expired, the transaction should
|
|
// either only sweep the HTLC timeout transaction, or sweep both the
|
|
// HTLC timeout transaction and Bob's commit output depending on the
|
|
// commitment type.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// Assert the expected number of pending sweeps are found.
|
|
sweeps := ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
numExpected = 1
|
|
if sweeps[0].DeadlineHeight != sweeps[1].DeadlineHeight {
|
|
numExpected = 2
|
|
}
|
|
} else {
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
numExpected = 1
|
|
}
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Assert the sweeping tx is found in the mempool.
|
|
htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0}
|
|
ht.AssertOutpointInMempool(htlcTimeoutOutpoint)
|
|
|
|
// Mine a block to confirm the sweep.
|
|
ht.MineBlocksAndAssertNumTxes(1, numExpected)
|
|
|
|
// At this point, Bob should no longer show any channels as pending
|
|
// close.
|
|
ht.AssertNumPendingForceClose(bob, 0)
|
|
|
|
// Coop close, no anchors.
|
|
ht.CloseChannel(alice, aliceChanPoint)
|
|
}
|
|
|
|
// testMultiHopRemoteForceCloseOnChainHtlcTimeout tests that if we extend a
|
|
// multi-hop HTLC, and the final destination of the HTLC force closes the
|
|
// channel, then we properly timeout the HTLC directly on *their* commitment
|
|
// transaction once the timeout has expired. Once we sweep the transaction, we
|
|
// should also cancel back the initial HTLC.
|
|
func testMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(
|
|
ht, runMultiHopRemoteForceCloseOnChainHtlcTimeout,
|
|
)
|
|
}
|
|
|
|
func runMultiHopRemoteForceCloseOnChainHtlcTimeout(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, true, c, zeroConf,
|
|
)
|
|
|
|
// With our channels set up, we'll then send a single HTLC from Alice
|
|
// to Carol. As Carol is in hodl mode, she won't settle this HTLC which
|
|
// opens up the base for out tests.
|
|
const htlcAmt = btcutil.Amount(30000)
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// We'll now send a single HTLC across our multi-hop network.
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: int64(htlcAmt),
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// blocksMined records how many blocks have mined after the creation of
|
|
// the invoice so it can be used to calculate how many more blocks need
|
|
// to be mined to trigger a force close later on.
|
|
var blocksMined uint32
|
|
|
|
// Once the HTLC has cleared, all the nodes in our mini network should
|
|
// show that the HTLC has been locked in.
|
|
ht.AssertActiveHtlcs(alice, payHash[:])
|
|
ht.AssertActiveHtlcs(bob, payHash[:])
|
|
ht.AssertActiveHtlcs(carol, payHash[:])
|
|
|
|
// At this point, we'll now instruct Carol to force close the
|
|
// transaction. This will let us exercise that Bob is able to sweep the
|
|
// expired HTLC on Carol's version of the commitment transaction.
|
|
closeStream, _ := ht.CloseChannelAssertPending(
|
|
carol, bobChanPoint, true,
|
|
)
|
|
|
|
// For anchor channels, the anchor won't be used for CPFP because
|
|
// channel arbitrator thinks Carol doesn't have preimage for her
|
|
// incoming HTLC on the commitment transaction Bob->Carol. Although
|
|
// Carol created this invoice, because it's a hold invoice, the
|
|
// preimage won't be generated automatically.
|
|
closeTx := ht.AssertStreamChannelForceClosed(
|
|
carol, bobChanPoint, true, closeStream,
|
|
)
|
|
|
|
// Increase the blocks mined. At this step
|
|
// AssertStreamChannelForceClosed mines one block.
|
|
blocksMined++
|
|
|
|
// At this point, Bob should have a pending force close channel as
|
|
// Carol has gone directly to chain.
|
|
ht.AssertNumPendingForceClose(bob, 1)
|
|
|
|
var expectedTxes int
|
|
switch c {
|
|
// Bob can sweep his commit and anchor outputs immediately. Carol will
|
|
// also offer her anchor to her sweeper.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// We expect to see only one sweeping tx to be published from
|
|
// Bob, which sweeps his commit and anchor outputs in the same
|
|
// tx. For Carol, since her anchor is not used for CPFP, it'd
|
|
// be uneconomical to sweep so it will fail.
|
|
expectedTxes = 1
|
|
|
|
// Bob can't sweep his commit output yet as he was the initiator of a
|
|
// script-enforced leased channel, so he'll always incur the additional
|
|
// CLTV. He can still offer his anchor output to his sweeper however.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// We expect to see only no sweeping txns to be published,
|
|
// neither Bob's or Carol's anchor sweep can succeed due to
|
|
// it's uneconomical.
|
|
expectedTxes = 0
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Mine one block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
blocksMined++
|
|
|
|
// We now mine a block to clear up the mempool.
|
|
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
blocksMined++
|
|
|
|
// Next, we'll mine enough blocks for the HTLC to expire. At this
|
|
// point, Bob should hand off the output to his internal utxo nursery,
|
|
// which will broadcast a sweep transaction.
|
|
numBlocks := padCLTV(uint32(finalCltvDelta) -
|
|
lncfg.DefaultOutgoingBroadcastDelta)
|
|
ht.MineEmptyBlocks(int(numBlocks - blocksMined))
|
|
|
|
// If we check Bob's pending channel report, it should show that he has
|
|
// a single HTLC that's now in the second stage, as it skipped the
|
|
// initial first stage since this is a direct HTLC.
|
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 2)
|
|
|
|
// We need to generate an additional block to expire the CSV 1.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// For script-enforced leased channels, Bob has failed to sweep his
|
|
// anchor output before, so it's still pending.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
} else {
|
|
// Bob should have a pending sweep request.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
}
|
|
|
|
// Mine a block to trigger the sweeper to sweep it.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Bob's sweeping transaction should now be found in the mempool at
|
|
// this point.
|
|
sweepTx := ht.AssertNumTxsInMempool(1)[0]
|
|
|
|
// If we mine an additional block, then this should confirm Bob's
|
|
// transaction which sweeps the direct HTLC output.
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, sweepTx)
|
|
|
|
// Now that the sweeping transaction has been confirmed, Bob should
|
|
// cancel back that HTLC. As a result, Alice should not know of any
|
|
// active HTLC's.
|
|
ht.AssertNumActiveHtlcs(alice, 0)
|
|
|
|
// Now we'll check Bob's pending channel report. Since this was Carol's
|
|
// commitment, he doesn't have to wait for any CSV delays, but he may
|
|
// still need to wait for a CLTV on his commit output to expire
|
|
// depending on the commitment type.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
resp := bob.RPC.PendingChannels()
|
|
|
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
|
|
numBlocks := int(forceCloseChan.BlocksTilMaturity)
|
|
ht.MineEmptyBlocks(numBlocks)
|
|
|
|
// Assert the commit output has been offered to the sweeper.
|
|
// Bob should have two pending sweep requests - one for the
|
|
// commit output and one for the anchor output.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3}
|
|
bobCommitSweep := ht.AssertOutpointInMempool(
|
|
bobCommitOutpoint,
|
|
)
|
|
bobCommitSweepTxid := bobCommitSweep.TxHash()
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &bobCommitSweepTxid)
|
|
}
|
|
ht.AssertNumPendingForceClose(bob, 0)
|
|
|
|
// While we're here, we assert that our expired invoice's state is
|
|
// correctly updated, and can no longer be settled.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_CANCELED)
|
|
|
|
// We'll close out the test by closing the channel from Alice to Bob,
|
|
// and then shutting down the new node we created as its no longer
|
|
// needed. Coop close, no anchors.
|
|
ht.CloseChannel(alice, aliceChanPoint)
|
|
}
|
|
|
|
// testMultiHopHtlcLocalChainClaim tests that in a multi-hop HTLC scenario, if
|
|
// we force close a channel with an incoming HTLC, and later find out the
|
|
// preimage via the witness beacon, we properly settle the HTLC on-chain using
|
|
// the HTLC success transaction in order to ensure we don't lose any funds.
|
|
func testMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcLocalChainClaim)
|
|
}
|
|
|
|
func runMultiHopHtlcLocalChainClaim(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
// For neutrino backend, we need to fund one more UTXO for Carol so she
|
|
// can sweep her outputs.
|
|
if ht.IsNeutrinoBackend() {
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
}
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
// won't send out the outgoing htlc.
|
|
const invoiceAmt = 100000
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: invoiceAmt,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
// will not immediately settle the payment.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// At this point, all 3 nodes should now have an active channel with
|
|
// the created HTLC pending on all of them.
|
|
ht.AssertActiveHtlcs(alice, payHash[:])
|
|
ht.AssertActiveHtlcs(bob, payHash[:])
|
|
ht.AssertActiveHtlcs(carol, payHash[:])
|
|
|
|
// Wait for carol to mark invoice as accepted. There is a small gap to
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
// hop logic.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// blocksMined records how many blocks have mined after the creation of
|
|
// the invoice so it can be used to calculate how many more blocks need
|
|
// to be mined to trigger a force close later on.
|
|
var blocksMined uint32
|
|
|
|
// At this point, Bob decides that he wants to exit the channel
|
|
// immediately, so he force closes his commitment transaction.
|
|
closeStream, _ := ht.CloseChannelAssertPending(
|
|
bob, aliceChanPoint, true,
|
|
)
|
|
|
|
// For anchor channels, the anchor won't be used for CPFP as there's no
|
|
// deadline pressure for Bob on the channel Alice->Bob at the moment.
|
|
// For Bob's local commitment tx, there's only one incoming HTLC which
|
|
// he doesn't have the preimage yet. Thus this anchor won't be
|
|
// force-swept.
|
|
hasAnchorSweep := false
|
|
bobForceClose := ht.AssertStreamChannelForceClosed(
|
|
bob, aliceChanPoint, hasAnchorSweep, closeStream,
|
|
)
|
|
|
|
// Increase the blocks mined. At this step
|
|
// AssertStreamChannelForceClosed mines one block.
|
|
blocksMined++
|
|
|
|
var expectedTxes int
|
|
switch c {
|
|
// Alice will sweep her commitment and anchor output immediately. Bob
|
|
// will also offer his anchor to his sweeper.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
ht.AssertNumPendingSweeps(alice, 2)
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// We expect to see only one sweeping tx to be published from
|
|
// Alice, which sweeps her commit and anchor outputs in the
|
|
// same tx. For Bob, since his anchor is not used for CPFP,
|
|
// it'd be uneconomical to sweep so it will fail.
|
|
expectedTxes = 1
|
|
|
|
// Alice will offer her anchor output to her sweeper. Her commitment
|
|
// output cannot be swept yet as it has incurred an additional CLTV due
|
|
// to being the initiator of a script-enforced leased channel.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
ht.AssertNumPendingSweeps(alice, 1)
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// We expect to see only no sweeping txns to be published,
|
|
// neither Alice's or Bob's anchor sweep can succeed due to
|
|
// it's uneconomical.
|
|
expectedTxes = 0
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Mine a block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
blocksMined++
|
|
|
|
// Assert the expected num of txns are found in the mempool.
|
|
ht.AssertNumTxsInMempool(expectedTxes)
|
|
|
|
// Mine a block to clean up the mempool for the rest of the test.
|
|
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
blocksMined++
|
|
|
|
// Suspend Bob to force Carol to go to chain.
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Settle invoice. This will just mark the invoice as settled, as there
|
|
// is no link anymore to remove the htlc from the commitment tx. For
|
|
// this test, it is important to actually settle and not leave the
|
|
// invoice in the accepted state, because without a known preimage, the
|
|
// channel arbitrator won't go to chain.
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
// We now advance the block height to the point where Carol will force
|
|
// close her channel with Bob, broadcast the closing tx but keep it
|
|
// unconfirmed.
|
|
numBlocks := padCLTV(uint32(invoiceReq.CltvExpiry -
|
|
lncfg.DefaultIncomingBroadcastDelta))
|
|
ht.MineEmptyBlocks(int(numBlocks - blocksMined))
|
|
|
|
// Carol's commitment transaction should now be in the mempool.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// Look up the closing transaction. It should be spending from the
|
|
// funding transaction,
|
|
closingTx := ht.AssertOutpointInMempool(
|
|
ht.OutPointFromChannelPoint(bobChanPoint),
|
|
)
|
|
closingTxid := closingTx.TxHash()
|
|
|
|
// Mine a block that should confirm the commit tx.
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &closingTxid)
|
|
|
|
// After the force close transaction is mined, Carol should offer her
|
|
// second-level success HTLC tx and anchor to the sweeper.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Restart bob again.
|
|
require.NoError(ht, restartBob())
|
|
|
|
// Lower the fee rate so Bob's two anchor outputs are economical to
|
|
// be swept in one tx.
|
|
ht.SetFeeEstimate(chainfee.FeePerKwFloor)
|
|
|
|
// After the force close transaction is mined, transactions will be
|
|
// broadcast by both Bob and Carol.
|
|
switch c {
|
|
// Carol will broadcast her sweeping txns and Bob will sweep his
|
|
// commitment and anchor outputs, we'd expect to see three txns,
|
|
// - Carol's second level HTLC transaction.
|
|
// - Carol's anchor sweeping txns since it's used for CPFP.
|
|
// - Bob's sweep tx spending his commitment output, and two anchor
|
|
// outputs, one from channel Alice to Bob and the other from channel
|
|
// Bob to Carol.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
ht.AssertNumPendingSweeps(bob, 3)
|
|
expectedTxes = 3
|
|
|
|
// Carol will broadcast her sweeping txns and Bob will sweep his
|
|
// anchor outputs. Bob can't sweep his commitment output yet as it has
|
|
// incurred an additional CLTV due to being the initiator of a
|
|
// script-enforced leased channel:
|
|
// - Carol's second level HTLC transaction.
|
|
// - Carol's anchor sweeping txns since it's used for CPFP.
|
|
// - Bob's sweep tx spending his two anchor outputs, one from channel
|
|
// Alice to Bob and the other from channel Bob to Carol.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
expectedTxes = 3
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Mine a block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Assert transactions can be found in the mempool.
|
|
ht.AssertNumTxsInMempool(expectedTxes)
|
|
|
|
// At this point we suspend Alice to make sure she'll handle the
|
|
// on-chain settle after a restart.
|
|
restartAlice := ht.SuspendNode(alice)
|
|
|
|
// Mine a block to confirm the expected transactions (+ the coinbase).
|
|
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
|
|
// For a channel of the anchor type, we will subtract one block
|
|
// from the default CSV, as the Sweeper will handle the input, and the
|
|
// Sweeper sweeps the input as soon as the lock expires.
|
|
secondLevelMaturity := uint32(defaultCSV - 1)
|
|
|
|
// Keep track of the second level tx maturity.
|
|
carolSecondLevelCSV := secondLevelMaturity
|
|
|
|
// When Bob notices Carol's second level transaction in the block, he
|
|
// will extract the preimage and broadcast a second level tx to claim
|
|
// the HTLC in his (already closed) channel with Alice.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to trigger the sweep of the second level tx.
|
|
ht.MineEmptyBlocks(1)
|
|
carolSecondLevelCSV--
|
|
|
|
// Check Bob's second level tx.
|
|
bobSecondLvlTx := ht.GetNumTxsFromMempool(1)[0]
|
|
|
|
// It should spend from the commitment in the channel with Alice.
|
|
ht.AssertTxSpendFrom(bobSecondLvlTx, *bobForceClose)
|
|
|
|
// At this point, Bob should have broadcast his second layer success
|
|
// transaction, and should have sent it to the nursery for incubation.
|
|
ht.AssertNumHTLCsAndStage(bob, aliceChanPoint, 1, 1)
|
|
|
|
// The channel between Bob and Carol will still be pending force close
|
|
// if this is a leased channel. In that case, we'd also check the HTLC
|
|
// stages are correct in that channel.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
ht.AssertNumPendingForceClose(bob, 2)
|
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, 1, 1)
|
|
} else {
|
|
ht.AssertNumPendingForceClose(bob, 1)
|
|
}
|
|
|
|
// We'll now mine a block which should confirm Bob's second layer
|
|
// transaction.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Keep track of Bob's second level maturity, and decrement our track
|
|
// of Carol's.
|
|
bobSecondLevelCSV := secondLevelMaturity
|
|
carolSecondLevelCSV--
|
|
|
|
// Now that the preimage from Bob has hit the chain, restart Alice to
|
|
// ensure she'll pick it up.
|
|
require.NoError(ht, restartAlice())
|
|
|
|
// If we then mine 1 additional blocks, Carol's second level tx should
|
|
// mature, and she can pull the funds from it with a sweep tx.
|
|
ht.MineEmptyBlocks(int(carolSecondLevelCSV))
|
|
bobSecondLevelCSV -= carolSecondLevelCSV
|
|
|
|
// Carol should have one a sweep request for her second level tx.
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
bobSecondLevelCSV--
|
|
|
|
// Carol's sweep tx should be broadcast.
|
|
carolSweep := ht.AssertNumTxsInMempool(1)[0]
|
|
|
|
// Bob should offer his second level tx to his sweeper.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mining one additional block, Bob's second level tx is mature, and he
|
|
// can sweep the output.
|
|
block = ht.MineBlocksAndAssertNumTxes(bobSecondLevelCSV, 1)[0]
|
|
ht.AssertTxInBlock(block, carolSweep)
|
|
|
|
bobSweep := ht.GetNumTxsFromMempool(1)[0]
|
|
bobSweepTxid := bobSweep.TxHash()
|
|
|
|
// When we mine one additional block, that will confirm Bob's sweep.
|
|
// Now Bob should have no pending channels anymore, as this just
|
|
// resolved it by the confirmation of the sweep transaction.
|
|
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &bobSweepTxid)
|
|
|
|
// With the script-enforced lease commitment type, Alice and Bob still
|
|
// haven't been able to sweep their respective commit outputs due to the
|
|
// additional CLTV. We'll need to mine enough blocks for the timelock to
|
|
// expire and prompt their sweep.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
for _, node := range []*node.HarnessNode{alice, bob} {
|
|
ht.AssertNumPendingForceClose(node, 1)
|
|
}
|
|
|
|
// Due to the way the test is set up, Alice and Bob share the
|
|
// same CLTV for their commit outputs even though it's enforced
|
|
// on different channels (Alice-Bob and Bob-Carol).
|
|
resp := alice.RPC.PendingChannels()
|
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
|
|
// Mine enough blocks for the timelock to expire.
|
|
numBlocks := uint32(forceCloseChan.BlocksTilMaturity)
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// Both Alice and Bob should now offer their commit outputs to
|
|
// the sweeper. For Alice, she still has her anchor output as
|
|
// pending sweep as it's not used for CPFP, thus it's
|
|
// uneconomical to sweep it alone.
|
|
ht.AssertNumPendingSweeps(alice, 2)
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Both Alice and Bob show broadcast their commit sweeps.
|
|
aliceCommitOutpoint := wire.OutPoint{
|
|
Hash: *bobForceClose, Index: 3,
|
|
}
|
|
ht.AssertOutpointInMempool(
|
|
aliceCommitOutpoint,
|
|
).TxHash()
|
|
bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
|
|
ht.AssertOutpointInMempool(
|
|
bobCommitOutpoint,
|
|
).TxHash()
|
|
|
|
// Confirm their sweeps.
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
}
|
|
|
|
// All nodes should show zero pending and open channels.
|
|
for _, node := range []*node.HarnessNode{alice, bob, carol} {
|
|
ht.AssertNumPendingForceClose(node, 0)
|
|
ht.AssertNodeNumChannels(node, 0)
|
|
}
|
|
|
|
// Finally, check that the Alice's payment is correctly marked
|
|
// succeeded.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
}
|
|
|
|
// testMultiHopHtlcRemoteChainClaim tests that in the multi-hop HTLC scenario,
|
|
// if the remote party goes to chain while we have an incoming HTLC, then when
|
|
// we found out the preimage via the witness beacon, we properly settle the
|
|
// HTLC directly on-chain using the preimage in order to ensure that we don't
|
|
// lose any funds.
|
|
func testMultiHopHtlcRemoteChainClaim(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcRemoteChainClaim)
|
|
}
|
|
|
|
func runMultiHopHtlcRemoteChainClaim(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
// won't send out the outgoing htlc.
|
|
const invoiceAmt = 100000
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: invoiceAmt,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
// will not immediately settle the payment.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// At this point, all 3 nodes should now have an active channel with
|
|
// the created HTLC pending on all of them.
|
|
ht.AssertActiveHtlcs(alice, payHash[:])
|
|
ht.AssertActiveHtlcs(bob, payHash[:])
|
|
ht.AssertActiveHtlcs(carol, payHash[:])
|
|
|
|
// Wait for carol to mark invoice as accepted. There is a small gap to
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
// hop logic.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// blocksMined records how many blocks have mined after the creation of
|
|
// the invoice so it can be used to calculate how many more blocks need
|
|
// to be mined to trigger a force close later on.
|
|
var blocksMined int
|
|
|
|
// Lower the fee rate so Bob's two anchor outputs are economical to
|
|
// be swept in one tx.
|
|
ht.SetFeeEstimate(chainfee.FeePerKwFloor)
|
|
|
|
// Next, Alice decides that she wants to exit the channel, so she'll
|
|
// immediately force close the channel by broadcast her commitment
|
|
// transaction.
|
|
closeStream, _ := ht.CloseChannelAssertPending(
|
|
alice, aliceChanPoint, true,
|
|
)
|
|
aliceForceClose := ht.AssertStreamChannelForceClosed(
|
|
alice, aliceChanPoint, true, closeStream,
|
|
)
|
|
|
|
// Increase the blocks mined. At this step
|
|
// AssertStreamChannelForceClosed mines one block.
|
|
blocksMined++
|
|
|
|
// Wait for the channel to be marked pending force close.
|
|
ht.AssertChannelPendingForceClose(alice, aliceChanPoint)
|
|
|
|
// After AssertStreamChannelForceClosed returns, it has mined a block
|
|
// so now bob will attempt to redeem his anchor output. Check the
|
|
// anchor is offered to the sweeper.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
ht.AssertNumPendingSweeps(alice, 1)
|
|
|
|
// Mine enough blocks for Alice to sweep her funds from the force
|
|
// closed channel. AssertStreamChannelForceClosed() already mined a
|
|
// block containing the commitment tx and the commit sweep tx will be
|
|
// broadcast immediately before it can be included in a block, so mine
|
|
// one less than defaultCSV in order to perform mempool assertions.
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
ht.MineEmptyBlocks(defaultCSV - blocksMined)
|
|
blocksMined = defaultCSV
|
|
|
|
// Alice should now sweep her funds.
|
|
ht.AssertNumPendingSweeps(alice, 2)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
blocksMined++
|
|
|
|
// Mine Alice's commit sweeping tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
blocksMined++
|
|
}
|
|
|
|
// Suspend bob, so Carol is forced to go on chain.
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Settle invoice. This will just mark the invoice as settled, as there
|
|
// is no link anymore to remove the htlc from the commitment tx. For
|
|
// this test, it is important to actually settle and not leave the
|
|
// invoice in the accepted state, because without a known preimage, the
|
|
// channel arbitrator won't go to chain.
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
// We'll now mine enough blocks so Carol decides that she needs to go
|
|
// on-chain to claim the HTLC as Bob has been inactive.
|
|
numBlocks := padCLTV(uint32(
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
|
))
|
|
ht.MineEmptyBlocks(int(numBlocks) - blocksMined)
|
|
|
|
// Carol's commitment transaction should now be in the mempool.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// The closing transaction should be spending from the funding
|
|
// transaction.
|
|
closingTx := ht.AssertOutpointInMempool(
|
|
ht.OutPointFromChannelPoint(bobChanPoint),
|
|
)
|
|
closingTxid := closingTx.TxHash()
|
|
|
|
// Since Carol has time-sensitive HTLCs, she will use the anchor for
|
|
// CPFP purpose. Assert she has two pending anchor sweep requests - one
|
|
// from local commit and the other from remote commit.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Mine a block, which should contain: the commitment.
|
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &closingTxid)
|
|
|
|
// After the force close transaction is mined, Carol should offer her
|
|
// second level HTLC tx to the sweeper, along with her anchor output.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Restart bob again.
|
|
require.NoError(ht, restartBob())
|
|
|
|
// After the force close transaction is mined, we should expect Bob and
|
|
// Carol to broadcast some transactions depending on the channel
|
|
// commitment type.
|
|
switch c {
|
|
// Carol should broadcast her second level HTLC transaction and Bob
|
|
// should broadcast a sweeping tx to sweep his commitment output and
|
|
// anchor outputs from the two channels.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
ht.AssertNumPendingSweeps(bob, 3)
|
|
|
|
// Carol should broadcast her second level HTLC transaction and Bob
|
|
// should broadcast a transaction to sweep his anchor outputs. Bob
|
|
// can't sweep his commitment output yet as he has incurred an
|
|
// additional CLTV due to being the channel initiator of a force closed
|
|
// script-enforced leased channel.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Keep track of the second level tx maturity.
|
|
carolSecondLevelCSV := uint32(defaultCSV)
|
|
|
|
// Mine a block to trigger the sweeps, also confirms Carol's CPFP
|
|
// anchor sweeping.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
carolSecondLevelCSV--
|
|
ht.AssertNumTxsInMempool(2)
|
|
|
|
// Mine a block to confirm the expected transactions.
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
|
|
// When Bob notices Carol's second level transaction in the block, he
|
|
// will extract the preimage and offer the HTLC to his sweeper.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// NOTE: after Bob is restarted, the sweeping of the direct preimage
|
|
// spent will happen immediately so we don't need to mine a block to
|
|
// trigger Bob's sweeper to sweep it.
|
|
bobHtlcSweep := ht.GetNumTxsFromMempool(1)[0]
|
|
bobHtlcSweepTxid := bobHtlcSweep.TxHash()
|
|
|
|
// It should spend from the commitment in the channel with Alice.
|
|
ht.AssertTxSpendFrom(bobHtlcSweep, *aliceForceClose)
|
|
|
|
// We'll now mine a block which should confirm Bob's HTLC sweep
|
|
// transaction.
|
|
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, &bobHtlcSweepTxid)
|
|
carolSecondLevelCSV--
|
|
|
|
// Now that the sweeping transaction has been confirmed, Bob should now
|
|
// recognize that all contracts for the Bob-Carol channel have been
|
|
// fully resolved
|
|
aliceBobPendingChansLeft := 0
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
aliceBobPendingChansLeft = 1
|
|
}
|
|
for _, node := range []*node.HarnessNode{alice, bob} {
|
|
ht.AssertNumPendingForceClose(
|
|
node, aliceBobPendingChansLeft,
|
|
)
|
|
}
|
|
|
|
// If we then mine 3 additional blocks, Carol's second level tx will
|
|
// mature, and she should pull the funds.
|
|
ht.MineEmptyBlocks(int(carolSecondLevelCSV))
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Mine a block to trigger the sweep of the second level tx.
|
|
ht.MineEmptyBlocks(1)
|
|
carolSweep := ht.AssertNumTxsInMempool(1)[0]
|
|
|
|
// When Carol's sweep gets confirmed, she should have no more pending
|
|
// channels.
|
|
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
|
ht.AssertTxInBlock(block, carolSweep)
|
|
ht.AssertNumPendingForceClose(carol, 0)
|
|
|
|
// With the script-enforced lease commitment type, Alice and Bob still
|
|
// haven't been able to sweep their respective commit outputs due to the
|
|
// additional CLTV. We'll need to mine enough blocks for the timelock to
|
|
// expire and prompt their sweep.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// Due to the way the test is set up, Alice and Bob share the
|
|
// same CLTV for their commit outputs even though it's enforced
|
|
// on different channels (Alice-Bob and Bob-Carol).
|
|
resp := alice.RPC.PendingChannels()
|
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
|
|
// Mine enough blocks for the timelock to expire.
|
|
numBlocks := int(forceCloseChan.BlocksTilMaturity)
|
|
ht.MineEmptyBlocks(numBlocks)
|
|
|
|
// Both Alice and Bob should offer their commit sweeps.
|
|
ht.AssertNumPendingSweeps(alice, 2)
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Both Alice and Bob should broadcast their commit sweeps.
|
|
aliceCommitOutpoint := wire.OutPoint{
|
|
Hash: *aliceForceClose, Index: 3,
|
|
}
|
|
ht.AssertOutpointInMempool(aliceCommitOutpoint)
|
|
bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
|
|
ht.AssertOutpointInMempool(bobCommitOutpoint)
|
|
|
|
// Confirm their sweeps.
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
|
|
// Alice and Bob should not show any pending channels anymore as
|
|
// they have been fully resolved.
|
|
for _, node := range []*node.HarnessNode{alice, bob} {
|
|
ht.AssertNumPendingForceClose(node, 0)
|
|
}
|
|
}
|
|
|
|
// The invoice should show as settled for Carol, indicating that it was
|
|
// swept on-chain.
|
|
invoice := ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED)
|
|
require.Equal(ht, int64(invoiceAmt), invoice.AmtPaidSat)
|
|
|
|
// Finally, check that the Alice's payment is correctly marked
|
|
// succeeded.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
}
|
|
|
|
// testMultiHopHtlcAggregation tests that in a multi-hop HTLC scenario, if we
|
|
// force close a channel with both incoming and outgoing HTLCs, we can properly
|
|
// resolve them using the second level timeout and success transactions. In
|
|
// case of anchor channels, the second-level spends can also be aggregated and
|
|
// properly feebumped, so we'll check that as well.
|
|
func testMultiHopHtlcAggregation(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runMultiHopHtlcAggregation)
|
|
}
|
|
|
|
func runMultiHopHtlcAggregation(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// We need one additional UTXO to create the sweeping tx for the
|
|
// second-level success txes.
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
|
|
|
// First, we'll create a three hop network: Alice -> Bob -> Carol.
|
|
aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice+Carol can actually find a route.
|
|
var (
|
|
carolRouteHints []*lnrpc.RouteHint
|
|
aliceRouteHints []*lnrpc.RouteHint
|
|
)
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
carolRouteHints = makeRouteHints(bob, carol, zeroConf)
|
|
aliceRouteHints = makeRouteHints(bob, alice, zeroConf)
|
|
}
|
|
|
|
// To ensure we have capacity in both directions of the route, we'll
|
|
// make a fairly large payment Alice->Carol and settle it.
|
|
const reBalanceAmt = 500_000
|
|
invoice := &lnrpc.Invoice{
|
|
Value: reBalanceAmt,
|
|
RouteHints: carolRouteHints,
|
|
}
|
|
resp := carol.RPC.AddInvoice(invoice)
|
|
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
|
|
|
|
// Make sure Carol has settled the invoice.
|
|
ht.AssertInvoiceSettled(carol, resp.PaymentAddr)
|
|
|
|
// With the network active, we'll now add a new hodl invoices at both
|
|
// Alice's and Carol's end. Make sure the cltv expiry delta is large
|
|
// enough, otherwise Bob won't send out the outgoing htlc.
|
|
const numInvoices = 5
|
|
const invoiceAmt = 50_000
|
|
|
|
var (
|
|
carolInvoices []*invoicesrpc.AddHoldInvoiceResp
|
|
aliceInvoices []*invoicesrpc.AddHoldInvoiceResp
|
|
alicePreimages []lntypes.Preimage
|
|
payHashes [][]byte
|
|
invoiceStreamsCarol []rpc.SingleInvoiceClient
|
|
invoiceStreamsAlice []rpc.SingleInvoiceClient
|
|
)
|
|
|
|
// Add Carol invoices.
|
|
for i := 0; i < numInvoices; i++ {
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: invoiceAmt,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: carolRouteHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
carolInvoices = append(carolInvoices, carolInvoice)
|
|
payHashes = append(payHashes, payHash[:])
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
invoiceStreamsCarol = append(invoiceStreamsCarol, stream)
|
|
}
|
|
|
|
// We'll give Alice's invoices a longer CLTV expiry, to ensure the
|
|
// channel Bob<->Carol will be closed first.
|
|
for i := 0; i < numInvoices; i++ {
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], ht.Random32Bytes())
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: invoiceAmt,
|
|
CltvExpiry: thawHeightDelta - 4,
|
|
Hash: payHash[:],
|
|
RouteHints: aliceRouteHints,
|
|
}
|
|
aliceInvoice := alice.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
aliceInvoices = append(aliceInvoices, aliceInvoice)
|
|
alicePreimages = append(alicePreimages, preimage)
|
|
payHashes = append(payHashes, payHash[:])
|
|
|
|
// Subscribe the invoice.
|
|
stream := alice.RPC.SubscribeSingleInvoice(payHash[:])
|
|
invoiceStreamsAlice = append(invoiceStreamsAlice, stream)
|
|
}
|
|
|
|
// Now that we've created the invoices, we'll pay them all from
|
|
// Alice<->Carol, going through Bob. We won't wait for the response
|
|
// however, as neither will immediately settle the payment.
|
|
|
|
// Alice will pay all of Carol's invoices.
|
|
for _, carolInvoice := range carolInvoices {
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
}
|
|
|
|
// And Carol will pay Alice's.
|
|
for _, aliceInvoice := range aliceInvoices {
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: aliceInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
carol.RPC.SendPayment(req)
|
|
}
|
|
|
|
// At this point, all 3 nodes should now the HTLCs active on their
|
|
// channels.
|
|
ht.AssertActiveHtlcs(alice, payHashes...)
|
|
ht.AssertActiveHtlcs(bob, payHashes...)
|
|
ht.AssertActiveHtlcs(carol, payHashes...)
|
|
|
|
// Wait for Alice and Carol to mark the invoices as accepted. There is
|
|
// a small gap to bridge between adding the htlc to the channel and
|
|
// executing the exit hop logic.
|
|
for _, stream := range invoiceStreamsCarol {
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
}
|
|
|
|
for _, stream := range invoiceStreamsAlice {
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
}
|
|
|
|
// Increase the fee estimate so that the following force close tx will
|
|
// be cpfp'ed.
|
|
ht.SetFeeEstimate(30000)
|
|
|
|
// We want Carol's htlcs to expire off-chain to demonstrate bob's force
|
|
// close. However, Carol will cancel her invoices to prevent force
|
|
// closes, so we shut her down for now.
|
|
restartCarol := ht.SuspendNode(carol)
|
|
|
|
// We'll now mine enough blocks to trigger Bob's broadcast of his
|
|
// commitment transaction due to the fact that the Carol's HTLCs are
|
|
// 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),
|
|
)
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// Bob's force close transaction should now be found in the mempool. If
|
|
// there are anchors, we expect it to be offered to Bob's sweeper.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// Bob has two anchor sweep requests, one for remote (invalid) and the
|
|
// other for local.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
closeTx := ht.AssertOutpointInMempool(
|
|
ht.OutPointFromChannelPoint(bobChanPoint),
|
|
)
|
|
closeTxid := closeTx.TxHash()
|
|
|
|
// Go through the closing transaction outputs, and make an index for
|
|
// the HTLC outputs.
|
|
successOuts := make(map[wire.OutPoint]struct{})
|
|
timeoutOuts := make(map[wire.OutPoint]struct{})
|
|
for i, txOut := range closeTx.TxOut {
|
|
op := wire.OutPoint{
|
|
Hash: closeTxid,
|
|
Index: uint32(i),
|
|
}
|
|
|
|
switch txOut.Value {
|
|
// If this HTLC goes towards Carol, Bob will claim it with a
|
|
// timeout Tx. In this case the value will be the invoice
|
|
// amount.
|
|
case invoiceAmt:
|
|
timeoutOuts[op] = struct{}{}
|
|
|
|
// If the HTLC has direction towards Alice, Bob will claim it
|
|
// with the success TX when he learns the preimage. In this
|
|
// case one extra sat will be on the output, because of the
|
|
// routing fee.
|
|
case invoiceAmt + 1:
|
|
successOuts[op] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Once bob has force closed, we can restart carol.
|
|
require.NoError(ht, restartCarol())
|
|
|
|
// Mine a block to confirm the closing transaction.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// The above mined block will trigger Bob to sweep his anchor output.
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// Let Alice settle her invoices. When Bob now gets the preimages, he
|
|
// has no other option than to broadcast his second-level transactions
|
|
// to claim the money.
|
|
for _, preimage := range alicePreimages {
|
|
alice.RPC.SettleInvoice(preimage[:])
|
|
}
|
|
|
|
expectedTxes := 0
|
|
switch c {
|
|
// In case of anchors, all success transactions will be aggregated into
|
|
// one, the same is the case for the timeout transactions. In this case
|
|
// Carol will also sweep her commitment and anchor output in a single
|
|
// tx.
|
|
case lnrpc.CommitmentType_ANCHORS,
|
|
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
|
lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
|
|
// Bob should have `numInvoices` for both HTLC success and
|
|
// timeout txns, plus one anchor sweep.
|
|
ht.AssertNumPendingSweeps(bob, numInvoices*2+1)
|
|
|
|
// Carol should have commit and anchor outputs.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// We expect to see three sweeping txns:
|
|
// 1. Bob's sweeping tx for all timeout HTLCs.
|
|
// 2. Bob's sweeping tx for all success HTLCs.
|
|
// 3. Carol's sweeping tx for her commit and anchor outputs.
|
|
expectedTxes = 3
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Mine a block to confirm Bob's anchor sweeping, which will also
|
|
// trigger his sweeper to sweep HTLCs.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Assert the sweeping txns are found in the mempool.
|
|
txes := ht.GetNumTxsFromMempool(expectedTxes)
|
|
|
|
// Since Bob can aggregate the transactions, we expect a single
|
|
// transaction, that have multiple spends from the commitment.
|
|
var (
|
|
timeoutTxs []*chainhash.Hash
|
|
successTxs []*chainhash.Hash
|
|
)
|
|
for _, tx := range txes {
|
|
txid := tx.TxHash()
|
|
|
|
for i := range tx.TxIn {
|
|
prevOp := tx.TxIn[i].PreviousOutPoint
|
|
if _, ok := successOuts[prevOp]; ok {
|
|
successTxs = append(successTxs, &txid)
|
|
|
|
break
|
|
}
|
|
|
|
if _, ok := timeoutOuts[prevOp]; ok {
|
|
timeoutTxs = append(timeoutTxs, &txid)
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// In case of anchor we expect all the timeout and success second
|
|
// levels to be aggregated into one tx. For earlier channel types, they
|
|
// will be separate transactions.
|
|
if lntest.CommitTypeHasAnchors(c) {
|
|
require.Len(ht, timeoutTxs, 1)
|
|
require.Len(ht, successTxs, 1)
|
|
} else {
|
|
require.Len(ht, timeoutTxs, numInvoices)
|
|
require.Len(ht, successTxs, numInvoices)
|
|
}
|
|
|
|
// All mempool transactions should be spending from the commitment
|
|
// transaction.
|
|
ht.AssertAllTxesSpendFrom(txes, closeTxid)
|
|
|
|
// Mine a block to confirm the all the transactions, including Carol's
|
|
// commitment tx, anchor tx(optional), and Bob's second-level timeout
|
|
// and success txes.
|
|
ht.MineBlocksAndAssertNumTxes(1, expectedTxes)
|
|
|
|
// At this point, Bob should have broadcast his second layer success
|
|
// transaction, and should have sent it to the nursery for incubation,
|
|
// or to the sweeper for sweeping.
|
|
forceCloseChan := ht.AssertNumPendingForceClose(bob, 1)[0]
|
|
ht.Logf("Bob's timelock on commit=%v, timelock on htlc=%v",
|
|
forceCloseChan.BlocksTilMaturity,
|
|
forceCloseChan.PendingHtlcs[0].BlocksTilMaturity)
|
|
|
|
// For this channel, we also check the number of HTLCs and the stage
|
|
// are correct.
|
|
ht.AssertNumHTLCsAndStage(bob, bobChanPoint, numInvoices*2, 2)
|
|
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// If we then mine additional blocks, Bob can sweep his
|
|
// commitment output.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Assert the tx has been offered to the sweeper.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine one block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Find the commitment sweep.
|
|
bobCommitSweep := ht.GetNumTxsFromMempool(1)[0]
|
|
ht.AssertTxSpendFrom(bobCommitSweep, closeTxid)
|
|
|
|
// Also ensure it is not spending from any of the HTLC output.
|
|
for _, txin := range bobCommitSweep.TxIn {
|
|
for _, timeoutTx := range timeoutTxs {
|
|
require.NotEqual(ht, *timeoutTx,
|
|
txin.PreviousOutPoint.Hash,
|
|
"found unexpected spend of timeout tx")
|
|
}
|
|
|
|
for _, successTx := range successTxs {
|
|
require.NotEqual(ht, *successTx,
|
|
txin.PreviousOutPoint.Hash,
|
|
"found unexpected spend of success tx")
|
|
}
|
|
}
|
|
}
|
|
|
|
switch c {
|
|
// Mining one additional block, Bob's second level tx is mature, and he
|
|
// can sweep the output. Before the blocks are mined, we should expect
|
|
// to see Bob's commit sweep in the mempool.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
|
|
// Since Bob is the initiator of the Bob-Carol script-enforced leased
|
|
// channel, he incurs an additional CLTV when sweeping outputs back to
|
|
// his wallet. We'll need to mine enough blocks for the timelock to
|
|
// expire to prompt his broadcast.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
resp := bob.RPC.PendingChannels()
|
|
require.Len(ht, resp.PendingForceClosingChannels, 1)
|
|
forceCloseChan := resp.PendingForceClosingChannels[0]
|
|
require.Positive(ht, forceCloseChan.BlocksTilMaturity)
|
|
numBlocks := uint32(forceCloseChan.BlocksTilMaturity)
|
|
|
|
// Add debug log.
|
|
height := ht.CurrentHeight()
|
|
bob.AddToLogf("itest: now mine %d blocks at height %d",
|
|
numBlocks, height)
|
|
ht.MineEmptyBlocks(int(numBlocks) - 1)
|
|
|
|
default:
|
|
ht.Fatalf("unhandled commitment type %v", c)
|
|
}
|
|
|
|
// Make sure Bob's sweeper has received all the sweeping requests.
|
|
ht.AssertNumPendingSweeps(bob, numInvoices*2)
|
|
|
|
// Mine one block to trigger the sweeps.
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// For leased channels, Bob's commit output will mature after the above
|
|
// block.
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
ht.AssertNumPendingSweeps(bob, numInvoices*2+1)
|
|
}
|
|
|
|
// We now wait for 30 seconds to overcome the flake - there's a block
|
|
// race between contractcourt and sweeper, causing the sweep to be
|
|
// broadcast earlier.
|
|
//
|
|
// TODO(yy): remove this once `blockbeat` is in place.
|
|
numExpected := 1
|
|
err := wait.NoError(func() error {
|
|
mem := ht.GetRawMempool()
|
|
if len(mem) == numExpected {
|
|
return nil
|
|
}
|
|
|
|
if len(mem) > 0 {
|
|
numExpected = len(mem)
|
|
}
|
|
|
|
return fmt.Errorf("want %d, got %v in mempool: %v", numExpected,
|
|
len(mem), mem)
|
|
}, wait.DefaultTimeout)
|
|
ht.Logf("Checking mempool got: %v", err)
|
|
|
|
// Make sure it spends from the second level tx.
|
|
secondLevelSweep := ht.GetNumTxsFromMempool(numExpected)[0]
|
|
bobSweep := secondLevelSweep.TxHash()
|
|
|
|
// It should be sweeping all the second-level outputs.
|
|
var secondLvlSpends int
|
|
for _, txin := range secondLevelSweep.TxIn {
|
|
for _, timeoutTx := range timeoutTxs {
|
|
if *timeoutTx == txin.PreviousOutPoint.Hash {
|
|
secondLvlSpends++
|
|
}
|
|
}
|
|
|
|
for _, successTx := range successTxs {
|
|
if *successTx == txin.PreviousOutPoint.Hash {
|
|
secondLvlSpends++
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(yy): bring the following check back when `blockbeat` is in
|
|
// place - atm we may have two sweeping transactions in the mempool.
|
|
// require.Equal(ht, 2*numInvoices, secondLvlSpends)
|
|
|
|
// When we mine one additional block, that will confirm Bob's second
|
|
// level sweep. Now Bob should have no pending channels anymore, as
|
|
// this just resolved it by the confirmation of the sweep transaction.
|
|
block := ht.MineBlocksAndAssertNumTxes(1, numExpected)[0]
|
|
ht.AssertTxInBlock(block, &bobSweep)
|
|
|
|
// For leased channels, we need to mine one more block to confirm Bob's
|
|
// commit output sweep.
|
|
//
|
|
// NOTE: we mine this block conditionally, as the commit output may
|
|
// have already been swept one block earlier due to the race in block
|
|
// consumption among subsystems.
|
|
pendingChanResp := bob.RPC.PendingChannels()
|
|
if len(pendingChanResp.PendingForceClosingChannels) != 0 {
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
}
|
|
ht.AssertNumPendingForceClose(bob, 0)
|
|
|
|
// THe channel with Alice is still open.
|
|
ht.AssertNodeNumChannels(bob, 1)
|
|
|
|
// Carol should have no channels left (open nor pending).
|
|
ht.AssertNumPendingForceClose(carol, 0)
|
|
ht.AssertNodeNumChannels(carol, 0)
|
|
|
|
// Coop close, no anchors.
|
|
ht.CloseChannel(alice, aliceChanPoint)
|
|
}
|
|
|
|
// createThreeHopNetwork creates a topology of `Alice -> Bob -> Carol`.
|
|
func createThreeHopNetwork(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, carolHodl bool, c lnrpc.CommitmentType,
|
|
zeroConf bool) (*lnrpc.ChannelPoint,
|
|
*lnrpc.ChannelPoint, *node.HarnessNode) {
|
|
|
|
ht.EnsureConnected(alice, bob)
|
|
|
|
// We'll create a new node "carol" and have Bob connect to her.
|
|
// If the carolHodl flag is set, we'll make carol always hold onto the
|
|
// HTLC, this way it'll force Bob to go to chain to resolve the HTLC.
|
|
carolFlags := lntest.NodeArgsForCommitType(c)
|
|
if carolHodl {
|
|
carolFlags = append(carolFlags, "--hodl.exit-settle")
|
|
}
|
|
|
|
if zeroConf {
|
|
carolFlags = append(
|
|
carolFlags, "--protocol.option-scid-alias",
|
|
"--protocol.zero-conf",
|
|
)
|
|
}
|
|
carol := ht.NewNode("Carol", carolFlags)
|
|
|
|
ht.ConnectNodes(bob, carol)
|
|
|
|
// Make sure there are enough utxos for anchoring. Because the anchor
|
|
// by itself often doesn't meet the dust limit, a utxo from the wallet
|
|
// needs to be attached as an additional input. This can still lead to
|
|
// a positively-yielding transaction.
|
|
for i := 0; i < 2; i++ {
|
|
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice)
|
|
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob)
|
|
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
|
|
|
|
// Mine 1 block to get the above coins confirmed.
|
|
ht.MineBlocksAndAssertNumTxes(1, 3)
|
|
}
|
|
|
|
// We'll start the test by creating a channel between Alice and Bob,
|
|
// which will act as the first leg for out multi-hop HTLC.
|
|
const chanAmt = 1000000
|
|
var aliceFundingShim *lnrpc.FundingShim
|
|
var thawHeight uint32
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
minerHeight := ht.CurrentHeight()
|
|
thawHeight = minerHeight + thawHeightDelta
|
|
aliceFundingShim, _ = deriveFundingShim(
|
|
ht, alice, bob, chanAmt, thawHeight, true, c,
|
|
)
|
|
}
|
|
|
|
var privateChan bool
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
privateChan = true
|
|
}
|
|
|
|
aliceParams := lntest.OpenChannelParams{
|
|
Private: privateChan,
|
|
Amt: chanAmt,
|
|
CommitmentType: c,
|
|
FundingShim: aliceFundingShim,
|
|
ZeroConf: zeroConf,
|
|
}
|
|
|
|
// If the channel type is taproot, then use an explicit channel type to
|
|
// open it.
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
aliceParams.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
}
|
|
|
|
// We'll create a channel from Bob to Carol. After this channel is
|
|
// open, our topology looks like: A -> B -> C.
|
|
var bobFundingShim *lnrpc.FundingShim
|
|
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
bobFundingShim, _ = deriveFundingShim(
|
|
ht, bob, carol, chanAmt, thawHeight, true, c,
|
|
)
|
|
}
|
|
|
|
// Prepare params for Bob.
|
|
bobParams := lntest.OpenChannelParams{
|
|
Amt: chanAmt,
|
|
Private: privateChan,
|
|
CommitmentType: c,
|
|
FundingShim: bobFundingShim,
|
|
ZeroConf: zeroConf,
|
|
}
|
|
|
|
// If the channel type is taproot, then use an explicit channel type to
|
|
// open it.
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
bobParams.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
|
}
|
|
|
|
var (
|
|
acceptStreamBob rpc.AcceptorClient
|
|
acceptStreamCarol rpc.AcceptorClient
|
|
cancelBob context.CancelFunc
|
|
cancelCarol context.CancelFunc
|
|
)
|
|
|
|
// If a zero-conf channel is being opened, the nodes are signalling the
|
|
// zero-conf feature bit. Setup a ChannelAcceptor for the fundee.
|
|
if zeroConf {
|
|
acceptStreamBob, cancelBob = bob.RPC.ChannelAcceptor()
|
|
go acceptChannel(ht.T, true, acceptStreamBob)
|
|
|
|
acceptStreamCarol, cancelCarol = carol.RPC.ChannelAcceptor()
|
|
go acceptChannel(ht.T, true, acceptStreamCarol)
|
|
}
|
|
|
|
// Open channels in batch to save blocks mined.
|
|
reqs := []*lntest.OpenChannelRequest{
|
|
{Local: alice, Remote: bob, Param: aliceParams},
|
|
{Local: bob, Remote: carol, Param: bobParams},
|
|
}
|
|
resp := ht.OpenMultiChannelsAsync(reqs)
|
|
aliceChanPoint := resp[0]
|
|
bobChanPoint := resp[1]
|
|
|
|
// Make sure alice and carol know each other's channels.
|
|
//
|
|
// We'll only do this though if it wasn't a private channel we opened
|
|
// earlier.
|
|
if !privateChan {
|
|
ht.AssertTopologyChannelOpen(alice, bobChanPoint)
|
|
ht.AssertTopologyChannelOpen(carol, aliceChanPoint)
|
|
} else {
|
|
// Otherwise, we want to wait for all the channels to be shown
|
|
// as active before we proceed.
|
|
ht.AssertChannelExists(alice, aliceChanPoint)
|
|
ht.AssertChannelExists(carol, bobChanPoint)
|
|
}
|
|
|
|
// Remove the ChannelAcceptor for Bob and Carol.
|
|
if zeroConf {
|
|
cancelBob()
|
|
cancelCarol()
|
|
}
|
|
|
|
return aliceChanPoint, bobChanPoint, carol
|
|
}
|
|
|
|
// testHtlcTimeoutResolverExtractPreimageRemote tests that in the multi-hop
|
|
// setting, Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using
|
|
// the 2nd level success tx2nd level success tx, Bob's timeout resolver will
|
|
// extract the preimage from the sweep tx found in mempool or blocks(for
|
|
// neutrino). The 2nd level success tx is broadcast by Carol and spends the
|
|
// outpoint on her commit tx.
|
|
func testHtlcTimeoutResolverExtractPreimageRemote(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runExtraPreimageFromRemoteCommit)
|
|
}
|
|
|
|
// runExtraPreimageFromRemoteCommit checks that Bob's htlc timeout resolver
|
|
// will extract the preimage from the 2nd level success tx broadcast by Carol
|
|
// which spends the htlc output on her commitment tx.
|
|
func runExtraPreimageFromRemoteCommit(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
if ht.IsNeutrinoBackend() {
|
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
}
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
// won't send out the outgoing htlc.
|
|
preimage := ht.RandomPreimage()
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: 100_000,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
eveInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
// will not immediately settle the payment.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: eveInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// Once the payment sent, Alice should have one outgoing HTLC active.
|
|
ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:])
|
|
|
|
// Bob should have two HTLCs active. One incoming HTLC from Alice, and
|
|
// one outgoing to Carol.
|
|
ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:])
|
|
htlc := ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:])
|
|
|
|
// Carol should have one incoming HTLC from Bob.
|
|
ht.AssertIncomingHTLCActive(carol, bobChanPoint, payHash[:])
|
|
|
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
// hop logic.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// Bob now goes offline so the link between Bob and Carol is broken.
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Carol now settles the invoice, since her link with Bob is broken,
|
|
// Bob won't know the preimage.
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
// We'll now mine enough blocks to trigger Carol's broadcast of her
|
|
// commitment transaction due to the fact that the HTLC is about to
|
|
// timeout. With the default incoming broadcast delta of 10, this
|
|
// will be the htlc expiry height minus 10.
|
|
numBlocks := padCLTV(uint32(
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
|
|
))
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// Carol's force close transaction should now be found in the mempool.
|
|
// If there are anchors, we also expect Carol's contractcourt to offer
|
|
// the anchors to her sweeper - one from the local commitment and the
|
|
// other from the remote.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// We now mine a block to confirm Carol's closing transaction, which
|
|
// will trigger her sweeper to sweep her CPFP anchor sweeping.
|
|
ht.MineClosingTx(bobChanPoint)
|
|
|
|
// With the closing transaction confirmed, we should expect Carol's
|
|
// HTLC success transaction to be offered to the sweeper along with her
|
|
// anchor output.
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
|
|
// Mine a block to trigger the sweep, and clean up the anchor sweeping
|
|
// tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
ht.AssertNumTxsInMempool(1)
|
|
|
|
// Restart Bob. Once he finishes syncing the channel state, he should
|
|
// notice the force close from Carol.
|
|
require.NoError(ht, restartBob())
|
|
|
|
// Get the current height to compute number of blocks to mine to
|
|
// trigger the htlc timeout resolver from Bob.
|
|
height := ht.CurrentHeight()
|
|
|
|
// We'll now mine enough blocks to trigger Bob's timeout resolver.
|
|
numBlocks = htlc.ExpirationHeight - height -
|
|
lncfg.DefaultOutgoingBroadcastDelta
|
|
|
|
// We should now have Carol's htlc success tx in the mempool.
|
|
numTxesMempool := 1
|
|
ht.AssertNumTxsInMempool(numTxesMempool)
|
|
|
|
// For neutrino backend, the timeout resolver needs to extract the
|
|
// preimage from the blocks.
|
|
if ht.IsNeutrinoBackend() {
|
|
// Mine a block to confirm Carol's 2nd level success tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
numBlocks--
|
|
}
|
|
|
|
// Mine empty blocks so Carol's htlc success tx stays in mempool. Once
|
|
// the height is reached, Bob's timeout resolver will resolve the htlc
|
|
// by extracing the preimage from the mempool.
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// Finally, check that the Alice's payment is marked as succeeded as
|
|
// Bob has settled the htlc using the preimage extracted from Carol's
|
|
// 2nd level success tx.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
|
|
switch c {
|
|
// For anchor channel type, we should expect to see Bob's commit output
|
|
// and his anchor output be swept in a single tx in the mempool.
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
numTxesMempool++
|
|
|
|
// For script-enforced leased channel, Bob's anchor sweep tx won't
|
|
// happen as it's not used for CPFP, hence no wallet utxo is used so
|
|
// it'll be uneconomical.
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
}
|
|
|
|
// For neutrino backend, Carol's second-stage sweep should be offered
|
|
// to her sweeper.
|
|
if ht.IsNeutrinoBackend() {
|
|
ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
}
|
|
|
|
// Mine a block to clean the mempool.
|
|
ht.MineBlocksAndAssertNumTxes(1, numTxesMempool)
|
|
|
|
// NOTE: for non-standby nodes there's no need to clean up the force
|
|
// close as long as the mempool is cleaned.
|
|
ht.CleanShutDown()
|
|
}
|
|
|
|
// testHtlcTimeoutResolverExtractPreimage tests that in the multi-hop setting,
|
|
// Alice->Bob->Carol, when Bob's outgoing HTLC is swept by Carol using the
|
|
// direct preimage spend, Bob's timeout resolver will extract the preimage from
|
|
// the sweep tx found in mempool or blocks(for neutrino). The direct spend tx
|
|
// is broadcast by Carol and spends the outpoint on Bob's commit tx.
|
|
func testHtlcTimeoutResolverExtractPreimageLocal(ht *lntest.HarnessTest) {
|
|
runMultiHopHtlcClaimTest(ht, runExtraPreimageFromLocalCommit)
|
|
}
|
|
|
|
// runExtraPreimageFromLocalCommit checks that Bob's htlc timeout resolver will
|
|
// extract the preimage from the direct spend broadcast by Carol which spends
|
|
// the htlc output on Bob's commitment tx.
|
|
func runExtraPreimageFromLocalCommit(ht *lntest.HarnessTest,
|
|
alice, bob *node.HarnessNode, c lnrpc.CommitmentType, zeroConf bool) {
|
|
|
|
// 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(
|
|
ht, alice, bob, false, c, zeroConf,
|
|
)
|
|
|
|
// If this is a taproot channel, then we'll need to make some manual
|
|
// route hints so Alice can actually find a route.
|
|
var routeHints []*lnrpc.RouteHint
|
|
if c == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
routeHints = makeRouteHints(bob, carol, zeroConf)
|
|
}
|
|
|
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
|
// won't send out the outgoing htlc.
|
|
preimage := ht.RandomPreimage()
|
|
payHash := preimage.Hash()
|
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
|
Value: 100_000,
|
|
CltvExpiry: finalCltvDelta,
|
|
Hash: payHash[:],
|
|
RouteHints: routeHints,
|
|
}
|
|
carolInvoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
|
|
|
// Subscribe the invoice.
|
|
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
|
|
|
// Now that we've created the invoice, we'll send a single payment from
|
|
// Alice to Carol. We won't wait for the response however, as Carol
|
|
// will not immediately settle the payment.
|
|
req := &routerrpc.SendPaymentRequest{
|
|
PaymentRequest: carolInvoice.PaymentRequest,
|
|
TimeoutSeconds: 60,
|
|
FeeLimitMsat: noFeeLimitMsat,
|
|
}
|
|
alice.RPC.SendPayment(req)
|
|
|
|
// Once the payment sent, Alice should have one outgoing HTLC active.
|
|
ht.AssertOutgoingHTLCActive(alice, aliceChanPoint, payHash[:])
|
|
|
|
// Bob should have two HTLCs active. One incoming HTLC from Alice, and
|
|
// one outgoing to Carol.
|
|
ht.AssertIncomingHTLCActive(bob, aliceChanPoint, payHash[:])
|
|
htlc := ht.AssertOutgoingHTLCActive(bob, bobChanPoint, payHash[:])
|
|
|
|
// Carol should have one incoming HTLC from Bob.
|
|
ht.AssertIncomingHTLCActive(carol, bobChanPoint, payHash[:])
|
|
|
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
|
// bridge between adding the htlc to the channel and executing the exit
|
|
// hop logic.
|
|
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
|
|
|
|
// Bob now goes offline so the link between Bob and Carol is broken.
|
|
restartBob := ht.SuspendNode(bob)
|
|
|
|
// Carol now settles the invoice, since her link with Bob is broken,
|
|
// Bob won't know the preimage.
|
|
carol.RPC.SettleInvoice(preimage[:])
|
|
|
|
// Stop Carol so it's easier to check the mempool's state since she
|
|
// will broadcast the anchor sweeping once Bob force closes.
|
|
restartCarol := ht.SuspendNode(carol)
|
|
|
|
// Restart Bob to force close the channel.
|
|
require.NoError(ht, restartBob())
|
|
|
|
// Bob force closes the channel, which gets his commitment tx into the
|
|
// mempool.
|
|
ht.CloseChannelAssertPending(bob, bobChanPoint, true)
|
|
|
|
// Bob should now has offered his anchors to his sweeper - both local
|
|
// and remote versions.
|
|
ht.AssertNumPendingSweeps(bob, 2)
|
|
|
|
// Mine Bob's force close tx.
|
|
closeTx := ht.MineClosingTx(bobChanPoint)
|
|
|
|
// Mine Bob's anchor sweeping tx.
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
blocksMined := 1
|
|
|
|
// We'll now mine enough blocks to trigger Carol's sweeping of the htlc
|
|
// via the direct spend. With the default incoming broadcast delta of
|
|
// 10, this will be the htlc expiry height minus 10.
|
|
//
|
|
// NOTE: we need to mine 1 fewer block as we've already mined one to
|
|
// confirm Bob's force close tx.
|
|
numBlocks := padCLTV(uint32(
|
|
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta - 1,
|
|
))
|
|
|
|
// If this is a nont script-enforced channel, Bob will be able to sweep
|
|
// his commit output after 4 blocks.
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
// Mine 3 blocks so the output will be offered to the sweeper.
|
|
ht.MineEmptyBlocks(defaultCSV - blocksMined - 1)
|
|
|
|
// Assert the commit output has been offered to the sweeper.
|
|
ht.AssertNumPendingSweeps(bob, 1)
|
|
|
|
// Mine a block to trigger the sweep.
|
|
ht.MineEmptyBlocks(1)
|
|
blocksMined = defaultCSV
|
|
}
|
|
|
|
// Mine empty blocks so it's easier to check Bob's sweeping txes below.
|
|
ht.MineEmptyBlocks(int(numBlocks) - blocksMined)
|
|
|
|
// With the above blocks mined, we should expect Carol's to offer the
|
|
// htlc output on Bob's commitment to the sweeper.
|
|
//
|
|
// TODO(yy): it's not offered to the sweeper yet, instead, the utxo
|
|
// nursery is creating and broadcasting the sweep tx - we should unify
|
|
// this behavior and offer it to the sweeper.
|
|
// ht.AssertNumPendingSweeps(carol, 1)
|
|
|
|
// Increase the fee rate used by the sweeper so Carol's direct spend tx
|
|
// won't be replaced by Bob's timeout tx.
|
|
ht.SetFeeEstimate(30000)
|
|
|
|
// Restart Carol to sweep the htlc output.
|
|
require.NoError(ht, restartCarol())
|
|
|
|
ht.AssertNumPendingSweeps(carol, 2)
|
|
ht.MineEmptyBlocks(1)
|
|
|
|
// Construct the htlc output on Bob's commitment tx, and decide its
|
|
// index based on the commit type below.
|
|
htlcOutpoint := wire.OutPoint{Hash: closeTx.TxHash()}
|
|
|
|
// Check the current mempool state and we should see,
|
|
// - Carol's direct spend tx.
|
|
// - Bob's local output sweep tx, if this is NOT script enforced lease.
|
|
// - Carol's anchor sweep tx cannot be broadcast as it's uneconomical.
|
|
switch c {
|
|
case lnrpc.CommitmentType_ANCHORS, lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
|
htlcOutpoint.Index = 2
|
|
ht.AssertNumTxsInMempool(2)
|
|
|
|
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
|
|
htlcOutpoint.Index = 2
|
|
ht.AssertNumTxsInMempool(1)
|
|
}
|
|
|
|
// Get the current height to compute number of blocks to mine to
|
|
// trigger the timeout resolver from Bob.
|
|
height := ht.CurrentHeight()
|
|
|
|
// We'll now mine enough blocks to trigger Bob's htlc timeout resolver
|
|
// to act. Once his timeout resolver starts, it will extract the
|
|
// preimage from Carol's direct spend tx found in the mempool.
|
|
numBlocks = htlc.ExpirationHeight - height -
|
|
lncfg.DefaultOutgoingBroadcastDelta
|
|
|
|
// Decrease the fee rate used by the sweeper so Bob's timeout tx will
|
|
// not replace Carol's direct spend tx.
|
|
ht.SetFeeEstimate(1000)
|
|
|
|
// Mine empty blocks so Carol's direct spend tx stays in mempool. Once
|
|
// the height is reached, Bob's timeout resolver will resolve the htlc
|
|
// by extracing the preimage from the mempool.
|
|
ht.MineEmptyBlocks(int(numBlocks))
|
|
|
|
// For neutrino backend, the timeout resolver needs to extract the
|
|
// preimage from the blocks.
|
|
if ht.IsNeutrinoBackend() {
|
|
// Make sure the direct spend tx is still in the mempool.
|
|
ht.AssertOutpointInMempool(htlcOutpoint)
|
|
|
|
// Mine a block to confirm two txns,
|
|
// - Carol's direct spend tx.
|
|
// - Bob's to_local output sweep tx.
|
|
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
|
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
} else {
|
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
}
|
|
}
|
|
|
|
// Finally, check that the Alice's payment is marked as succeeded as
|
|
// Bob has settled the htlc using the preimage extracted from Carol's
|
|
// direct spend tx.
|
|
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
|
|
|
// NOTE: for non-standby nodes there's no need to clean up the force
|
|
// close as long as the mempool is cleaned.
|
|
ht.CleanShutDown()
|
|
}
|