itest: update itests to use new script-enforced lease commitment type

We update several of our integration tests that exercise different
scenarios with regards to the broadcast of a channel commitment
transaction with HTLCs in-flight to use the new commitment type for
channel leases. We do this to ensure we have complete coverage of said
channel commitment type. This required changing several assumptions
throughout the tests based on when we should expect sweeps to happen.
This commit is contained in:
Wilmer Paulino 2021-07-13 15:57:22 -07:00 committed by Olaoluwa Osuntokun
parent e15ad026d4
commit 974fc346cf
No known key found for this signature in database
GPG Key ID: 3BBD59E99B280306
11 changed files with 600 additions and 243 deletions

View File

@ -1529,17 +1529,18 @@ func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error {
}
func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client,
timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash {
timeout time.Duration, inputs ...wire.OutPoint) chainhash.Hash {
tx := getSpendingTxInMempool(t, miner, timeout, chanPoint)
tx := getSpendingTxInMempool(t, miner, timeout, inputs...)
return tx.TxHash()
}
// getSpendingTxInMempool waits for a transaction spending the given outpoint to
// appear in the mempool and returns that tx in full.
func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client,
timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx {
timeout time.Duration, inputs ...wire.OutPoint) *wire.MsgTx {
inputSet := make(map[wire.OutPoint]struct{}, len(inputs))
breakTimeout := time.After(timeout)
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
@ -1559,13 +1560,30 @@ func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client,
for _, txid := range mempool {
tx, err := miner.GetRawTransaction(txid)
require.NoError(t.t, err, "unable to fetch tx")
msgTx := tx.MsgTx()
// Include the inputs again in case they were
// removed in a previous iteration.
for _, input := range inputs {
inputSet[input] = struct{}{}
}
for _, txIn := range msgTx.TxIn {
if txIn.PreviousOutPoint == chanPoint {
return msgTx
input := txIn.PreviousOutPoint
if _, ok := inputSet[input]; ok {
delete(inputSet, input)
}
}
if len(inputSet) > 0 {
// Missing input, check next transaction
// or try again.
continue
}
// Transaction spends all expected inputs,
// return.
return msgTx
}
}
}

View File

