itest+lntest: fix channel force close test
Also flatten the tests to make them easier to be maintained.
This commit is contained in:
4 files changed
+160
-245
No files matched your search
@@ -210,8 +210,12 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
TestFunc: testChannelUnsettledBalance,
|
TestFunc: testChannelUnsettledBalance,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "channel force closure",
|
Name: "channel force closure anchor",
|
||||||
TestFunc: testChannelForceClosure,
|
TestFunc: testChannelForceClosureAnchor,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "channel force closure simple taproot",
|
||||||
|
TestFunc: testChannelForceClosureSimpleTaproot,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "failing channel",
|
Name: "failing channel",
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ package itest
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd"
|
"github.com/lightningnetwork/lnd"
|
||||||
"github.com/lightningnetwork/lnd/chainreg"
|
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
@@ -20,97 +18,82 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testChannelForceClosure performs a test to exercise the behavior of "force"
|
const pushAmt = btcutil.Amount(5e5)
|
||||||
// closing a channel or unilaterally broadcasting the latest local commitment
|
|
||||||
// state on-chain. The test creates a new channel between Alice and Carol, then
|
// testChannelForceClosureAnchor runs `runChannelForceClosureTest` with anchor
|
||||||
// force closes the channel after some cursory assertions. Within the test, a
|
// channels.
|
||||||
// total of 3 + n transactions will be broadcast, representing the commitment
|
func testChannelForceClosureAnchor(ht *lntest.HarnessTest) {
|
||||||
// transaction, a transaction sweeping the local CSV delayed output, a
|
// Create a simple network: Alice -> Carol, using anchor channels.
|
||||||
// transaction sweeping the CSV delayed 2nd-layer htlcs outputs, and n
|
//
|
||||||
// htlc timeout transactions, where n is the number of payments Alice attempted
|
// Prepare params.
|
||||||
|
openChannelParams := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := node.CfgAnchor
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfgCarol}
|
||||||
|
|
||||||
|
runChannelForceClosureTest(ht, cfgs, openChannelParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testChannelForceClosureSimpleTaproot runs `runChannelForceClosureTest` with
|
||||||
|
// simple taproot channels.
|
||||||
|
func testChannelForceClosureSimpleTaproot(ht *lntest.HarnessTest) {
|
||||||
|
// Create a simple network: Alice -> Carol, using simple taproot
|
||||||
|
// channels.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
openChannelParams := lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
// If the channel is a taproot channel, then we'll need to
|
||||||
|
// create a private channel.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): lift after G175
|
||||||
|
CommitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||||
|
Private: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := node.CfgSimpleTaproot
|
||||||
|
cfgCarol := append([]string{"--hodl.exit-settle"}, cfg...)
|
||||||
|
cfgs := [][]string{cfg, cfgCarol}
|
||||||
|
|
||||||
|
runChannelForceClosureTest(ht, cfgs, openChannelParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runChannelForceClosureTest performs a test to exercise the behavior of
|
||||||
|
// "force" closing a channel or unilaterally broadcasting the latest local
|
||||||
|
// commitment state on-chain. The test creates a new channel between Alice and
|
||||||
|
// Carol, then force closes the channel after some cursory assertions. Within
|
||||||
|
// the test, a total of 3 + n transactions will be broadcast, representing the
|
||||||
|
// commitment transaction, a transaction sweeping the local CSV delayed output,
|
||||||
|
// a transaction sweeping the CSV delayed 2nd-layer htlcs outputs, and n htlc
|
||||||
|
// timeout transactions, where n is the number of payments Alice attempted
|
||||||
// to send to Carol. This test includes several restarts to ensure that the
|
// to send to Carol. This test includes several restarts to ensure that the
|
||||||
// transaction output states are persisted throughout the forced closure
|
// transaction output states are persisted throughout the forced closure
|
||||||
// process.
|
// process.
|
||||||
//
|
func runChannelForceClosureTest(ht *lntest.HarnessTest,
|
||||||
// TODO(roasbeef): also add an unsettled HTLC before force closing.
|
cfgs [][]string, params lntest.OpenChannelParams) {
|
||||||
func testChannelForceClosure(ht *lntest.HarnessTest) {
|
|
||||||
// We'll test the scenario for some of the commitment types, to ensure
|
|
||||||
// outputs can be swept.
|
|
||||||
commitTypes := []lnrpc.CommitmentType{
|
|
||||||
lnrpc.CommitmentType_ANCHORS,
|
|
||||||
lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channelType := range commitTypes {
|
|
||||||
testName := fmt.Sprintf("committype=%v", channelType)
|
|
||||||
|
|
||||||
channelType := channelType
|
|
||||||
success := ht.Run(testName, func(t *testing.T) {
|
|
||||||
st := ht.Subtest(t)
|
|
||||||
|
|
||||||
args := lntest.NodeArgsForCommitType(channelType)
|
|
||||||
alice := st.NewNode("Alice", args)
|
|
||||||
defer st.Shutdown(alice)
|
|
||||||
|
|
||||||
// Since we'd like to test failure scenarios with
|
|
||||||
// outstanding htlcs, we'll introduce another node into
|
|
||||||
// our test network: Carol.
|
|
||||||
carolArgs := []string{"--hodl.exit-settle"}
|
|
||||||
carolArgs = append(carolArgs, args...)
|
|
||||||
carol := st.NewNode("Carol", carolArgs)
|
|
||||||
defer st.Shutdown(carol)
|
|
||||||
|
|
||||||
// Each time, we'll send Alice new set of coins in
|
|
||||||
// order to fund the channel.
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
|
|
||||||
// NOTE: Alice needs 3 more UTXOs to sweep her
|
|
||||||
// second-layer txns after a restart - after a restart
|
|
||||||
// all the time-sensitive sweeps are swept immediately
|
|
||||||
// without being aggregated.
|
|
||||||
//
|
|
||||||
// TODO(yy): remove this once the can recover its state
|
|
||||||
// from restart.
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
|
||||||
|
|
||||||
// Also give Carol some coins to allow her to sweep her
|
|
||||||
// anchor.
|
|
||||||
st.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
|
||||||
|
|
||||||
channelForceClosureTest(st, alice, carol, channelType)
|
|
||||||
})
|
|
||||||
if !success {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func channelForceClosureTest(ht *lntest.HarnessTest,
|
|
||||||
alice, carol *node.HarnessNode, channelType lnrpc.CommitmentType) {
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chanAmt = btcutil.Amount(10e6)
|
numInvoices = 6
|
||||||
pushAmt = btcutil.Amount(5e6)
|
commitFeeRate = 20000
|
||||||
paymentAmt = 100000
|
|
||||||
numInvoices = 6
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const commitFeeRate = 20000
|
|
||||||
ht.SetFeeEstimate(commitFeeRate)
|
ht.SetFeeEstimate(commitFeeRate)
|
||||||
|
|
||||||
// TODO(roasbeef): should check default value in config here
|
// Create a three hop network: Alice -> Carol.
|
||||||
// instead, or make delay a param
|
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, params)
|
||||||
defaultCLTV := uint32(chainreg.DefaultBitcoinTimeLockDelta)
|
alice, carol := nodes[0], nodes[1]
|
||||||
|
chanPoint := chanPoints[0]
|
||||||
// We must let Alice have an open channel before she can send a node
|
|
||||||
// announcement, so we open a channel with Carol,
|
|
||||||
ht.ConnectNodes(alice, carol)
|
|
||||||
|
|
||||||
// We need one additional UTXO for sweeping the remote anchor.
|
// We need one additional UTXO for sweeping the remote anchor.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
if ht.IsNeutrinoBackend() {
|
||||||
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||||
|
}
|
||||||
|
|
||||||
// Before we start, obtain Carol's current wallet balance, we'll check
|
// Before we start, obtain Carol's current wallet balance, we'll check
|
||||||
// to ensure that at the end of the force closure by Alice, Carol
|
// to ensure that at the end of the force closure by Alice, Carol
|
||||||
@@ -118,24 +101,6 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
carolBalResp := carol.RPC.WalletBalance()
|
carolBalResp := carol.RPC.WalletBalance()
|
||||||
carolStartingBalance := carolBalResp.ConfirmedBalance
|
carolStartingBalance := carolBalResp.ConfirmedBalance
|
||||||
|
|
||||||
// If the channel is a taproot channel, then we'll need to create a
|
|
||||||
// private channel.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): lift after G175
|
|
||||||
var privateChan bool
|
|
||||||
if channelType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
|
||||||
privateChan = true
|
|
||||||
}
|
|
||||||
|
|
||||||
chanPoint := ht.OpenChannel(
|
|
||||||
alice, carol, lntest.OpenChannelParams{
|
|
||||||
Private: privateChan,
|
|
||||||
Amt: chanAmt,
|
|
||||||
PushAmt: pushAmt,
|
|
||||||
CommitmentType: channelType,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send payments from Alice to Carol, since Carol is htlchodl mode, the
|
// Send payments from Alice to Carol, since Carol is htlchodl mode, the
|
||||||
// htlc outputs should be left unsettled, and should be swept by the
|
// htlc outputs should be left unsettled, and should be swept by the
|
||||||
// utxo nursery.
|
// utxo nursery.
|
||||||
@@ -145,7 +110,7 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
Dest: carolPubKey,
|
Dest: carolPubKey,
|
||||||
Amt: int64(paymentAmt),
|
Amt: int64(paymentAmt),
|
||||||
PaymentHash: ht.Random32Bytes(),
|
PaymentHash: ht.Random32Bytes(),
|
||||||
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
FinalCltvDelta: finalCltvDelta,
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
}
|
}
|
||||||
@@ -162,13 +127,13 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
curHeight := int32(ht.CurrentHeight())
|
curHeight := int32(ht.CurrentHeight())
|
||||||
|
|
||||||
// Using the current height of the chain, derive the relevant heights
|
// Using the current height of the chain, derive the relevant heights
|
||||||
// for incubating two-stage htlcs.
|
// for sweeping two-stage htlcs.
|
||||||
var (
|
var (
|
||||||
startHeight = uint32(curHeight)
|
startHeight = uint32(curHeight)
|
||||||
commCsvMaturityHeight = startHeight + 1 + defaultCSV
|
commCsvMaturityHeight = startHeight + 1 + defaultCSV
|
||||||
htlcExpiryHeight = padCLTV(startHeight + defaultCLTV)
|
htlcExpiryHeight = padCLTV(startHeight + finalCltvDelta)
|
||||||
htlcCsvMaturityHeight = padCLTV(
|
htlcCsvMaturityHeight = padCLTV(
|
||||||
startHeight + defaultCLTV + 1 + defaultCSV,
|
startHeight + finalCltvDelta + 1 + defaultCSV,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,21 +164,15 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
)
|
)
|
||||||
|
|
||||||
// The several restarts in this test are intended to ensure that when a
|
// The several restarts in this test are intended to ensure that when a
|
||||||
// channel is force-closed, the UTXO nursery has persisted the state of
|
// channel is force-closed, the contract court has persisted the state
|
||||||
// the channel in the closure process and will recover the correct
|
// of the channel in the closure process and will recover the correct
|
||||||
// state when the system comes back on line. This restart tests state
|
// state when the system comes back on line. This restart tests state
|
||||||
// persistence at the beginning of the process, when the commitment
|
// persistence at the beginning of the process, when the commitment
|
||||||
// transaction has been broadcast but not yet confirmed in a block.
|
// transaction has been broadcast but not yet confirmed in a block.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
// To give the neutrino backend some time to catch up with the chain,
|
|
||||||
// we wait here until we have enough UTXOs to actually sweep the local
|
|
||||||
// and remote anchor.
|
|
||||||
const expectedUtxos = 6
|
|
||||||
ht.AssertNumUTXOs(alice, expectedUtxos)
|
|
||||||
|
|
||||||
// We expect to see Alice's force close tx in the mempool.
|
// We expect to see Alice's force close tx in the mempool.
|
||||||
ht.GetNumTxsFromMempool(1)
|
ht.AssertNumTxsInMempool(1)
|
||||||
|
|
||||||
// Mine a block which should confirm the commitment transaction
|
// Mine a block which should confirm the commitment transaction
|
||||||
// broadcast as a result of the force closure. Once mined, we also
|
// broadcast as a result of the force closure. Once mined, we also
|
||||||
@@ -258,46 +217,34 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// The following restart is intended to ensure that outputs from the
|
// The following restart is intended to ensure that outputs from the
|
||||||
// force close commitment transaction have been persisted once the
|
// force close commitment transaction have been persisted once the
|
||||||
// transaction has been confirmed, but before the outputs are spendable
|
// transaction has been confirmed, but before the outputs are
|
||||||
// (the "kindergarten" bucket.)
|
// spendable.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
// Carol should offer her commit and anchor outputs to the sweeper.
|
// Carol should offer her commit and anchor outputs to the sweeper.
|
||||||
sweepTxns := ht.AssertNumPendingSweeps(carol, 2)
|
sweepTxns := ht.AssertNumPendingSweeps(carol, 2)
|
||||||
|
|
||||||
// Find Carol's anchor sweep.
|
// Identify Carol's pending sweeps.
|
||||||
var carolAnchor, carolCommit = sweepTxns[0], sweepTxns[1]
|
var carolAnchor, carolCommit = sweepTxns[0], sweepTxns[1]
|
||||||
if carolAnchor.AmountSat != uint32(anchorSize) {
|
if carolAnchor.AmountSat != uint32(anchorSize) {
|
||||||
carolAnchor, carolCommit = carolCommit, carolAnchor
|
carolAnchor, carolCommit = carolCommit, carolAnchor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mine a block to trigger Carol's sweeper to make decisions on the
|
|
||||||
// anchor sweeping.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// Carol's sweep tx should be in the mempool already, as her output is
|
// Carol's sweep tx should be in the mempool already, as her output is
|
||||||
// not timelocked.
|
// not timelocked. This sweep tx should spend her to_local output as
|
||||||
|
// the anchor output is not economical to spend.
|
||||||
carolTx := ht.GetNumTxsFromMempool(1)[0]
|
carolTx := ht.GetNumTxsFromMempool(1)[0]
|
||||||
|
|
||||||
// Carol's sweeping tx should have 2-input-1-output shape.
|
// Carol's sweeping tx should have 1-input-1-output shape.
|
||||||
require.Len(ht, carolTx.TxIn, 2)
|
require.Len(ht, carolTx.TxIn, 1)
|
||||||
require.Len(ht, carolTx.TxOut, 1)
|
require.Len(ht, carolTx.TxOut, 1)
|
||||||
|
|
||||||
// Calculate the total fee Carol paid.
|
// Calculate the total fee Carol paid.
|
||||||
totalFeeCarol := ht.CalculateTxFee(carolTx)
|
totalFeeCarol := ht.CalculateTxFee(carolTx)
|
||||||
|
|
||||||
// If we have anchors, add an anchor resolution for carol.
|
// Carol's anchor report won't be created since it's uneconomical to
|
||||||
op := fmt.Sprintf("%v:%v", carolAnchor.Outpoint.TxidStr,
|
// sweep. So we expect to see only the commit sweep report.
|
||||||
carolAnchor.Outpoint.OutputIndex)
|
op := fmt.Sprintf("%v:%v", carolCommit.Outpoint.TxidStr,
|
||||||
carolReports[op] = &lnrpc.Resolution{
|
|
||||||
ResolutionType: lnrpc.ResolutionType_ANCHOR,
|
|
||||||
Outcome: lnrpc.ResolutionOutcome_CLAIMED,
|
|
||||||
SweepTxid: carolTx.TxHash().String(),
|
|
||||||
AmountSat: anchorSize,
|
|
||||||
Outpoint: carolAnchor.Outpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
op = fmt.Sprintf("%v:%v", carolCommit.Outpoint.TxidStr,
|
|
||||||
carolCommit.Outpoint.OutputIndex)
|
carolCommit.Outpoint.OutputIndex)
|
||||||
carolReports[op] = &lnrpc.Resolution{
|
carolReports[op] = &lnrpc.Resolution{
|
||||||
ResolutionType: lnrpc.ResolutionType_COMMIT,
|
ResolutionType: lnrpc.ResolutionType_COMMIT,
|
||||||
@@ -319,9 +266,9 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
// Alice should still have the anchor sweeping request.
|
// Alice should still have the anchor sweeping request.
|
||||||
ht.AssertNumPendingSweeps(alice, 1)
|
ht.AssertNumPendingSweeps(alice, 1)
|
||||||
|
|
||||||
// The following restart checks to ensure that outputs in the
|
// The following restart checks to ensure that outputs in the contract
|
||||||
// kindergarten bucket are persisted while waiting for the required
|
// court are persisted while waiting for the required number of
|
||||||
// number of confirmations to be reported.
|
// confirmations to be reported.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
// Alice should see the channel in her set of pending force closed
|
// Alice should see the channel in her set of pending force closed
|
||||||
@@ -344,12 +291,12 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
aliceBalance = forceClose.Channel.LocalBalance
|
aliceBalance = forceClose.Channel.LocalBalance
|
||||||
|
|
||||||
// At this point, the nursery should show that the commitment
|
// At this point, the nursery should show that the commitment
|
||||||
// output has 2 block left before its CSV delay expires. In
|
// output has 3 block left before its CSV delay expires. In
|
||||||
// total, we have mined exactly defaultCSV blocks, so the htlc
|
// total, we have mined exactly defaultCSV blocks, so the htlc
|
||||||
// outputs should also reflect that this many blocks have
|
// outputs should also reflect that this many blocks have
|
||||||
// passed.
|
// passed.
|
||||||
err = checkCommitmentMaturity(
|
err = checkCommitmentMaturity(
|
||||||
forceClose, commCsvMaturityHeight, 2,
|
forceClose, commCsvMaturityHeight, 3,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -368,9 +315,9 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
require.NoError(ht, err, "timeout while checking force closed channel")
|
require.NoError(ht, err, "timeout while checking force closed channel")
|
||||||
|
|
||||||
// Generate an additional block, which should cause the CSV delayed
|
// Generate two blocks, which should cause the CSV delayed output from
|
||||||
// output from the commitment txn to expire.
|
// the commitment txn to expire.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineBlocks(2)
|
||||||
|
|
||||||
// At this point, the CSV will expire in the next block, meaning that
|
// At this point, the CSV will expire in the next block, meaning that
|
||||||
// the output should be offered to the sweeper.
|
// the output should be offered to the sweeper.
|
||||||
@@ -380,14 +327,9 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
commitSweep, anchorSweep = anchorSweep, commitSweep
|
commitSweep, anchorSweep = anchorSweep, commitSweep
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart Alice to ensure that she resumes watching the finalized
|
|
||||||
// commitment sweep txid.
|
|
||||||
ht.RestartNode(alice)
|
|
||||||
|
|
||||||
// Mine one block and the sweeping transaction should now be broadcast.
|
// Mine one block and the sweeping transaction should now be broadcast.
|
||||||
// So we fetch the node's mempool to ensure it has been properly
|
// So we fetch the node's mempool to ensure it has been properly
|
||||||
// broadcast.
|
// broadcast.
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
sweepingTXID := ht.AssertNumTxsInMempool(1)[0]
|
sweepingTXID := ht.AssertNumTxsInMempool(1)[0]
|
||||||
|
|
||||||
// Fetch the sweep transaction, all input it's spending should be from
|
// Fetch the sweep transaction, all input it's spending should be from
|
||||||
@@ -398,7 +340,12 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
"sweep transaction not spending from commit")
|
"sweep transaction not spending from commit")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect a resolution which spends our commit output.
|
// Restart Alice to ensure that she resumes watching the finalized
|
||||||
|
// commitment sweep txid.
|
||||||
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
|
// Alice's anchor report won't be created since it's uneconomical to
|
||||||
|
// sweep. We expect a resolution which spends our commit output.
|
||||||
op = fmt.Sprintf("%v:%v", commitSweep.Outpoint.TxidStr,
|
op = fmt.Sprintf("%v:%v", commitSweep.Outpoint.TxidStr,
|
||||||
commitSweep.Outpoint.OutputIndex)
|
commitSweep.Outpoint.OutputIndex)
|
||||||
aliceReports[op] = &lnrpc.Resolution{
|
aliceReports[op] = &lnrpc.Resolution{
|
||||||
@@ -409,17 +356,6 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
AmountSat: uint64(aliceBalance),
|
AmountSat: uint64(aliceBalance),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add alice's anchor to our expected set of reports.
|
|
||||||
op = fmt.Sprintf("%v:%v", aliceAnchor.Outpoint.TxidStr,
|
|
||||||
aliceAnchor.Outpoint.OutputIndex)
|
|
||||||
aliceReports[op] = &lnrpc.Resolution{
|
|
||||||
ResolutionType: lnrpc.ResolutionType_ANCHOR,
|
|
||||||
Outcome: lnrpc.ResolutionOutcome_CLAIMED,
|
|
||||||
SweepTxid: sweepingTXID.String(),
|
|
||||||
Outpoint: aliceAnchor.Outpoint,
|
|
||||||
AmountSat: uint64(anchorSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we can find the commitment sweep in our set of known
|
// Check that we can find the commitment sweep in our set of known
|
||||||
// sweeps, using the simple transaction id ListSweeps output.
|
// sweeps, using the simple transaction id ListSweeps output.
|
||||||
ht.AssertSweepFound(alice, sweepingTXID.String(), false, 0)
|
ht.AssertSweepFound(alice, sweepingTXID.String(), false, 0)
|
||||||
@@ -489,17 +425,13 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Advance the blockchain until just before the CLTV expires, nothing
|
// Advance the blockchain until just before the CLTV expires, nothing
|
||||||
// exciting should have happened during this time.
|
// exciting should have happened during this time.
|
||||||
ht.MineEmptyBlocks(cltvHeightDelta)
|
ht.MineBlocks(cltvHeightDelta)
|
||||||
|
|
||||||
// We now restart Alice, to ensure that she will broadcast the
|
// We now restart Alice, to ensure that she will broadcast the
|
||||||
// presigned htlc timeout txns after the delay expires after
|
// presigned htlc timeout txns after the delay expires after
|
||||||
// experiencing a while waiting for the htlc outputs to incubate.
|
// experiencing a while waiting for the htlc outputs to incubate.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
// To give the neutrino backend some time to catch up with the chain,
|
|
||||||
// we wait here until we have enough UTXOs to
|
|
||||||
// ht.AssertNumUTXOs(alice, expectedUtxos)
|
|
||||||
|
|
||||||
// Alice should now see the channel in her set of pending force closed
|
// Alice should now see the channel in her set of pending force closed
|
||||||
// channels with one pending HTLC.
|
// channels with one pending HTLC.
|
||||||
err = wait.NoError(func() error {
|
err = wait.NoError(func() error {
|
||||||
@@ -534,24 +466,23 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Now, generate the block which will cause Alice to offer the
|
// Now, generate the block which will cause Alice to offer the
|
||||||
// presigned htlc timeout txns to the sweeper.
|
// presigned htlc timeout txns to the sweeper.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineBlocks(1)
|
||||||
|
|
||||||
// Since Alice had numInvoices (6) htlcs extended to Carol before force
|
// Since Alice had numInvoices (6) htlcs extended to Carol before force
|
||||||
// closing, we expect Alice to broadcast an htlc timeout txn for each
|
// closing, we expect Alice to broadcast an htlc timeout txn for each
|
||||||
// one.
|
// one. We also expect Alice to still have her anchor since it's not
|
||||||
ht.AssertNumPendingSweeps(alice, numInvoices)
|
// swept.
|
||||||
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
|
|
||||||
// Wait for them all to show up in the mempool
|
// Wait for them all to show up in the mempool
|
||||||
//
|
htlcTxIDs := ht.AssertNumTxsInMempool(1)
|
||||||
// NOTE: after restart, all the htlc timeout txns will be offered to
|
|
||||||
// the sweeper with `Immediate` set to true, so they won't be
|
|
||||||
// aggregated.
|
|
||||||
htlcTxIDs := ht.AssertNumTxsInMempool(numInvoices)
|
|
||||||
|
|
||||||
// Retrieve each htlc timeout txn from the mempool, and ensure it is
|
// Retrieve each htlc timeout txn from the mempool, and ensure it is
|
||||||
// well-formed. This entails verifying that each only spends from
|
// well-formed. The sweeping tx should spend all the htlc outputs.
|
||||||
// output, and that output is from the commitment txn.
|
//
|
||||||
numInputs := 2
|
// NOTE: We also add 1 output as the outgoing HTLC is swept using twice
|
||||||
|
// its value as its budget, so a wallet utxo is used.
|
||||||
|
numInputs := 6 + 1
|
||||||
|
|
||||||
// Construct a map of the already confirmed htlc timeout outpoints,
|
// Construct a map of the already confirmed htlc timeout outpoints,
|
||||||
// that will count the number of times each is spent by the sweep txn.
|
// that will count the number of times each is spent by the sweep txn.
|
||||||
@@ -560,6 +491,8 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
var htlcTxOutpointSet = make(map[wire.OutPoint]int)
|
var htlcTxOutpointSet = make(map[wire.OutPoint]int)
|
||||||
|
|
||||||
var htlcLessFees uint64
|
var htlcLessFees uint64
|
||||||
|
|
||||||
|
//nolint:ll
|
||||||
for _, htlcTxID := range htlcTxIDs {
|
for _, htlcTxID := range htlcTxIDs {
|
||||||
// Fetch the sweep transaction, all input it's spending should
|
// Fetch the sweep transaction, all input it's spending should
|
||||||
// be from the commitment transaction which was broadcast
|
// be from the commitment transaction which was broadcast
|
||||||
@@ -652,10 +585,10 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Generate a block that mines the htlc timeout txns. Doing so now
|
// Generate a block that mines the htlc timeout txns. Doing so now
|
||||||
// activates the 2nd-stage CSV delayed outputs.
|
// activates the 2nd-stage CSV delayed outputs.
|
||||||
ht.MineBlocksAndAssertNumTxes(1, numInvoices)
|
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
|
|
||||||
// Alice is restarted here to ensure that she promptly moved the crib
|
// Alice is restarted here to ensure that her contract court properly
|
||||||
// outputs to the kindergarten bucket after the htlc timeout txns were
|
// handles the 2nd-stage sweeps after the htlc timeout txns were
|
||||||
// confirmed.
|
// confirmed.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
@@ -664,12 +597,19 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
currentHeight = int32(ht.CurrentHeight())
|
currentHeight = int32(ht.CurrentHeight())
|
||||||
ht.Logf("current height: %v, htlcCsvMaturityHeight=%v", currentHeight,
|
ht.Logf("current height: %v, htlcCsvMaturityHeight=%v", currentHeight,
|
||||||
htlcCsvMaturityHeight)
|
htlcCsvMaturityHeight)
|
||||||
numBlocks := int(htlcCsvMaturityHeight - uint32(currentHeight) - 2)
|
numBlocks := int(htlcCsvMaturityHeight - uint32(currentHeight) - 1)
|
||||||
ht.MineEmptyBlocks(numBlocks)
|
ht.MineBlocks(numBlocks)
|
||||||
|
|
||||||
// Restart Alice to ensure that she can recover from a failure before
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
// having graduated the htlc outputs in the kindergarten bucket.
|
|
||||||
ht.RestartNode(alice)
|
// Restart Alice to ensure that she can recover from a failure.
|
||||||
|
//
|
||||||
|
// TODO(yy): Skip this step for neutrino as it cannot recover the
|
||||||
|
// sweeping txns from the mempool. We need to also store the txns in
|
||||||
|
// the sweeper store to make it work for the neutrino case.
|
||||||
|
if !ht.IsNeutrinoBackend() {
|
||||||
|
ht.RestartNode(alice)
|
||||||
|
}
|
||||||
|
|
||||||
// Now that the channel has been fully swept, it should no longer show
|
// Now that the channel has been fully swept, it should no longer show
|
||||||
// incubated, check to see that Alice's node still reports the channel
|
// incubated, check to see that Alice's node still reports the channel
|
||||||
@@ -687,55 +627,13 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
require.NoError(ht, err, "timeout while checking force closed channel")
|
require.NoError(ht, err, "timeout while checking force closed channel")
|
||||||
|
|
||||||
// Generate a block that causes Alice to sweep the htlc outputs in the
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
// kindergarten bucket.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
ht.AssertNumPendingSweeps(alice, numInvoices)
|
|
||||||
|
|
||||||
// Mine a block to trigger the sweep.
|
|
||||||
ht.MineEmptyBlocks(1)
|
|
||||||
|
|
||||||
// A temp hack to ensure the CI is not blocking the current
|
|
||||||
// development. There's a known issue in block sync among different
|
|
||||||
// subsystems, which is scheduled to be fixed in 0.18.1.
|
|
||||||
if ht.IsNeutrinoBackend() {
|
|
||||||
// We expect the htlcs to be aggregated into one tx. However,
|
|
||||||
// due to block sync issue, they may end up in two txns. Here
|
|
||||||
// we assert that there are two txns found in the mempool - if
|
|
||||||
// succeeded, it means the aggregation failed, and we won't
|
|
||||||
// continue the test.
|
|
||||||
//
|
|
||||||
// NOTE: we don't check `len(mempool) == 1` because it will
|
|
||||||
// give us false positive.
|
|
||||||
err := wait.NoError(func() error {
|
|
||||||
mempool := ht.Miner().GetRawMempool()
|
|
||||||
if len(mempool) == 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("expected 2 txes in mempool, found "+
|
|
||||||
"%d", len(mempool))
|
|
||||||
}, lntest.DefaultTimeout)
|
|
||||||
ht.Logf("Assert num of txns got %v", err)
|
|
||||||
|
|
||||||
// If there are indeed two txns found in the mempool, we won't
|
|
||||||
// continue the test.
|
|
||||||
if err == nil {
|
|
||||||
ht.Log("Neutrino backend failed to aggregate htlc " +
|
|
||||||
"sweeps!")
|
|
||||||
|
|
||||||
// Clean the mempool.
|
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the single sweep txn to appear in the mempool.
|
// Wait for the single sweep txn to appear in the mempool.
|
||||||
htlcSweepTxID := ht.AssertNumTxsInMempool(1)[0]
|
htlcSweepTxid := ht.AssertNumTxsInMempool(1)[0]
|
||||||
|
|
||||||
// Fetch the htlc sweep transaction from the mempool.
|
// Fetch the htlc sweep transaction from the mempool.
|
||||||
htlcSweepTx := ht.GetRawTransaction(htlcSweepTxID)
|
htlcSweepTx := ht.GetRawTransaction(htlcSweepTxid)
|
||||||
|
|
||||||
// Ensure the htlc sweep transaction only has one input for each htlc
|
// Ensure the htlc sweep transaction only has one input for each htlc
|
||||||
// Alice extended before force closing.
|
// Alice extended before force closing.
|
||||||
@@ -747,6 +645,7 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
// Ensure that each output spends from exactly one htlc timeout output.
|
// Ensure that each output spends from exactly one htlc timeout output.
|
||||||
for _, txIn := range htlcSweepTx.MsgTx().TxIn {
|
for _, txIn := range htlcSweepTx.MsgTx().TxIn {
|
||||||
outpoint := txIn.PreviousOutPoint
|
outpoint := txIn.PreviousOutPoint
|
||||||
|
|
||||||
// Check that the input is a confirmed htlc timeout txn.
|
// Check that the input is a confirmed htlc timeout txn.
|
||||||
_, ok := htlcTxOutpointSet[outpoint]
|
_, ok := htlcTxOutpointSet[outpoint]
|
||||||
require.Truef(ht, ok, "htlc sweep output not spending from "+
|
require.Truef(ht, ok, "htlc sweep output not spending from "+
|
||||||
@@ -784,11 +683,11 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Check that we can find the htlc sweep in our set of sweeps using
|
// Check that we can find the htlc sweep in our set of sweeps using
|
||||||
// the verbose output of the listsweeps output.
|
// the verbose output of the listsweeps output.
|
||||||
ht.AssertSweepFound(alice, htlcSweepTx.Hash().String(), true, 0)
|
ht.AssertSweepFound(alice, htlcSweepTxid.String(), true, 0)
|
||||||
|
|
||||||
// The following restart checks to ensure that the nursery store is
|
// The following restart checks to ensure that the sweeper is storing
|
||||||
// storing the txid of the previously broadcast htlc sweep txn, and
|
// the txid of the previously broadcast htlc sweep txn, and that it
|
||||||
// that it begins watching that txid after restarting.
|
// begins watching that txid after restarting.
|
||||||
ht.RestartNode(alice)
|
ht.RestartNode(alice)
|
||||||
|
|
||||||
// Now that the channel has been fully swept, it should no longer show
|
// Now that the channel has been fully swept, it should no longer show
|
||||||
@@ -804,7 +703,7 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = checkPendingHtlcStageAndMaturity(
|
err = checkPendingHtlcStageAndMaturity(
|
||||||
forceClose, 2, htlcCsvMaturityHeight-1, -1,
|
forceClose, 2, htlcCsvMaturityHeight-1, 0,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -817,7 +716,7 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
// Generate the final block that sweeps all htlc funds into the user's
|
// Generate the final block that sweeps all htlc funds into the user's
|
||||||
// wallet, and make sure the sweep is in this block.
|
// wallet, and make sure the sweep is in this block.
|
||||||
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
ht.AssertTxInBlock(block, htlcSweepTxID)
|
ht.AssertTxInBlock(block, htlcSweepTxid)
|
||||||
|
|
||||||
// Now that the channel has been fully swept, it should no longer show
|
// Now that the channel has been fully swept, it should no longer show
|
||||||
// up within the pending channels RPC.
|
// up within the pending channels RPC.
|
||||||
@@ -846,12 +745,6 @@ func channelForceClosureTest(ht *lntest.HarnessTest,
|
|||||||
carolExpectedBalance := btcutil.Amount(carolStartingBalance) +
|
carolExpectedBalance := btcutil.Amount(carolStartingBalance) +
|
||||||
pushAmt - totalFeeCarol
|
pushAmt - totalFeeCarol
|
||||||
|
|
||||||
// In addition, if this is an anchor-enabled channel, further add the
|
|
||||||
// anchor size.
|
|
||||||
if lntest.CommitTypeHasAnchors(channelType) {
|
|
||||||
carolExpectedBalance += btcutil.Amount(anchorSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Equal(ht, carolExpectedBalance,
|
require.Equal(ht, carolExpectedBalance,
|
||||||
btcutil.Amount(carolBalResp.ConfirmedBalance),
|
btcutil.Amount(carolBalResp.ConfirmedBalance),
|
||||||
"carol's balance is incorrect")
|
"carol's balance is incorrect")
|
||||||
|
|||||||
+18
-1
@@ -1660,6 +1660,22 @@ func (h *HarnessTest) CleanupForceClose(hn *node.HarnessNode) {
|
|||||||
// Wait for the channel to be marked pending force close.
|
// Wait for the channel to be marked pending force close.
|
||||||
h.AssertNumPendingForceClose(hn, 1)
|
h.AssertNumPendingForceClose(hn, 1)
|
||||||
|
|
||||||
|
// Mine enough blocks for the node to sweep its funds from the force
|
||||||
|
// closed channel. The commit sweep resolver is offers the input to the
|
||||||
|
// sweeper when it's force closed, and broadcast the sweep tx at
|
||||||
|
// defaulCSV-1.
|
||||||
|
//
|
||||||
|
// NOTE: we might empty blocks here as we don't know the exact number
|
||||||
|
// of blocks to mine. This may end up mining more blocks than needed.
|
||||||
|
h.MineEmptyBlocks(node.DefaultCSV - 1)
|
||||||
|
|
||||||
|
// Assert there is one pending sweep.
|
||||||
|
h.AssertNumPendingSweeps(hn, 1)
|
||||||
|
|
||||||
|
// The node should now sweep the funds, clean up by mining the sweeping
|
||||||
|
// tx.
|
||||||
|
h.MineBlocksAndAssertNumTxes(1, 1)
|
||||||
|
|
||||||
// Mine blocks to get any second level HTLC resolved. If there are no
|
// Mine blocks to get any second level HTLC resolved. If there are no
|
||||||
// HTLCs, this will behave like h.AssertNumPendingCloseChannels.
|
// HTLCs, this will behave like h.AssertNumPendingCloseChannels.
|
||||||
h.mineTillForceCloseResolved(hn)
|
h.mineTillForceCloseResolved(hn)
|
||||||
@@ -2001,7 +2017,8 @@ func (h *HarnessTest) AssertSweepFound(hn *node.HarnessNode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("sweep tx %v not found", sweep)
|
return fmt.Errorf("sweep tx %v not found in resp %v", sweep,
|
||||||
|
sweepResp)
|
||||||
}, wait.DefaultTimeout)
|
}, wait.DefaultTimeout)
|
||||||
require.NoError(h, err, "%s: timeout checking sweep tx", hn.Name())
|
require.NoError(h, err, "%s: timeout checking sweep tx", hn.Name())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,8 @@ func (h *HarnessTest) mineTillForceCloseResolved(hn *node.HarnessNode) {
|
|||||||
return nil
|
return nil
|
||||||
}, DefaultTimeout)
|
}, DefaultTimeout)
|
||||||
|
|
||||||
require.NoErrorf(h, err, "assert force close resolved timeout")
|
require.NoErrorf(h, err, "%s: assert force close resolved timeout",
|
||||||
|
hn.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssertTxInMempool asserts a given transaction can be found in the mempool.
|
// AssertTxInMempool asserts a given transaction can be found in the mempool.
|
||||||
|
|||||||
Reference in new issue
Block a user