itest: test anchor sweep with commitment deadline

This commit is contained in:
yyforyongyu 2021-06-04 06:13:34 +08:00
parent 26858da59d
commit 7c64e0445a
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
2 changed files with 226 additions and 1 deletions

@ -0,0 +1,222 @@
package itest
import (
"context"
"testing"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/routing"
"github.com/stretchr/testify/require"
)
// TODO(yy): move channel force closed related tests into this file.
// testCommitmentTransactionDeadline tests that the anchor sweep transaction is
// taking account of the deadline of the commitment transaction. It tests two
// scenarios:
// 1) when the CPFP is skipped, checks that the deadline is not used.
// 2) when the CPFP is used, checks that the deadline is applied.
// Note that whether the deadline is used or not is implicitly checked by its
// corresponding fee rates.
func testCommitmentTransactionDeadline(net *lntest.NetworkHarness,
t *harnessTest) {
// Get the default max fee rate used in sweeping the commitment
// transaction.
defaultMax := lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte
maxPerKw := chainfee.SatPerKVByte(defaultMax * 1000).FeePerKWeight()
const (
// feeRateConfDefault(sat/kw) is used when no conf target is
// set. This value will be returned by the fee estimator but
// won't be used because our commitment fee rate is capped by
// DefaultAnchorsCommitMaxFeeRateSatPerVByte.
feeRateDefault = 20000
// finalCTLV is used when Alice sends payment to Bob.
finalCTLV = 144
// deadline is used when Alice sweep the anchor. Notice there
// is a block padding of 3 added, such that the value of
// deadline is 147.
deadline = uint32(finalCTLV + routing.BlockPadding)
)
// feeRateSmall(sat/kw) is used when we want to skip the CPFP
// on anchor transactions. When the fee rate is smaller than
// the parent's (commitment transaction) fee rate, the CPFP
// will be skipped. Atm, the parent tx's fee rate is roughly
// 2500 sat/kw in this test.
feeRateSmall := maxPerKw / 2
// feeRateLarge(sat/kw) is used when we want to use the anchor
// transaction to CPFP our commitment transaction.
feeRateLarge := maxPerKw * 2
ctxt, cancel := context.WithTimeout(
context.Background(), defaultTimeout,
)
defer cancel()
// Before we start, set up the default fee rate and we will test the
// actual fee rate against it to decide whether we are using the
// deadline to perform fee estimation.
net.SetFeeEstimate(feeRateDefault)
// setupNode creates a new node and sends 1 btc to the node.
setupNode := func(name string) *lntest.HarnessNode {
// Create the node.
args := []string{"--hodl.exit-settle"}
args = append(args, commitTypeAnchors.Args()...)
node := net.NewNode(t.t, name, args)
// Send some coins to the node.
net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, node)
return node
}
// calculateSweepFeeRate runs multiple steps to calculate the fee rate
// used in sweeping the transactions.
calculateSweepFeeRate := func(expectedSweepTxNum int) int64 {
// Create two nodes, Alice and Bob.
alice := setupNode("Alice")
defer shutdownAndAssert(net, t, alice)
bob := setupNode("Bob")
defer shutdownAndAssert(net, t, bob)
// Connect Alice to Bob.
net.ConnectNodes(ctxt, t.t, alice, bob)
// Open a channel between Alice and Bob.
chanPoint := openChannelAndAssert(
ctxt, t, net, alice, bob,
lntest.OpenChannelParams{
Amt: 10e6,
PushAmt: 5e6,
},
)
// Send a payment with a specified finalCTLVDelta, which will
// be used as our deadline later on when Alice force closes the
// channel.
_, err := alice.RouterClient.SendPaymentV2(
ctxt,
&routerrpc.SendPaymentRequest{
Dest: bob.PubKey[:],
Amt: 10e4,
PaymentHash: makeFakePayHash(t),
FinalCltvDelta: finalCTLV,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
require.NoError(t.t, err, "unable to send alice htlc")
// Once the HTLC has cleared, all the nodes in our mini network
// should show that the HTLC has been locked in.
nodes := []*lntest.HarnessNode{alice, bob}
err = wait.NoError(func() error {
return assertNumActiveHtlcs(nodes, 1)
}, defaultTimeout)
require.NoError(t.t, err, "htlc mismatch")
// Alice force closes the channel.
_, _, err = net.CloseChannel(ctxt, alice, chanPoint, true)
require.NoError(t.t, err, "unable to force close channel")
// Now that the channel has been force closed, it should show
// up in the PendingChannels RPC under the waiting close
// section.
pendingChansRequest := &lnrpc.PendingChannelsRequest{}
pendingChanResp, err := alice.PendingChannels(
ctxt, pendingChansRequest,
)
require.NoError(
t.t, err, "unable to query for pending channels",
)
require.NoError(
t.t, checkNumWaitingCloseChannels(pendingChanResp, 1),
)
// We should see only one sweep transaction because the anchor
// sweep is skipped.
sweepTxns, err := getNTxsFromMempool(
net.Miner.Client,
expectedSweepTxNum, minerMempoolTimeout,
)
require.NoError(
t.t, err, "failed to find commitment tx in mempool",
)
// Mine a block to confirm these transactions such that they
// don't remain in the mempool for any subsequent tests.
_, err = net.Miner.Client.Generate(1)
require.NoError(t.t, err, "unable to mine blocks")
// Calculate the fee rate used.
feeRate := calculateTxnsFeeRate(t.t, net.Miner, sweepTxns)
return feeRate
}
// Setup our fee estimation for the deadline. Because the fee rate is
// smaller than the parent tx's fee rate, this value won't be used and
// we should see only one sweep tx in the mempool.
net.SetFeeEstimateWithConf(feeRateSmall, deadline)
// Calculate fee rate used.
feeRate := calculateSweepFeeRate(1)
// We expect the default max fee rate is used. Allow some deviation
// because weight estimates during tx generation are estimates.
require.InEpsilonf(
t.t, int64(maxPerKw), feeRate, 0.01,
"expected fee rate:%d, got fee rate:%d", maxPerKw, feeRate,
)
// Setup our fee estimation for the deadline. Because the fee rate is
// greater than the parent tx's fee rate, this value will be used to
// sweep the anchor transaction and we should see two sweep
// transactions in the mempool.
net.SetFeeEstimateWithConf(feeRateLarge, deadline)
// Calculate fee rate used.
feeRate = calculateSweepFeeRate(2)
// We expect the anchor to be swept with the deadline, which has the
// fee rate of feeRateLarge.
require.InEpsilonf(
t.t, int64(feeRateLarge), feeRate, 0.01,
"expected fee rate:%d, got fee rate:%d", feeRateLarge, feeRate,
)
}
// calculateTxnsFeeRate takes a list of transactions and estimates the fee rate
// used to sweep them.
func calculateTxnsFeeRate(t *testing.T,
miner *rpctest.Harness, txns []*wire.MsgTx) int64 {
var totalWeight, totalFee int64
for _, tx := range txns {
utx := btcutil.NewTx(tx)
totalWeight += blockchain.GetTransactionWeight(utx)
fee, err := getTxFee(miner.Client, tx)
require.NoError(t, err)
totalFee += int64(fee)
}
feeRate := totalFee * 1000 / totalWeight
return feeRate
}

@ -111,7 +111,6 @@ var allTestCases = []*testCase{
name: "private channel update policy",
test: testUpdateChannelPolicyForPrivateChannel,
},
{
name: "invoice routing hints",
test: testInvoiceRoutingHints,
@ -239,6 +238,10 @@ var allTestCases = []*testCase{
name: "hold invoice force close",
test: testHoldInvoiceForceClose,
},
{
name: "commitment deadline",
test: testCommitmentTransactionDeadline,
},
{
name: "cpfp",
test: testCPFP,