diff --git a/lntemp/harness.go b/lntemp/harness.go index dfc8a6b27..0350ef995 100644 --- a/lntemp/harness.go +++ b/lntemp/harness.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -595,6 +596,14 @@ func (h *HarnessTest) SetFeeEstimate(fee chainfee.SatPerKWeight) { h.feeService.SetFeeRate(fee, 1) } +// SetFeeEstimateWithConf sets a fee rate of a specified conf target to be +// returned from fee estimator. +func (h *HarnessTest) SetFeeEstimateWithConf( + fee chainfee.SatPerKWeight, conf uint32) { + + h.feeService.SetFeeRate(fee, conf) +} + // validateNodeState checks that the node doesn't have any uncleaned states // which will affect its following tests. func (h *HarnessTest) validateNodeState(hn *node.HarnessNode) { @@ -1475,3 +1484,42 @@ func (h *HarnessTest) ReceiveInvoiceUpdate( return nil } + +// CalculateTxFee retrieves parent transactions and reconstructs the fee paid. +func (h *HarnessTest) CalculateTxFee(tx *wire.MsgTx) btcutil.Amount { + var balance btcutil.Amount + for _, in := range tx.TxIn { + parentHash := in.PreviousOutPoint.Hash + rawTx := h.Miner.GetRawTransaction(&parentHash) + parent := rawTx.MsgTx() + balance += btcutil.Amount( + parent.TxOut[in.PreviousOutPoint.Index].Value, + ) + } + + for _, out := range tx.TxOut { + balance -= btcutil.Amount(out.Value) + } + + return balance +} + +// CalculateTxesFeeRate takes a list of transactions and estimates the fee rate +// used to sweep them. +// +// NOTE: only used in current test file. +func (h *HarnessTest) CalculateTxesFeeRate(txns []*wire.MsgTx) int64 { + const scale = 1000 + + var totalWeight, totalFee int64 + for _, tx := range txns { + utx := btcutil.NewTx(tx) + totalWeight += blockchain.GetTransactionWeight(utx) + + fee := h.CalculateTxFee(tx) + totalFee += int64(fee) + } + feeRate := totalFee * scale / totalWeight + + return feeRate +} diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 9bbd507d4..3dfe73ddd 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -207,4 +207,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "channel unsettled balance", TestFunc: testChannelUnsettledBalance, }, + { + Name: "commitment deadline", + TestFunc: testCommitmentTransactionDeadline, + }, } diff --git a/lntest/itest/lnd_channel_force_close_test.go b/lntest/itest/lnd_channel_force_close_test.go index 0d7c4e4e2..a0767dcf1 100644 --- a/lntest/itest/lnd_channel_force_close_test.go +++ b/lntest/itest/lnd_channel_force_close_test.go @@ -14,6 +14,8 @@ import ( "github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" @@ -30,9 +32,7 @@ import ( // // Note that whether the deadline is used or not is implicitly checked by its // corresponding fee rates. -func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, - t *harnessTest) { - +func testCommitmentTransactionDeadline(ht *lntemp.HarnessTest) { // Get the default max fee rate used in sweeping the commitment // transaction. defaultMax := lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte @@ -65,27 +65,27 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // transaction to CPFP our commitment transaction. feeRateLarge := maxPerKw * 2 - ctxb := context.Background() - // 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) + ht.SetFeeEstimate(feeRateDefault) // setupNode creates a new node and sends 1 btc to the node. - setupNode := func(name string) *lntest.HarnessNode { + setupNode := func(name string) *node.HarnessNode { // Create the node. args := []string{"--hodl.exit-settle"} - args = append(args, nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)...) - node := net.NewNode(t.t, name, args) + args = append(args, nodeArgsForCommitType( + lnrpc.CommitmentType_ANCHORS)..., + ) + node := ht.NewNode(name, args) // Send some coins to the node. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, node) + ht.FundCoins(btcutil.SatoshiPerBitcoin, node) // For neutrino backend, we need one additional UTXO to create // the sweeping tx for the remote anchor. - if net.BackendCfg.Name() == lntest.NeutrinoBackendName { - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, node) + if ht.IsNeutrinoBackend() { + ht.FundCoins(btcutil.SatoshiPerBitcoin, node) } return node @@ -96,17 +96,17 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, calculateSweepFeeRate := func(expectedSweepTxNum int) int64 { // Create two nodes, Alice and Bob. alice := setupNode("Alice") - defer shutdownAndAssert(net, t, alice) + defer ht.Shutdown(alice) bob := setupNode("Bob") - defer shutdownAndAssert(net, t, bob) + defer ht.Shutdown(bob) // Connect Alice to Bob. - net.ConnectNodes(t.t, alice, bob) + ht.ConnectNodes(alice, bob) // Open a channel between Alice and Bob. - chanPoint := openChannelAndAssert( - t, net, alice, bob, lntest.OpenChannelParams{ + chanPoint := ht.OpenChannel( + alice, bob, lntemp.OpenChannelParams{ Amt: 10e6, PushAmt: 5e6, }, @@ -115,64 +115,38 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // Send a payment with a specified finalCTLVDelta, which will // be used as our deadline later on when Alice force closes the // channel. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - _, 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") + req := &routerrpc.SendPaymentRequest{ + Dest: bob.PubKey[:], + Amt: 10e4, + PaymentHash: ht.Random32Bytes(), + FinalCltvDelta: finalCTLV, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + alice.RPC.SendPayment(req) // 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") + ht.AssertNumActiveHtlcs(alice, 1) + ht.AssertNumActiveHtlcs(bob, 1) // Alice force closes the channel. - _, _, err = net.CloseChannel(alice, chanPoint, true) - require.NoError(t.t, err, "unable to force close channel") + ht.CloseChannelAssertPending(alice, chanPoint, true) // Now that the channel has been force closed, it should show // up in the PendingChannels RPC under the waiting close // section. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - 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), - ) + ht.AssertChannelWaitingClose(alice, chanPoint) // Check our sweep transactions can be found in mempool. - sweepTxns, err := getNTxsFromMempool( - net.Miner.Client, - expectedSweepTxNum, minerMempoolTimeout, - ) - require.NoError( - t.t, err, "failed to find commitment tx in mempool", - ) + sweepTxns := ht.Miner.GetNumTxsFromMempool(expectedSweepTxNum) // 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") + ht.MineBlocks(1) // Calculate the fee rate used. - feeRate := calculateTxnsFeeRate(t.t, net.Miner, sweepTxns) + feeRate := ht.CalculateTxesFeeRate(sweepTxns) return feeRate } @@ -180,7 +154,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // 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) + ht.SetFeeEstimateWithConf(feeRateSmall, deadline) // Calculate fee rate used. feeRate := calculateSweepFeeRate(1) @@ -188,7 +162,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // 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, + ht, int64(maxPerKw), feeRate, 0.01, "expected fee rate:%d, got fee rate:%d", maxPerKw, feeRate, ) @@ -196,7 +170,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // 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) + ht.SetFeeEstimateWithConf(feeRateLarge, deadline) // Calculate fee rate used. feeRate = calculateSweepFeeRate(2) @@ -204,31 +178,11 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness, // 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, + ht, 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 *lntest.HarnessMiner, 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 -} - // testChannelForceClosure 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 diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 70b3d6371..2323ed5d1 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -126,10 +126,6 @@ var allTestCases = []*testCase{ name: "hold invoice force close", test: testHoldInvoiceForceClose, }, - { - name: "commitment deadline", - test: testCommitmentTransactionDeadline, - }, { name: "cpfp", test: testCPFP,