mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
653a8ac55e
This commit replaces `AssertTopologyChannelOpen` with `AssertChannelInGraph`, which asserts a given channel edge is found. `AssertTopologyChannelOpen` only asserts a given edge has been received via the topology subscription, while we need to make sure the channel is in the graph before continuing our tests.
2698 lines
95 KiB
Go
2698 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)
|
|
|
|
// Dust HTLCs are immediately canceled backwards as soon as the local
|
|
// commitment tx is successfully broadcasted to the local mempool.
|
|
ht.AssertActiveHtlcs(alice, payHash)
|
|
|
|
// 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)
|
|
|
|
// 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.AssertChannelInGraph(alice, bobChanPoint)
|
|
ht.AssertChannelInGraph(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()
|
|
}
|