@ -14,7 +14,7 @@ import (
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
@ -393,7 +393,7 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
const thawHeight uint32 = 10
const chanSize = funding.MaxBtcFundingAmount
fundingShim1, chanPoint1, _ := deriveFundingShim(
net, t, carol, dave, chanSize, thawHeight, 1, false,
net, t, carol, dave, chanSize, thawHeight, false,
)
_ = openChannelStream(
t, net, carol, dave, lntest.OpenChannelParams{
@ -409,7 +409,7 @@ func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
// do exactly that now. For this one we publish the transaction so we
// can mine it later.
fundingShim2, chanPoint2, _ := deriveFundingShim(
net, t, carol, dave, chanSize, thawHeight, 2, true,
net, t, carol, dave, chanSize, thawHeight, true,
)
// At this point, we'll now carry out the normal basic channel funding
@ -637,17 +637,14 @@ func testChannelFundingPersistence(net *lntest.NetworkHarness, t *harnessTest) {
// keys on both sides.
func deriveFundingShim(net *lntest.NetworkHarness, t *harnessTest,
carol, dave *lntest.HarnessNode, chanSize btcutil.Amount,
thawHeight uint32, keyIndex int32, publish bool) (*lnrpc.FundingShim,
thawHeight uint32, publish bool) (*lnrpc.FundingShim,
*lnrpc.ChannelPoint, *chainhash.Hash) {
ctxb := context.Background()
keyLoc := &signrpc.KeyLocator{
KeyFamily: 9999,
KeyIndex: keyIndex,
}
carolFundingKey, err := carol.WalletKitClient.DeriveKey(ctxb, keyLoc)
keyLoc := &walletrpc.KeyReq{KeyFamily: 9999}
carolFundingKey, err := carol.WalletKitClient.DeriveNextKey(ctxb, keyLoc)
require.NoError(t.t, err)
daveFundingKey, err := dave.WalletKitClient.DeriveKey(ctxb, keyLoc)
daveFundingKey, err := dave.WalletKitClient.DeriveNextKey(ctxb, keyLoc)
require.NoError(t.t, err)
// Now that we have the multi-sig keys for each party, we can manually

View File

@ -187,8 +187,9 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// Bob's force close transaction should now be found in the mempool. If
// there are anchors, we also expect Bob's anchor sweep.
hasAnchors := commitTypeHasAnchors(c)
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
if hasAnchors {
expectedTxes = 2
}
@ -251,19 +252,25 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
require.NoError(t.t, err)
}
switch c {
// With the closing transaction confirmed, we should expect Bob's HTLC
// timeout transactions to be broadcast due to the expiry being reached.
// We will also expect the success transactions, since he learnt the
// preimages from Alice. We also expect Carol to sweep her commitment
// output.
expectedTxes = 2*numInvoices + 1
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 2*numInvoices + 1
// 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 anchor output in a separate tx (since it
// will be low fee).
if c == lnrpc.CommitmentType_ANCHORS {
// Carol will also sweep her commitment and anchor output as separate
// txs (since it will be low fee).
case lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
expectedTxes = 4
default:
t.Fatalf("unhandled commitment type %v", c)
}
txes, err := getNTxsFromMempool(
@ -298,7 +305,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// 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 c == lnrpc.CommitmentType_ANCHORS {
if hasAnchors {
require.Len(t.t, timeoutTxs, 1)
require.Len(t.t, successTxs, 1)
} else {
@ -340,47 +347,75 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
)
require.NoError(t.t, err)
// If we then mine additional blocks, Bob can sweep his commitment
// output.
_, err = net.Miner.Client.Generate(defaultCSV - 2)
require.NoError(t.t, err)
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// If we then mine additional blocks, Bob can sweep his commitment
// output.
_, err = net.Miner.Client.Generate(defaultCSV - 2)
require.NoError(t.t, err)
// Find the commitment sweep.
bobCommitSweepHash, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.NoError(t.t, err)
bobCommitSweep, err := net.Miner.Client.GetRawTransaction(bobCommitSweepHash)
require.NoError(t.t, err)
// Find the commitment sweep.
bobCommitSweepHash, err := waitForTxInMempool(
net.Miner.Client, minerMempoolTimeout,
)
require.NoError(t.t, err)
bobCommitSweep, err := net.Miner.Client.GetRawTransaction(
bobCommitSweepHash,
)
require.NoError(t.t, err)
require.Equal(
t.t, closeTxid, bobCommitSweep.MsgTx().TxIn[0].PreviousOutPoint.Hash,
)
require.Equal(
t.t, closeTxid,
bobCommitSweep.MsgTx().TxIn[0].PreviousOutPoint.Hash,
)
// Also ensure it is not spending from any of the HTLC output.
for _, txin := range bobCommitSweep.MsgTx().TxIn {
for _, timeoutTx := range timeoutTxs {
if *timeoutTx == txin.PreviousOutPoint.Hash {
t.Fatalf("found unexpected spend of timeout tx")
// Also ensure it is not spending from any of the HTLC output.
for _, txin := range bobCommitSweep.MsgTx().TxIn {
for _, timeoutTx := range timeoutTxs {
if *timeoutTx == txin.PreviousOutPoint.Hash {
t.Fatalf("found unexpected spend of " +
"timeout tx")
}
}
}
for _, successTx := range successTxs {
if *successTx == txin.PreviousOutPoint.Hash {
t.Fatalf("found unexpected spend of success tx")
for _, successTx := range successTxs {
if *successTx == txin.PreviousOutPoint.Hash {
t.Fatalf("found unexpected spend of " +
"success tx")
}
}
}
}
switch {
switch c {
// In case this is a non-anchor channel type, we must mine 2 blocks, as
// the nursery waits an extra block before sweeping. Before the blocks
// are mined, we should expect to see Bob's commit sweep in the mempool.
case lnrpc.CommitmentType_LEGACY:
_ = mineBlocks(t, net, 2, 1)
// Mining one additional block, Bob's second level tx is mature, and he
// can sweep the output.
case c == lnrpc.CommitmentType_ANCHORS:
// 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:
_ = mineBlocks(t, net, 1, 1)
// In case this is a non-anchor channel type, we must mine 2 blocks, as
// the nursery waits an extra block before sweeping.
// 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:
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
resp, err := bob.PendingChannels(
ctxt, &lnrpc.PendingChannelsRequest{},
)
require.NoError(t.t, err)
require.Len(t.t, resp.PendingForceClosingChannels, 1)
forceCloseChan := resp.PendingForceClosingChannels[0]
require.Positive(t.t, forceCloseChan.BlocksTilMaturity)
_ = mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity), 0)
default:
_ = mineBlocks(t, net, 2, 1)
t.Fatalf("unhandled commitment type %v", c)
}
bobSweep, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)

View File

@ -85,16 +85,29 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// At this point, Bob decides that he wants to exit the channel
// immediately, so he force closes his commitment transaction.
hasAnchors := commitTypeHasAnchors(c)
bobForceClose := closeChannelAndAssertType(
t, net, bob, aliceChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
t, net, bob, aliceChanPoint, hasAnchors, true,
)
// Alice will sweep her commitment output immediately. If there are
// anchors, Alice will also sweep hers.
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
var expectedTxes int
switch c {
// Alice will sweep her commitment output immediately.
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 1
// Alice will sweep her commitment and anchor output immediately.
case lnrpc.CommitmentType_ANCHORS:
expectedTxes = 2
// Alice will sweep her anchor output immediately. 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:
expectedTxes = 1
default:
t.Fatalf("unhandled commitment type %v", c)
}
_, err = waitForNTxsInMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
@ -155,15 +168,28 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
err = restartBob()
require.NoError(t.t, err)
// After the force close transacion is mined, Carol should broadcast her
// second level HTLC transacion. Bob will broadcast a sweep tx to sweep
// his output in the channel with Carol. He can do this immediately, as
// the output is not timelocked since Carol was the one force closing.
// If there are anchors on the commitment, Bob will also sweep his
// anchor.
expectedTxes = 2
if c == lnrpc.CommitmentType_ANCHORS {
// After the force close transacion is mined, transactions will be
// broadcast by both Bob and Carol.
switch c {
// Carol will broadcast her second level HTLC transaction and Bob will
// sweep his commitment output.
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 2
// Carol will broadcast her second level HTLC transaction and Bob will
// sweep his commitment and anchor output.
case lnrpc.CommitmentType_ANCHORS:
expectedTxes = 3
// Carol will broadcast her second level HTLC transaction and anchor
// sweep, and Bob will sweep his anchor output. 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.
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
expectedTxes = 3
default:
t.Fatalf("unhandled commitment type %v", c)
}
txes, err := getNTxsFromMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
@ -179,24 +205,20 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
restartAlice, err := net.SuspendNode(alice)
require.NoError(t.t, err)
// Mine a block to confirm the two transactions (+ the coinbase).
// Mine a block to confirm the expected transactions (+ the coinbase).
block = mineBlocks(t, net, 1, expectedTxes)[0]
require.Len(t.t, block.Transactions, expectedTxes+1)
var secondLevelMaturity uint32
switch c {
// If this is 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.
case lnrpc.CommitmentType_ANCHORS:
secondLevelMaturity = defaultCSV - 1
// For non-anchor channel types, the nursery will handle sweeping the
// second level output, and it will wait one extra block before
// sweeping it.
default:
secondLevelMaturity = defaultCSV
secondLevelMaturity := uint32(defaultCSV)
// If this is 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.
if hasAnchors {
secondLevelMaturity = defaultCSV - 1
}
// Keep track of the second level tx maturity.
@ -220,8 +242,12 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// At this point, Bob should have broadcast his second layer success
// transaction, and should have sent it to the nursery for incubation.
numPendingChans := 1
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
numPendingChans++
}
err = waitForNumChannelPendingForceClose(
bob, 1, func(c *lnrpcForceCloseChannel) error {
bob, numPendingChans, func(c *lnrpcForceCloseChannel) error {
if c.Channel.LocalBalance != 0 {
return nil
}
@ -288,14 +314,57 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, bobSweep)
err = waitForNumChannelPendingForceClose(bob, 0, nil)
require.NoError(t.t, err)
assertNodeNumChannels(t, bob, 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 {
for _, node := range []*lntest.HarnessNode{alice, bob} {
err = waitForNumChannelPendingForceClose(node, 1, nil)
require.NoError(t.t, err)
}
// Also Carol should have no channels left (open nor pending).
err = waitForNumChannelPendingForceClose(carol, 0, nil)
require.NoError(t.t, err)
assertNodeNumChannels(t, carol, 0)
// 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).
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := alice.PendingChannels(
ctxt, &lnrpc.PendingChannelsRequest{},
)
require.NoError(t.t, err)
require.Len(t.t, resp.PendingForceClosingChannels, 1)
forceCloseChan := resp.PendingForceClosingChannels[0]
require.Positive(t.t, forceCloseChan.BlocksTilMaturity)
// Mine enough blocks for the timelock to expire.
numBlocks := uint32(forceCloseChan.BlocksTilMaturity)
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
// Both Alice and Bob show broadcast their commit sweeps.
aliceCommitOutpoint := wire.OutPoint{Hash: *bobForceClose, Index: 3}
aliceCommitSweep := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
aliceCommitOutpoint,
)
bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
bobCommitSweep := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
bobCommitOutpoint,
)
// Confirm their sweeps.
block := mineBlocks(t, net, 1, 2)[0]
assertTxInBlock(t, block, &aliceCommitSweep)
assertTxInBlock(t, block, &bobCommitSweep)
}
// All nodes should show zero pending and open channels.
for _, node := range []*lntest.HarnessNode{alice, bob, carol} {
err = waitForNumChannelPendingForceClose(node, 0, nil)
require.NoError(t.t, err)
assertNodeNumChannels(t, node, 0)
}
// Finally, check that the Alice's payment is correctly marked
// succeeded.

View File

@ -4,7 +4,6 @@ import (
"context"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lncfg"
@ -104,23 +103,24 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
// Bob's force close transaction should now be found in the mempool. If
// there are anchors, we also expect Bob's anchor sweep.
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
hasAnchors := commitTypeHasAnchors(c)
if hasAnchors {
expectedTxes = 2
}
bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint)
require.NoError(t.t, err)
_, err = waitForNTxsInMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
)
require.NoError(t.t, err)
closeTx := getSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{
Hash: *bobFundingTxid,
Index: bobChanPoint.OutputIndex,
},
bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint)
require.NoError(t.t, err)
bobChanOutpoint := wire.OutPoint{
Hash: *bobFundingTxid,
Index: bobChanPoint.OutputIndex,
}
closeTxid := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, bobChanOutpoint,
)
closeTxid := closeTx.TxHash()
// Mine a block to confirm the closing transaction.
mineBlocks(t, net, 1, expectedTxes)
@ -137,87 +137,86 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
// With the closing transaction confirmed, we should expect Bob's HTLC
// timeout transaction to be broadcast due to the expiry being reached.
// If there are anchors, we also expect Carol's anchor sweep now.
txes, err := getNTxsFromMempool(
_, err = getNTxsFromMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
)
require.NoError(t.t, err)
// Lookup the timeout transaction that is expected to spend from the
// closing tx. We distinguish it from a possibly anchor sweep by value.
var htlcTimeout *chainhash.Hash
for _, tx := range txes {
prevOp := tx.TxIn[0].PreviousOutPoint
require.Equal(t.t, closeTxid, prevOp.Hash)
// Assume that the timeout tx doesn't spend an output of exactly
// the size of the anchor.
if closeTx.TxOut[prevOp.Index].Value != anchorSize {
hash := tx.TxHash()
htlcTimeout = &hash
}
// We'll also obtain the expected HTLC timeout transaction hash.
htlcOutpoint := wire.OutPoint{Hash: closeTxid, Index: 0}
commitOutpoint := wire.OutPoint{Hash: closeTxid, Index: 1}
if hasAnchors {
htlcOutpoint.Index = 2
commitOutpoint.Index = 3
}
require.NotNil(t.t, htlcTimeout)
// We'll mine the remaining blocks in order to generate the sweep
// transaction of Bob's commitment output. The commitment was just
// mined at the current tip and the sweep will be broadcast so it can
// be mined at the tip+defaultCSV'th block, so mine one less to be able
// to make mempool assertions.
mineBlocks(t, net, defaultCSV-1, expectedTxes)
// Check that the sweep spends from the mined commitment.
txes, err = getNTxsFromMempool(net.Miner.Client, 1, minerMempoolTimeout)
require.NoError(t.t, err)
assertAllTxesSpendFrom(t, txes, closeTxid)
// Bob's pending channel report should show that he has a commitment
// output awaiting sweeping, and also that there's an outgoing HTLC
// output pending.
pendingChansRequest := &lnrpc.PendingChannelsRequest{}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
pendingChanResp, err := bob.PendingChannels(ctxt, pendingChansRequest)
require.NoError(t.t, err)
require.NotZero(t.t, len(pendingChanResp.PendingForceClosingChannels))
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
require.NotZero(t.t, forceCloseChan.LimboBalance)
require.NotZero(t.t, len(forceCloseChan.PendingHtlcs))
// Mine a block to confirm Bob's commit sweep tx and assert it was in
// fact mined.
block := mineBlocks(t, net, 1, 1)[0]
commitSweepTxid := txes[0].TxHash()
assertTxInBlock(t, block, &commitSweepTxid)
// Mine an additional block to prompt Bob to broadcast their second
// layer sweep due to the CSV on the HTLC timeout output.
mineBlocks(t, net, 1, 0)
assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, wire.OutPoint{
Hash: *htlcTimeout,
Index: 0,
},
htlcTimeoutTxid := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint,
)
// The block should have confirmed Bob's HTLC timeout transaction.
// Therefore, at this point, there should be no active HTLC's on the
// commitment transaction from Alice -> Bob.
nodes = []*lntest.HarnessNode{alice}
// Mine a block to confirm the expected transactions.
_ = mineBlocks(t, net, 1, expectedTxes)
// With Bob's HTLC timeout transaction confirmed, there should be no
// active HTLC's on the commitment transaction from Alice -> Bob.
err = wait.NoError(func() error {
return assertNumActiveHtlcs(nodes, 0)
return assertNumActiveHtlcs([]*lntest.HarnessNode{alice}, 0)
}, defaultTimeout)
require.NoError(t.t, err)
// At this point, Bob should show that the pending HTLC has advanced to
// the second stage and is to be swept.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
pendingChanResp, err = bob.PendingChannels(ctxt, pendingChansRequest)
// the second stage and is ready to be swept once the timelock is up.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
pendingChansRequest := &lnrpc.PendingChannelsRequest{}
pendingChanResp, err := bob.PendingChannels(ctxt, pendingChansRequest)
require.NoError(t.t, err)
forceCloseChan = pendingChanResp.PendingForceClosingChannels[0]
require.Equal(t.t, 1, len(pendingChanResp.PendingForceClosingChannels))
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
require.NotZero(t.t, forceCloseChan.LimboBalance)
require.Positive(t.t, forceCloseChan.BlocksTilMaturity)
require.Equal(t.t, 1, len(forceCloseChan.PendingHtlcs))
require.Equal(t.t, uint32(2), forceCloseChan.PendingHtlcs[0].Stage)
// Next, we'll mine a final block that should confirm the second-layer
// sweeping transaction.
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 := uint32(forceCloseChan.BlocksTilMaturity)
mineBlocks(t, net, blocksTilMaturity, 0)
// Check that the sweep spends the expected inputs.
_ = assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
commitOutpoint, htlcTimeoutOutpoint,
)
} 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.
mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity-1), 0)
// Check that the sweep spends from the mined commitment.
_ = assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, commitOutpoint,
)
// Mine a block to confirm Bob's commit sweep tx and assert it
// was in fact mined.
_ = mineBlocks(t, net, 1, 1)[0]
// Mine an additional block to prompt Bob to broadcast their
// second layer sweep due to the CSV on the HTLC timeout output.
mineBlocks(t, net, 1, 0)
_ = assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
htlcTimeoutOutpoint,
)
}
// Next, we'll mine a final block that should confirm the sweeping
// transactions left.
_, err = net.Miner.Client.Generate(1)
require.NoError(t.t, err)

View File

@ -113,7 +113,8 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// transaction in order to go to the chain and sweep her HTLC. If there
// are anchors, Carol also sweeps hers.
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
hasAnchors := commitTypeHasAnchors(c)
if hasAnchors {
expectedTxes = 2
}
_, err = getNTxsFromMempool(
@ -143,15 +144,32 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
err = restartBob()
require.NoError(t.t, err)
// After the force close transaction is mined, Carol should broadcast
// her second level HTLC transaction. Bob will broadcast a sweep tx to
// sweep his output in the channel with Carol. When Bob notices Carol's
// second level transaction in the mempool, he will extract the preimage
// and settle the HTLC back off-chain. Bob will also sweep his anchor,
// if present.
expectedTxes = 2
if c == lnrpc.CommitmentType_ANCHORS {
// 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 {
// Carol should broadcast her second level HTLC transaction and Bob
// should broadcast a sweep tx to sweep his output in the channel with
// Carol.
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 2
// Carol should broadcast her second level HTLC transaction and Bob
// should broadcast a sweep tx to sweep his output in the channel with
// Carol, and another sweep tx to sweep his anchor output.
case lnrpc.CommitmentType_ANCHORS:
expectedTxes = 3
// Carol should broadcast her second level HTLC transaction and Bob
// should broadcast a sweep tx to sweep his anchor output. 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
default:
t.Fatalf("unhandled commitment type %v", c)
}
txes, err := getNTxsFromMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
@ -195,8 +213,8 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
}, defaultTimeout)
require.NoError(t.t, err)
// If we mine 4 additional blocks, then both outputs should now be
// mature.
// If we mine 4 additional blocks, then Carol can sweep the second level
// HTLC output.
_, err = net.Miner.Client.Generate(defaultCSV)
require.NoError(t.t, err)
@ -227,6 +245,41 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
err = checkPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
require.NoError(t.t, err)
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.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
pendingChanResp, err := bob.PendingChannels(
ctxt, &lnrpc.PendingChannelsRequest{},
)
require.NoError(t.t, err)
require.Len(t.t, pendingChanResp.PendingForceClosingChannels, 1)
forceCloseChan := pendingChanResp.PendingForceClosingChannels[0]
require.Positive(t.t, forceCloseChan.LimboBalance)
require.Positive(t.t, 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.
_ = mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity), 0)
commitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, commitOutpoint,
)
_, err = net.Miner.Client.Generate(1)
require.NoError(t.t, err)
}
err = waitForNumChannelPendingForceClose(bob, 0, nil)
require.NoError(t.t, err)
// We'll close out the channel between Alice and Bob, then shutdown
// carol to conclude the test.
closeChannelAndAssertType(t, net, alice, aliceChanPoint, false, false)

View File

@ -85,9 +85,9 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// Next, Alice decides that she wants to exit the channel, so she'll
// immediately force close the channel by broadcast her commitment
// transaction.
hasAnchors := commitTypeHasAnchors(c)
aliceForceClose := closeChannelAndAssertType(
t, net, alice, aliceChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
t, net, alice, aliceChanPoint, hasAnchors, true,
)
// Wait for the channel to be marked pending force close.
@ -97,29 +97,29 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// After closeChannelAndAssertType returns, it has mined a block so now
// bob will attempt to redeem his anchor commitment (if the channel
// type is of that type).
if c == lnrpc.CommitmentType_ANCHORS {
if hasAnchors {
_, err = waitForNTxsInMempool(
net.Miner.Client, 1, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find bob's anchor commit sweep: %v",
err)
}
require.NoError(t.t, err)
}
// Mine enough blocks for Alice to sweep her funds from the force
// closed channel. closeChannelAndAssertType() 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.
_, err = net.Miner.Client.Generate(defaultCSV - 1)
require.NoError(t.t, err)
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// Mine enough blocks for Alice to sweep her funds from the
// force closed channel. closeChannelAndAssertType() 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.
_, err = net.Miner.Client.Generate(defaultCSV - 1)
require.NoError(t.t, err)
// Alice should now sweep her funds.
_, err = waitForNTxsInMempool(
net.Miner.Client, 1, minerMempoolTimeout,
)
require.NoError(t.t, err)
// Alice should now sweep her funds.
_, err = waitForNTxsInMempool(
net.Miner.Client, 1, minerMempoolTimeout,
)
require.NoError(t.t, err)
}
// Suspend bob, so Carol is forced to go on chain.
restartBob, err := net.SuspendNode(bob)
@ -140,14 +140,17 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// 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,
) - defaultCSV)
invoiceReq.CltvExpiry - lncfg.DefaultIncomingBroadcastDelta,
))
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
numBlocks -= defaultCSV
}
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
if hasAnchors {
expectedTxes = 2
}
@ -181,14 +184,31 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
err = restartBob()
require.NoError(t.t, err)
// After the force close transacion is mined, Carol should broadcast her
// second level HTLC transacion. Bob will broadcast a sweep tx to sweep
// his output in the channel with Carol. He can do this immediately, as
// the output is not timelocked since Carol was the one force closing.
// If there are anchors, Bob should also sweep his.
expectedTxes = 2
if c == lnrpc.CommitmentType_ANCHORS {
// After the force close transacion 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 transaction to sweep his commitment output.
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 2
// Carol should broadcast her second level HTLC transaction and Bob
// should broadcast a transaction to sweep his commitment output and
// another to sweep his anchor output.
case lnrpc.CommitmentType_ANCHORS:
expectedTxes = 3
// Carol should broadcast her second level HTLC transaction and Bob
// should broadcast a transaction to sweep his anchor output. 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:
expectedTxes = 2
default:
t.Fatalf("unhandled commitment type %v", c)
}
txes, err := getNTxsFromMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
@ -228,10 +248,18 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
carolSecondLevelCSV--
// Now that the sweeping transaction has been confirmed, Bob should now
// recognize that all contracts have been fully resolved, and show no
// pending close channels.
err = waitForNumChannelPendingForceClose(bob, 0, nil)
require.NoError(t.t, err)
// 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 []*lntest.HarnessNode{alice, bob} {
err = waitForNumChannelPendingForceClose(
node, aliceBobPendingChansLeft, nil,
)
require.NoError(t.t, err)
}
// If we then mine 3 additional blocks, Carol's second level tx will
// mature, and she should pull the funds.
@ -251,6 +279,53 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
err = waitForNumChannelPendingForceClose(carol, 0, nil)
require.NoError(t.t, err)
// 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).
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := alice.PendingChannels(
ctxt, &lnrpc.PendingChannelsRequest{},
)
require.NoError(t.t, err)
require.Len(t.t, resp.PendingForceClosingChannels, 1)
forceCloseChan := resp.PendingForceClosingChannels[0]
require.Positive(t.t, forceCloseChan.BlocksTilMaturity)
// Mine enough blocks for the timelock to expire.
numBlocks := uint32(forceCloseChan.BlocksTilMaturity)
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
// Both Alice and Bob show broadcast their commit sweeps.
aliceCommitOutpoint := wire.OutPoint{Hash: *aliceForceClose, Index: 3}
aliceCommitSweep := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
aliceCommitOutpoint,
)
bobCommitOutpoint := wire.OutPoint{Hash: closingTxid, Index: 3}
bobCommitSweep := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
bobCommitOutpoint,
)
// Confirm their sweeps.
block := mineBlocks(t, net, 1, 2)[0]
assertTxInBlock(t, block, &aliceCommitSweep)
assertTxInBlock(t, block, &bobCommitSweep)
// Alice and Bob should not show any pending channels anymore as
// they have been fully resolved.
for _, node := range []*lntest.HarnessNode{alice, bob} {
err = waitForNumChannelPendingForceClose(node, 0, nil)
require.NoError(t.t, err)
}
}
// The invoice should show as settled for Carol, indicating that it was
// swept on-chain.
invoicesReq := &lnrpc.ListInvoiceRequest{}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
@ -72,9 +73,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// 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.
closeChannelAndAssertType(
t, net, bob, bobChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
hasAnchors := commitTypeHasAnchors(c)
closeTx := closeChannelAndAssertType(
t, net, bob, bobChanPoint, hasAnchors, true,
)
// At this point, Bob should have a pending force close channel as he
@ -92,27 +93,45 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
)
require.NoError(t.t, err)
// We'll mine defaultCSV blocks in order to generate the sweep
// transaction of Bob's funding output. If there are anchors, mine
// Carol's anchor sweep too.
if c == lnrpc.CommitmentType_ANCHORS {
// If the channel closed has anchors, we should expect to see a sweep
// transaction for Carol's anchor.
htlcOutpoint := wire.OutPoint{Hash: *closeTx, Index: 0}
bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 1}
if hasAnchors {
htlcOutpoint.Index = 2
bobCommitOutpoint.Index = 3
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.NoError(t.t, err)
}
// The sweep is broadcast on the block immediately before the CSV
// expires and the commitment was already mined inside
// closeChannelAndAssertType(), so mine one block less than defaultCSV
// in order to perform mempool assertions.
_, err = net.Miner.Client.Generate(defaultCSV - 1)
require.NoError(t.t, err)
// 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
// closeChannelAndAssertType(), so mine one block less than
// defaultCSV in order to perform mempool assertions.
_, err = net.Miner.Client.Generate(defaultCSV - 1)
require.NoError(t.t, err)
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.NoError(t.t, err)
commitSweepTx := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
bobCommitOutpoint,
)
blocks := mineBlocks(t, net, 1, 1)
assertTxInBlock(t, blocks[0], &commitSweepTx)
}
// 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 - defaultCSV))
numBlocks := padCLTV(finalCltvDelta)
if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
// Subtract the number of blocks already mined to confirm Bob's
// commit sweep.
numBlocks -= defaultCSV
}
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
@ -137,13 +156,14 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// We should also now find a transaction in the mempool, as Bob should
// have broadcast his second layer timeout transaction.
timeoutTx, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.NoError(t.t, err)
timeoutTx := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, htlcOutpoint,
)
// Next, we'll mine an additional block. This should serve to confirm
// the second layer timeout transaction.
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, timeoutTx)
assertTxInBlock(t, block, &timeoutTx)
// With the second layer timeout transaction confirmed, Bob should have
// canceled backwards the HTLC that carol sent.
@ -172,19 +192,37 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
)
require.NoError(t.t, err)
// We'll now mine 4 additional blocks. This should be enough for Bob's
// CSV timelock to expire and the sweeping transaction of the HTLC to be
// broadcast.
_, err = net.Miner.Client.Generate(defaultCSV)
// 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.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
resp, err := bob.PendingChannels(ctxt, &lnrpc.PendingChannelsRequest{})
require.NoError(t.t, err)
sweepTx, err := waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.Len(t.t, resp.PendingForceClosingChannels, 1)
forceCloseChan := resp.PendingForceClosingChannels[0]
require.Len(t.t, forceCloseChan.PendingHtlcs, 1)
pendingHtlc := forceCloseChan.PendingHtlcs[0]
require.Positive(t.t, pendingHtlc.BlocksTilMaturity)
numBlocks = uint32(pendingHtlc.BlocksTilMaturity)
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
// We'll then mine a final block which should confirm this second layer
// sweep transaction.
// 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.
htlcTimeoutOutpoint := wire.OutPoint{Hash: timeoutTx, Index: 0}
expectedInputsSwept := []wire.OutPoint{htlcTimeoutOutpoint}
if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
expectedInputsSwept = append(expectedInputsSwept, bobCommitOutpoint)
}
sweepTx := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout, expectedInputsSwept...,
)
block = mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, sweepTx)
assertTxInBlock(t, block, &sweepTx)
// At this point, Bob should no longer show any channels as pending
// close.

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
@ -83,9 +84,9 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// transaction. This will let us exercise that Bob is able to sweep the
// expired HTLC on Carol's version of the commitment transaction. If
// Carol has an anchor, it will be swept too.
closeChannelAndAssertType(
t, net, carol, bobChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
hasAnchors := commitTypeHasAnchors(c)
closeTx := closeChannelAndAssertType(
t, net, carol, bobChanPoint, hasAnchors, true,
)
// At this point, Bob should have a pending force close channel as
@ -93,13 +94,25 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
err = waitForNumChannelPendingForceClose(bob, 1, nil)
require.NoError(t.t, err)
// Bob can sweep his output immediately. If there is an anchor, Bob will
// sweep that as well.
expectedTxes := 1
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}
var expectedTxes int
switch c {
// Bob can sweep his commit output immediately.
case lnrpc.CommitmentType_LEGACY:
expectedTxes = 1
// Bob can sweep his commit and anchor outputs immediately.
case lnrpc.CommitmentType_ANCHORS:
expectedTxes = 2
// 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 sweep his anchor output however.
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
expectedTxes = 1
default:
t.Fatalf("unhandled commitment type %v", c)
}
_, err = waitForNTxsInMempool(
net.Miner.Client, expectedTxes, minerMempoolTimeout,
)
@ -169,8 +182,32 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
require.NoError(t.t, err)
// 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. As a result,
// he should show no additional pending transactions.
// 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 {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := bob.PendingChannels(
ctxt, &lnrpc.PendingChannelsRequest{},
)
require.NoError(t.t, err)
require.Len(t.t, resp.PendingForceClosingChannels, 1)
forceCloseChan := resp.PendingForceClosingChannels[0]
require.Positive(t.t, forceCloseChan.BlocksTilMaturity)
numBlocks := uint32(forceCloseChan.BlocksTilMaturity)
_, err = net.Miner.Client.Generate(numBlocks)
require.NoError(t.t, err)
bobCommitOutpoint := wire.OutPoint{Hash: *closeTx, Index: 3}
bobCommitSweep := assertSpendingTxInMempool(
t, net.Miner.Client, minerMempoolTimeout,
bobCommitOutpoint,
)
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, &bobCommitSweep)
}
err = waitForNumChannelPendingForceClose(bob, 0, nil)
require.NoError(t.t, err)

View File

@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
@ -71,6 +72,7 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
commitTypes := []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
}
for _, commitType := range commitTypes {
@ -221,11 +223,22 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
// 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, err := net.Miner.Client.GetBestBlock()
require.NoError(t.t, err)
thawHeight = uint32(minerHeight + 144)
aliceFundingShim, _, _ = deriveFundingShim(
net, t, alice, bob, chanAmt, thawHeight, true,
)
}
aliceChanPoint := openChannelAndAssert(
t, net, alice, bob,
lntest.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: aliceFundingShim,
},
)
@ -262,11 +275,18 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
// We'll then 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(
net, t, bob, carol, chanAmt, thawHeight, true,
)
}
bobChanPoint := openChannelAndAssert(
t, net, bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
CommitmentType: c,
FundingShim: bobFundingShim,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)

View File

@ -208,6 +208,17 @@ func getChanInfo(node *lntest.HarnessNode) (*lnrpc.Channel, error) {
return channelInfo.Channels[0], nil
}
// commitTypeHasAnchors returns whether commitType uses anchor outputs.
func commitTypeHasAnchors(commitType lnrpc.CommitmentType) bool {
switch commitType {
case lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return true
default:
return false
}
}
// nodeArgsForCommitType returns the command line flag to supply to enable this
// commitment type.
func nodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
@ -218,6 +229,11 @@ func nodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
return []string{}
case lnrpc.CommitmentType_ANCHORS:
return []string{"--protocol.anchors"}
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return []string{
"--protocol.anchors",
"--protocol.script-enforced-lease",
}
}
return nil
@ -238,7 +254,7 @@ func calcStaticFee(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
// the value of the two anchors to the resulting fee the initiator
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
if c == lnrpc.CommitmentType_ANCHORS {
if commitTypeHasAnchors(c) {
feePerKw = chainfee.SatPerKVByte(
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
).FeePerKWeight()