mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-15 03:51:23 +01:00
itest: add new itests to check CPFP anchor sweeping behavior
Replaced `testSweepAnchorCPFPLocalForceClose` with dedicated tests.
This commit is contained in:
parent
38184e88c8
commit
e68c0235c6
2 changed files with 547 additions and 166 deletions
|
@ -611,8 +611,12 @@ var allTestCases = []*lntest.TestCase{
|
||||||
TestFunc: testNativeSQLNoMigration,
|
TestFunc: testNativeSQLNoMigration,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "sweep anchor cpfp local force close",
|
Name: "sweep cpfp anchor outgoing timeout",
|
||||||
TestFunc: testSweepAnchorCPFPLocalForceClose,
|
TestFunc: testSweepCPFPAnchorOutgoingTimeout,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "sweep cpfp anchor incoming timeout",
|
||||||
|
TestFunc: testSweepCPFPAnchorIncomingTimeout,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "sweep htlcs",
|
Name: "sweep htlcs",
|
||||||
|
|
|
@ -24,126 +24,159 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testSweepAnchorCPFPLocalForceClose checks when a channel is force closed by
|
// testSweepCPFPAnchorOutgoingTimeout checks when a channel is force closed by
|
||||||
// a local node with a time-sensitive HTLC, the anchor output is used for
|
// a local node due to the outgoing HTLC times out, the anchor output is used
|
||||||
// CPFPing the force close tx.
|
// for CPFPing the force close tx.
|
||||||
//
|
//
|
||||||
// Setup:
|
// Setup:
|
||||||
// 1. Fund Alice with 2 UTXOs - she will need two to sweep her anchors from
|
// 1. Fund Alice with 1 UTXO - she only needs one for the funding process,
|
||||||
// the local and remote commitments, with one of them being invalid.
|
// 2. Fund Bob with 1 UTXO - he only needs one for the funding process, and
|
||||||
// 2. Fund Bob with no UTXOs - his sweeping txns don't need wallet utxos as he
|
// the change output will be used for sweeping his anchor on local commit.
|
||||||
// doesn't need to sweep any time-sensitive outputs.
|
// 3. Create a linear network from Alice -> Bob -> Carol.
|
||||||
// 3. Alice opens a channel with Bob, and sends him an HTLC without being
|
// 4. Alice pays an invoice to Carol through Bob, with Carol holding the
|
||||||
// settled - we achieve this by letting Bob hold the preimage, which means
|
// settlement.
|
||||||
// he will consider his incoming HTLC has no preimage.
|
// 5. Carol goes offline.
|
||||||
// 4. Alice force closes the channel.
|
|
||||||
//
|
//
|
||||||
// Test:
|
// Test:
|
||||||
// 1. Alice's force close tx should be CPFPed using the anchor output.
|
// 1. Bob force closes the channel with Carol, using the anchor output for
|
||||||
// 2. Bob attempts to sweep his anchor output and fails due to it's
|
// CPFPing the force close tx.
|
||||||
// uneconomical.
|
// 2. Bob's anchor output is swept and fee bumped based on its deadline and
|
||||||
// 3. Alice's RBF attempt is using the fee rates calculated from the deadline
|
// budget.
|
||||||
// and budget.
|
func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) {
|
||||||
// 4. Wallet UTXOs requirements are met - for Alice she needs at least 2, and
|
// Setup testing params.
|
||||||
// Bob he needs none.
|
|
||||||
func testSweepAnchorCPFPLocalForceClose(ht *lntest.HarnessTest) {
|
|
||||||
// Setup testing params for Alice.
|
|
||||||
//
|
//
|
||||||
// startFeeRate is returned by the fee estimator in sat/kw. This
|
// Invoice is 100k sats.
|
||||||
// will be used as the starting fee rate for the linear fee func used
|
invoiceAmt := btcutil.Amount(100_000)
|
||||||
// by Alice.
|
|
||||||
startFeeRate := chainfee.SatPerKWeight(2000)
|
|
||||||
|
|
||||||
// deadline is the expected deadline for the CPFP transaction.
|
// Use the smallest CLTV so we can mine fewer blocks.
|
||||||
deadline := uint32(10)
|
cltvDelta := routing.MinCLTVDelta
|
||||||
|
|
||||||
|
// deadlineDeltaAnchor is the expected deadline delta for the CPFP
|
||||||
|
// anchor sweeping tx.
|
||||||
|
deadlineDeltaAnchor := uint32(cltvDelta / 2)
|
||||||
|
|
||||||
|
// startFeeRateAnchor is the starting fee rate for the CPFP anchor
|
||||||
|
// sweeping tx.
|
||||||
|
startFeeRateAnchor := chainfee.SatPerKWeight(2500)
|
||||||
|
|
||||||
// Set up the fee estimator to return the testing fee rate when the
|
// Set up the fee estimator to return the testing fee rate when the
|
||||||
// conf target is the deadline.
|
// conf target is the deadline.
|
||||||
ht.SetFeeEstimateWithConf(startFeeRate, deadline)
|
|
||||||
|
|
||||||
// Calculate the final ctlv delta based on the expected deadline.
|
|
||||||
finalCltvDelta := int32(deadline - uint32(routing.BlockPadding) + 1)
|
|
||||||
|
|
||||||
// toLocalCSV is the CSV delay for Alice's to_local output. This value
|
|
||||||
// is chosen so the commit sweep happens after the anchor sweep,
|
|
||||||
// enabling us to focus on checking the fees in CPFP here.
|
|
||||||
toLocalCSV := deadline * 2
|
|
||||||
|
|
||||||
// htlcAmt is the amount of the HTLC in sats. With default settings,
|
|
||||||
// this will give us 25000 sats as the budget to sweep the CPFP anchor
|
|
||||||
// output.
|
|
||||||
htlcAmt := btcutil.Amount(100_000)
|
|
||||||
|
|
||||||
// Calculate the budget. Since it's a time-sensitive HTLC, we will use
|
|
||||||
// its value after subtracting its own budget as the CPFP budget.
|
|
||||||
valueLeft := htlcAmt.MulF64(1 - contractcourt.DefaultBudgetRatio)
|
|
||||||
budget := valueLeft.MulF64(1 - contractcourt.DefaultBudgetRatio)
|
|
||||||
|
|
||||||
// We now set up testing params for Bob.
|
|
||||||
//
|
//
|
||||||
// bobBalance is the push amount when Alice opens the channel with Bob.
|
// TODO(yy): switch to conf when `blockbeat` is in place.
|
||||||
// We will use zero here so we can focus on testing the CPFP logic from
|
// ht.SetFeeEstimateWithConf(startFeeRateAnchor, deadlineDeltaAnchor)
|
||||||
// Alice's side here.
|
ht.SetFeeEstimate(startFeeRateAnchor)
|
||||||
bobBalance := btcutil.Amount(0)
|
|
||||||
|
|
||||||
// Make sure our assumptions and calculations are correct.
|
// htlcValue is the outgoing HTLC's value.
|
||||||
require.EqualValues(ht, 25000, budget)
|
htlcValue := invoiceAmt
|
||||||
|
|
||||||
// We now set up the force close scenario. Alice will open a channel
|
// htlcBudget is the budget used to sweep the outgoing HTLC.
|
||||||
// with Bob, send an HTLC, and then force close it with a
|
htlcBudget := htlcValue.MulF64(contractcourt.DefaultBudgetRatio)
|
||||||
// time-sensitive outgoing HTLC.
|
|
||||||
|
// cpfpBudget is the budget used to sweep the CPFP anchor.
|
||||||
|
cpfpBudget := (htlcValue - htlcBudget).MulF64(
|
||||||
|
contractcourt.DefaultBudgetRatio,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a preimage, that will be held by Carol.
|
||||||
|
var preimage lntypes.Preimage
|
||||||
|
copy(preimage[:], ht.Random32Bytes())
|
||||||
|
payHash := preimage.Hash()
|
||||||
|
|
||||||
|
// We now set up the force close scenario. We will create a network
|
||||||
|
// from Alice -> Bob -> Carol, where Alice will send a payment to Carol
|
||||||
|
// via Bob, Carol goes offline. We expect Bob to sweep his anchor and
|
||||||
|
// outgoing HTLC.
|
||||||
//
|
//
|
||||||
// Prepare node params.
|
// Prepare params.
|
||||||
cfg := []string{
|
cfg := []string{
|
||||||
"--hodl.exit-settle",
|
|
||||||
"--protocol.anchors",
|
"--protocol.anchors",
|
||||||
|
// Use a small CLTV to mine less blocks.
|
||||||
|
fmt.Sprintf("--bitcoin.timelockdelta=%d", cltvDelta),
|
||||||
// Use a very large CSV, this way to_local outputs are never
|
// Use a very large CSV, this way to_local outputs are never
|
||||||
// swept so we can focus on testing HTLCs.
|
// swept so we can focus on testing HTLCs.
|
||||||
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
|
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", cltvDelta*10),
|
||||||
}
|
}
|
||||||
openChannelParams := lntest.OpenChannelParams{
|
openChannelParams := lntest.OpenChannelParams{
|
||||||
Amt: htlcAmt * 10,
|
Amt: invoiceAmt * 10,
|
||||||
PushAmt: bobBalance,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a two hop network: Alice -> Bob.
|
// Create a three hop network: Alice -> Bob -> Carol.
|
||||||
chanPoints, nodes := createSimpleNetwork(ht, cfg, 2, openChannelParams)
|
chanPoints, nodes := createSimpleNetwork(ht, cfg, 3, openChannelParams)
|
||||||
|
|
||||||
// Unwrap the results.
|
// Unwrap the results.
|
||||||
chanPoint := chanPoints[0]
|
abChanPoint, bcChanPoint := chanPoints[0], chanPoints[1]
|
||||||
alice, bob := nodes[0], nodes[1]
|
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||||
|
|
||||||
// Send one more utxo to Alice - she will need two utxos to sweep the
|
// For neutrino backend, we need one more UTXO for Bob to create his
|
||||||
// anchor output living on the local and remote commits.
|
// sweeping txns.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
if ht.IsNeutrinoBackend() {
|
||||||
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
|
}
|
||||||
|
|
||||||
// Send a payment with a specified finalCTLVDelta, which will be used
|
// Subscribe the invoice.
|
||||||
// as our deadline later on when Alice force closes the channel.
|
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||||
|
|
||||||
|
// With the network active, we'll now add a hodl invoice at Carol's
|
||||||
|
// end.
|
||||||
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||||
|
Value: int64(invoiceAmt),
|
||||||
|
CltvExpiry: finalCltvDelta,
|
||||||
|
Hash: payHash[:],
|
||||||
|
}
|
||||||
|
invoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
||||||
|
|
||||||
|
// Let Alice pay the invoices.
|
||||||
req := &routerrpc.SendPaymentRequest{
|
req := &routerrpc.SendPaymentRequest{
|
||||||
Dest: bob.PubKey[:],
|
PaymentRequest: invoice.PaymentRequest,
|
||||||
Amt: int64(htlcAmt),
|
|
||||||
PaymentHash: ht.Random32Bytes(),
|
|
||||||
FinalCltvDelta: finalCltvDelta,
|
|
||||||
TimeoutSeconds: 60,
|
TimeoutSeconds: 60,
|
||||||
FeeLimitMsat: noFeeLimitMsat,
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
}
|
}
|
||||||
alice.RPC.SendPayment(req)
|
|
||||||
|
|
||||||
// Once the HTLC has cleared, all the nodes in our mini network should
|
// Assert the payments are inflight.
|
||||||
// show that the HTLC has been locked in.
|
ht.SendPaymentAndAssertStatus(alice, req, lnrpc.Payment_IN_FLIGHT)
|
||||||
ht.AssertNumActiveHtlcs(alice, 1)
|
|
||||||
ht.AssertNumActiveHtlcs(bob, 1)
|
|
||||||
|
|
||||||
// Alice force closes the channel.
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
||||||
_, closeTxid := ht.CloseChannelAssertPending(alice, chanPoint, true)
|
// bridge between adding the htlc to the channel and executing the exit
|
||||||
|
// hop logic.
|
||||||
|
ht.AssertInvoiceState(streamCarol, lnrpc.Invoice_ACCEPTED)
|
||||||
|
|
||||||
// Now that the channel has been force closed, it should show up in the
|
// At this point, all 3 nodes should now have an active channel with
|
||||||
// PendingChannels RPC under the waiting close section.
|
// the created HTLCs pending on all of them.
|
||||||
ht.AssertChannelWaitingClose(alice, chanPoint)
|
//
|
||||||
|
// Alice should have one outgoing HTLCs on channel Alice -> Bob.
|
||||||
|
ht.AssertOutgoingHTLCActive(alice, abChanPoint, payHash[:])
|
||||||
|
|
||||||
// Alice should have two pending sweeps,
|
// Bob should have one incoming HTLC on channel Alice -> Bob, and one
|
||||||
// - anchor sweeping from her local commitment.
|
// outgoing HTLC on channel Bob -> Carol.
|
||||||
// - anchor sweeping from her remote commitment (invalid).
|
ht.AssertIncomingHTLCActive(bob, abChanPoint, payHash[:])
|
||||||
|
ht.AssertOutgoingHTLCActive(bob, bcChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// Carol should have one incoming HTLC on channel Bob -> Carol.
|
||||||
|
ht.AssertIncomingHTLCActive(carol, bcChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// Let Carol go offline so we can focus on testing Bob's sweeping
|
||||||
|
// behavior.
|
||||||
|
ht.Shutdown(carol)
|
||||||
|
|
||||||
|
// We'll now mine enough blocks to trigger Bob to force close channel
|
||||||
|
// Bob->Carol due to his outgoing HTLC is about to timeout. With the
|
||||||
|
// default outgoing broadcast delta of zero, this will be the same
|
||||||
|
// height as the outgoing htlc's expiry height.
|
||||||
|
numBlocks := padCLTV(uint32(
|
||||||
|
invoiceReq.CltvExpiry - lncfg.DefaultOutgoingBroadcastDelta,
|
||||||
|
))
|
||||||
|
ht.MineEmptyBlocks(int(numBlocks))
|
||||||
|
|
||||||
|
// Assert Bob's force closing tx has been broadcast.
|
||||||
|
closeTxid := ht.Miner.AssertNumTxsInMempool(1)[0]
|
||||||
|
|
||||||
|
// Remember the force close height so we can calculate the deadline
|
||||||
|
// height.
|
||||||
|
_, forceCloseHeight := ht.Miner.GetBestBlock()
|
||||||
|
|
||||||
|
// Bob should have two pending sweeps,
|
||||||
|
// - anchor sweeping from his local commitment.
|
||||||
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
//
|
//
|
||||||
// TODO(yy): consider only sweeping the anchor from the local
|
// TODO(yy): consider only sweeping the anchor from the local
|
||||||
// commitment. Previously we would sweep up to three versions of
|
// commitment. Previously we would sweep up to three versions of
|
||||||
|
@ -152,108 +185,114 @@ func testSweepAnchorCPFPLocalForceClose(ht *lntest.HarnessTest) {
|
||||||
// their commitment tx and replaces ours. With the new fee bumping, we
|
// their commitment tx and replaces ours. With the new fee bumping, we
|
||||||
// should be safe to only sweep our local anchor since we RBF it on
|
// should be safe to only sweep our local anchor since we RBF it on
|
||||||
// every new block, which destroys the remote's ability to pin us.
|
// every new block, which destroys the remote's ability to pin us.
|
||||||
ht.AssertNumPendingSweeps(alice, 2)
|
sweeps := ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
// Bob should have no pending sweeps here. Although he learned about
|
// The two anchor sweeping should have the same deadline height.
|
||||||
// the force close tx, because he doesn't have any outgoing HTLCs, he
|
deadlineHeight := uint32(forceCloseHeight) + deadlineDeltaAnchor
|
||||||
// doesn't need to sweep anything.
|
require.Equal(ht, deadlineHeight, sweeps[0].DeadlineHeight)
|
||||||
ht.AssertNumPendingSweeps(bob, 0)
|
require.Equal(ht, deadlineHeight, sweeps[1].DeadlineHeight)
|
||||||
|
|
||||||
// Mine a block so Alice's force closing tx stays in the mempool, which
|
// Remember the deadline height for the CPFP anchor.
|
||||||
// also triggers the sweep.
|
anchorDeadline := sweeps[0].DeadlineHeight
|
||||||
|
|
||||||
|
// Mine a block so Bob's force closing tx stays in the mempool, which
|
||||||
|
// also triggers the CPFP anchor sweep.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
// TODO(yy): we should also handle the edge case where the force close
|
// Bob should still have two pending sweeps,
|
||||||
// tx confirms here - we should cancel the fee bumping attempt for this
|
// - anchor sweeping from his local commitment.
|
||||||
// anchor sweep and let it stay in mempool? Or should we unlease the
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
// wallet input and ask the sweeper to re-sweep the anchor?
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
// ht.MineBlocksAndAssertNumTxes(1, 1)
|
|
||||||
|
|
||||||
// We now check the expected fee and fee rate are used for Alice.
|
// We now check the expected fee and fee rate are used for Bob's anchor
|
||||||
|
// sweeping tx.
|
||||||
//
|
//
|
||||||
// We should see Alice's anchor sweeping tx triggered by the above
|
// We should see Bob's anchor sweeping tx triggered by the above
|
||||||
// block, along with Alice's force close tx.
|
// block, along with his force close tx.
|
||||||
txns := ht.Miner.GetNumTxsFromMempool(2)
|
txns := ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
// Find the sweeping tx.
|
// Find the sweeping tx.
|
||||||
sweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
sweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
// Get the weight for Alice's sweep tx.
|
// Get the weight for Bob's anchor sweeping tx.
|
||||||
txWeight := ht.CalculateTxWeight(sweepTx)
|
txWeight := ht.CalculateTxWeight(sweepTx)
|
||||||
|
|
||||||
// Calculate the fee and fee rate of Alice's sweeping tx.
|
// Bob should start with the initial fee rate of 2500 sat/kw.
|
||||||
|
startFeeAnchor := startFeeRateAnchor.FeeForWeight(txWeight)
|
||||||
|
|
||||||
|
// Calculate the fee and fee rate of Bob's sweeping tx.
|
||||||
fee := uint64(ht.CalculateTxFee(sweepTx))
|
fee := uint64(ht.CalculateTxFee(sweepTx))
|
||||||
feeRate := uint64(ht.CalculateTxFeeRate(sweepTx))
|
feeRate := uint64(ht.CalculateTxFeeRate(sweepTx))
|
||||||
|
|
||||||
// Alice should start with the initial fee rate of 2000 sat/kw.
|
// feeFuncWidth is the width of the fee function. By the time we got
|
||||||
startFee := startFeeRate.FeeForWeight(txWeight)
|
// here, we've already mined one block, and the fee function maxes
|
||||||
|
// out one block before the deadline, so the width is the original
|
||||||
|
// deadline minus 2.
|
||||||
|
feeFuncWidth := deadlineDeltaAnchor - 2
|
||||||
|
|
||||||
// Calculate the expected delta increased per block.
|
// Calculate the expected delta increased per block.
|
||||||
//
|
feeDelta := (cpfpBudget - startFeeAnchor).MulF64(
|
||||||
// NOTE: Assume a wallet tr output is used for fee bumping, with the tx
|
1 / float64(feeFuncWidth),
|
||||||
// weight of 725, we expect this value to be 2355.
|
)
|
||||||
feeDeltaAlice := (budget - startFee).MulF64(1 / float64(10))
|
|
||||||
|
|
||||||
// We expect the startingFee and startingFeeRate being used. Allow some
|
// We expect the startingFee and startingFeeRate being used. Allow some
|
||||||
// deviation because weight estimates during tx generation are
|
// deviation because weight estimates during tx generation are
|
||||||
// estimates.
|
// estimates.
|
||||||
//
|
//
|
||||||
// TODO(yy): unify all the units and types re int vs uint!
|
// TODO(yy): unify all the units and types re int vs uint!
|
||||||
require.InEpsilonf(ht, uint64(startFee), fee, 0.01,
|
require.InEpsilonf(ht, uint64(startFeeAnchor), fee, 0.01,
|
||||||
"want %d, got %d", startFee, fee)
|
"want %d, got %d", startFeeAnchor, fee)
|
||||||
require.InEpsilonf(ht, uint64(startFeeRate), feeRate,
|
require.InEpsilonf(ht, uint64(startFeeRateAnchor), feeRate,
|
||||||
0.01, "want %d, got %d", startFeeRate, fee)
|
0.01, "want %d, got %d", startFeeRateAnchor, fee)
|
||||||
|
|
||||||
// Bob has no time-sensitive outputs, so he should sweep nothing.
|
// We now mine deadline-2 empty blocks. For each block mined, Bob
|
||||||
ht.AssertNumPendingSweeps(bob, 0)
|
// should perform an RBF on his CPFP anchor sweeping tx. By the end of
|
||||||
|
// this iteration, we expect Bob to use up his CPFP budget after one
|
||||||
// We now mine deadline-1 empty blocks. For each block mined, Alice
|
// more block.
|
||||||
// should perform an RBF on her CPFP anchor sweeping tx. By the end of
|
for i := uint32(1); i <= feeFuncWidth-1; i++ {
|
||||||
// this iteration, we expect Alice to use start sweeping her htlc
|
|
||||||
// output after one more block.
|
|
||||||
for i := uint32(1); i <= deadline; i++ {
|
|
||||||
// Mine an empty block. Since the sweeping tx is not confirmed,
|
// Mine an empty block. Since the sweeping tx is not confirmed,
|
||||||
// Alice's fee bumper should increase its fees.
|
// Bob's fee bumper should increase its fees.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
// Alice should still have two pending sweeps,
|
// Bob should still have two pending sweeps,
|
||||||
// - anchor sweeping from her local commitment.
|
// - anchor sweeping from his local commitment.
|
||||||
// - anchor sweeping from her remote commitment (invalid).
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
ht.AssertNumPendingSweeps(alice, 2)
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
// We expect to see two txns in the mempool,
|
// Make sure Bob's old sweeping tx has been removed from the
|
||||||
// - Alice's force close tx.
|
|
||||||
// - Alice's anchor sweep tx.
|
|
||||||
ht.Miner.AssertNumTxsInMempool(2)
|
|
||||||
|
|
||||||
// Make sure Alice's old sweeping tx has been removed from the
|
|
||||||
// mempool.
|
// mempool.
|
||||||
ht.Miner.AssertTxNotInMempool(sweepTx.TxHash())
|
ht.Miner.AssertTxNotInMempool(sweepTx.TxHash())
|
||||||
|
|
||||||
|
// We expect to see two txns in the mempool,
|
||||||
|
// - Bob's force close tx.
|
||||||
|
// - Bob's anchor sweep tx.
|
||||||
|
ht.Miner.AssertNumTxsInMempool(2)
|
||||||
|
|
||||||
// We expect the fees to increase by i*delta.
|
// We expect the fees to increase by i*delta.
|
||||||
expectedFee := startFee + feeDeltaAlice.MulF64(float64(i))
|
expectedFee := startFeeAnchor + feeDelta.MulF64(float64(i))
|
||||||
expectedFeeRate := chainfee.NewSatPerKWeight(
|
expectedFeeRate := chainfee.NewSatPerKWeight(
|
||||||
expectedFee, uint64(txWeight),
|
expectedFee, uint64(txWeight),
|
||||||
)
|
)
|
||||||
|
|
||||||
// We should see Alice's anchor sweeping tx being fee bumped
|
// We should see Bob's anchor sweeping tx being fee bumped
|
||||||
// since it's not confirmed, along with her force close tx.
|
// since it's not confirmed, along with his force close tx.
|
||||||
txns = ht.Miner.GetNumTxsFromMempool(2)
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
// Find the sweeping tx.
|
// Find the sweeping tx.
|
||||||
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
// Calculate the fee rate of Alice's new sweeping tx.
|
// Calculate the fee rate of Bob's new sweeping tx.
|
||||||
feeRate = uint64(ht.CalculateTxFeeRate(sweepTx))
|
feeRate = uint64(ht.CalculateTxFeeRate(sweepTx))
|
||||||
|
|
||||||
// Calculate the fee of Alice's new sweeping tx.
|
// Calculate the fee of Bob's new sweeping tx.
|
||||||
fee = uint64(ht.CalculateTxFee(sweepTx))
|
fee = uint64(ht.CalculateTxFee(sweepTx))
|
||||||
|
|
||||||
ht.Logf("Alice(deadline=%v): txWeight=%v, expected: [fee=%d, "+
|
ht.Logf("Bob(position=%v): txWeight=%v, expected: [fee=%d, "+
|
||||||
"feerate=%v], got: [fee=%v, feerate=%v]", deadline-i,
|
"feerate=%v], got: [fee=%v, feerate=%v]",
|
||||||
txWeight, expectedFee, expectedFeeRate, fee, feeRate)
|
feeFuncWidth-i, txWeight, expectedFee,
|
||||||
|
expectedFeeRate, fee, feeRate)
|
||||||
|
|
||||||
// Assert Alice's tx has the expected fee and fee rate.
|
// Assert Bob's tx has the expected fee and fee rate.
|
||||||
require.InEpsilonf(ht, uint64(expectedFee), fee, 0.01,
|
require.InEpsilonf(ht, uint64(expectedFee), fee, 0.01,
|
||||||
"deadline=%v, want %d, got %d", i, expectedFee, fee)
|
"deadline=%v, want %d, got %d", i, expectedFee, fee)
|
||||||
require.InEpsilonf(ht, uint64(expectedFeeRate), feeRate, 0.01,
|
require.InEpsilonf(ht, uint64(expectedFeeRate), feeRate, 0.01,
|
||||||
|
@ -261,27 +300,34 @@ func testSweepAnchorCPFPLocalForceClose(ht *lntest.HarnessTest) {
|
||||||
feeRate)
|
feeRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once out of the above loop, we should've mined deadline-1 blocks. If
|
// We now check the budget has been used up at the deadline-1 block.
|
||||||
// we mine one more block, we'd use up all the CPFP budget.
|
//
|
||||||
|
// Once out of the above loop, we expect to be 2 blocks before the CPFP
|
||||||
|
// deadline.
|
||||||
|
_, currentHeight := ht.Miner.GetBestBlock()
|
||||||
|
require.Equal(ht, int(anchorDeadline-2), int(currentHeight))
|
||||||
|
|
||||||
|
// Mine one more block, we'd use up all the CPFP budget.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
// Get the last sweeping tx - we should see two txns here, Alice's
|
// Make sure Bob's old sweeping tx has been removed from the mempool.
|
||||||
// anchor sweeping tx and her force close tx.
|
ht.Miner.AssertTxNotInMempool(sweepTx.TxHash())
|
||||||
|
|
||||||
|
// Get the last sweeping tx - we should see two txns here, Bob's anchor
|
||||||
|
// sweeping tx and his force close tx.
|
||||||
txns = ht.Miner.GetNumTxsFromMempool(2)
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
// Find the sweeping tx.
|
// Find the sweeping tx.
|
||||||
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
// Calculate the fee and fee rate of Alice's new sweeping tx.
|
// Calculate the fee of Bob's new sweeping tx.
|
||||||
fee = uint64(ht.CalculateTxFee(sweepTx))
|
fee = uint64(ht.CalculateTxFee(sweepTx))
|
||||||
feeRate = uint64(ht.CalculateTxFeeRate(sweepTx))
|
|
||||||
|
|
||||||
// Alice should still have two pending sweeps,
|
// Assert the budget is now used up.
|
||||||
// - anchor sweeping from her local commitment.
|
require.InEpsilonf(ht, uint64(cpfpBudget), fee, 0.01, "want %d, got %d",
|
||||||
// - anchor sweeping from her remote commitment (invalid).
|
cpfpBudget, fee)
|
||||||
ht.AssertNumPendingSweeps(alice, 2)
|
|
||||||
|
|
||||||
// Mine one more block. Since Alice's budget has been used up, there
|
// Mine one more block. Since Bob's budget has been used up, there
|
||||||
// won't be any more sweeping attempts. We now assert this by checking
|
// won't be any more sweeping attempts. We now assert this by checking
|
||||||
// that the sweeping tx stayed unchanged.
|
// that the sweeping tx stayed unchanged.
|
||||||
ht.MineEmptyBlocks(1)
|
ht.MineEmptyBlocks(1)
|
||||||
|
@ -289,30 +335,361 @@ func testSweepAnchorCPFPLocalForceClose(ht *lntest.HarnessTest) {
|
||||||
// Get the current sweeping tx and assert it stays unchanged.
|
// Get the current sweeping tx and assert it stays unchanged.
|
||||||
//
|
//
|
||||||
// We expect two txns here, one for the anchor sweeping, the other for
|
// We expect two txns here, one for the anchor sweeping, the other for
|
||||||
// the HTLC sweeping.
|
// the force close tx.
|
||||||
txns = ht.Miner.GetNumTxsFromMempool(2)
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
// Find the sweeping tx.
|
// Find the sweeping tx.
|
||||||
currentSweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
currentSweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
// Calculate the fee and fee rate of Alice's current sweeping tx.
|
// Assert the anchor sweep tx stays unchanged.
|
||||||
currentFee := uint64(ht.CalculateTxFee(sweepTx))
|
require.Equal(ht, sweepTx.TxHash(), currentSweepTx.TxHash())
|
||||||
currentFeeRate := uint64(ht.CalculateTxFeeRate(sweepTx))
|
|
||||||
|
// Mine a block to confirm Bob's sweeping and force close txns, this is
|
||||||
|
// needed to clean up the mempool.
|
||||||
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
|
||||||
|
// The above mined block should confirm Bob's force close tx, and his
|
||||||
|
// contractcourt will offer the HTLC to his sweeper. We are not testing
|
||||||
|
// the HTLC sweeping behaviors so we just perform a simple check and
|
||||||
|
// exit the test.
|
||||||
|
ht.AssertNumPendingSweeps(bob, 1)
|
||||||
|
|
||||||
|
// Finally, clean the mempool for the next test.
|
||||||
|
ht.CleanShutDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// testSweepCPFPAnchorIncomingTimeout checks when a channel is force closed by
|
||||||
|
// a local node due to the incoming HTLC is about to time out, the anchor
|
||||||
|
// output is used for CPFPing the force close tx.
|
||||||
|
//
|
||||||
|
// Setup:
|
||||||
|
// 1. Fund Alice with 1 UTXOs - she only needs one for the funding process,
|
||||||
|
// 2. Fund Bob with 1 UTXO - he only needs one for the funding process, and
|
||||||
|
// the change output will be used for sweeping his anchor on local commit.
|
||||||
|
// 3. Create a linear network from Alice -> Bob -> Carol.
|
||||||
|
// 4. Alice pays an invoice to Carol through Bob.
|
||||||
|
// 5. Alice goes offline.
|
||||||
|
// 6. Carol settles the invoice.
|
||||||
|
//
|
||||||
|
// Test:
|
||||||
|
// 1. Bob force closes the channel with Alice, using the anchor output for
|
||||||
|
// CPFPing the force close tx.
|
||||||
|
// 2. Bob's anchor output is swept and fee bumped based on its deadline and
|
||||||
|
// budget.
|
||||||
|
func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) {
|
||||||
|
// Setup testing params.
|
||||||
|
//
|
||||||
|
// Invoice is 100k sats.
|
||||||
|
invoiceAmt := btcutil.Amount(100_000)
|
||||||
|
|
||||||
|
// Use the smallest CLTV so we can mine fewer blocks.
|
||||||
|
cltvDelta := routing.MinCLTVDelta
|
||||||
|
|
||||||
|
// goToChainDelta is the broadcast delta of Bob's incoming HTLC. When
|
||||||
|
// the block height is at CLTV-goToChainDelta, Bob will force close the
|
||||||
|
// channel Alice=>Bob.
|
||||||
|
goToChainDelta := uint32(lncfg.DefaultIncomingBroadcastDelta)
|
||||||
|
|
||||||
|
// deadlineDeltaAnchor is the expected deadline delta for the CPFP
|
||||||
|
// anchor sweeping tx.
|
||||||
|
deadlineDeltaAnchor := goToChainDelta / 2
|
||||||
|
|
||||||
|
// startFeeRateAnchor is the starting fee rate for the CPFP anchor
|
||||||
|
// sweeping tx.
|
||||||
|
startFeeRateAnchor := chainfee.SatPerKWeight(2500)
|
||||||
|
|
||||||
|
// Set up the fee estimator to return the testing fee rate when the
|
||||||
|
// conf target is the deadline.
|
||||||
|
//
|
||||||
|
// TODO(yy): switch to conf when `blockbeat` is in place.
|
||||||
|
// ht.SetFeeEstimateWithConf(startFeeRateAnchor, deadlineDeltaAnchor)
|
||||||
|
ht.SetFeeEstimate(startFeeRateAnchor)
|
||||||
|
|
||||||
|
// Create a preimage, that will be held by Carol.
|
||||||
|
var preimage lntypes.Preimage
|
||||||
|
copy(preimage[:], ht.Random32Bytes())
|
||||||
|
payHash := preimage.Hash()
|
||||||
|
|
||||||
|
// We now set up the force close scenario. We will create a network
|
||||||
|
// from Alice -> Bob -> Carol, where Alice will send a payment to Carol
|
||||||
|
// via Bob, Alice goes offline, Carol settles the payment. We expect
|
||||||
|
// Bob to sweep his anchor and incoming HTLC.
|
||||||
|
//
|
||||||
|
// Prepare params.
|
||||||
|
cfg := []string{
|
||||||
|
"--protocol.anchors",
|
||||||
|
// Use a small CLTV to mine less blocks.
|
||||||
|
fmt.Sprintf("--bitcoin.timelockdelta=%d", cltvDelta),
|
||||||
|
// Use a very large CSV, this way to_local outputs are never
|
||||||
|
// swept so we can focus on testing HTLCs.
|
||||||
|
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", cltvDelta*10),
|
||||||
|
}
|
||||||
|
openChannelParams := lntest.OpenChannelParams{
|
||||||
|
Amt: invoiceAmt * 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a three hop network: Alice -> Bob -> Carol.
|
||||||
|
chanPoints, nodes := createSimpleNetwork(ht, cfg, 3, openChannelParams)
|
||||||
|
|
||||||
|
// Unwrap the results.
|
||||||
|
abChanPoint, bcChanPoint := chanPoints[0], chanPoints[1]
|
||||||
|
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||||
|
|
||||||
|
// For neutrino backend, we need one more UTXO for Bob to create his
|
||||||
|
// sweeping txns.
|
||||||
|
if ht.IsNeutrinoBackend() {
|
||||||
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe the invoice.
|
||||||
|
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||||
|
|
||||||
|
// With the network active, we'll now add a hodl invoice at Carol's
|
||||||
|
// end.
|
||||||
|
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||||
|
Value: int64(invoiceAmt),
|
||||||
|
CltvExpiry: finalCltvDelta,
|
||||||
|
Hash: payHash[:],
|
||||||
|
}
|
||||||
|
invoice := carol.RPC.AddHoldInvoice(invoiceReq)
|
||||||
|
|
||||||
|
// Let Alice pay the invoices.
|
||||||
|
req := &routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: invoice.PaymentRequest,
|
||||||
|
TimeoutSeconds: 60,
|
||||||
|
FeeLimitMsat: noFeeLimitMsat,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert the payments are inflight.
|
||||||
|
ht.SendPaymentAndAssertStatus(alice, req, lnrpc.Payment_IN_FLIGHT)
|
||||||
|
|
||||||
|
// Wait for Carol to mark invoice as accepted. There is a small gap to
|
||||||
|
// bridge between adding the htlc to the channel and executing the exit
|
||||||
|
// hop logic.
|
||||||
|
ht.AssertInvoiceState(streamCarol, lnrpc.Invoice_ACCEPTED)
|
||||||
|
|
||||||
|
// At this point, all 3 nodes should now have an active channel with
|
||||||
|
// the created HTLCs pending on all of them.
|
||||||
|
//
|
||||||
|
// Alice should have one outgoing HTLCs on channel Alice -> Bob.
|
||||||
|
ht.AssertOutgoingHTLCActive(alice, abChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// Bob should have one incoming HTLC on channel Alice -> Bob, and one
|
||||||
|
// outgoing HTLC on channel Bob -> Carol.
|
||||||
|
htlc := ht.AssertIncomingHTLCActive(bob, abChanPoint, payHash[:])
|
||||||
|
ht.AssertOutgoingHTLCActive(bob, bcChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// Calculate the budget used for Bob's anchor sweeping.
|
||||||
|
//
|
||||||
|
// htlcValue is the incoming HTLC's value.
|
||||||
|
htlcValue := btcutil.Amount(htlc.Amount)
|
||||||
|
|
||||||
|
// htlcBudget is the budget used to sweep the incoming HTLC.
|
||||||
|
htlcBudget := htlcValue.MulF64(contractcourt.DefaultBudgetRatio)
|
||||||
|
|
||||||
|
// cpfpBudget is the budget used to sweep the CPFP anchor.
|
||||||
|
cpfpBudget := (htlcValue - htlcBudget).MulF64(
|
||||||
|
contractcourt.DefaultBudgetRatio,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Carol should have one incoming HTLC on channel Bob -> Carol.
|
||||||
|
ht.AssertIncomingHTLCActive(carol, bcChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// Let Alice go offline. Once Bob later learns the preimage, he
|
||||||
|
// couldn't settle it with Alice so he has to go onchain to collect it.
|
||||||
|
ht.Shutdown(alice)
|
||||||
|
|
||||||
|
// Carol settles invoice.
|
||||||
|
carol.RPC.SettleInvoice(preimage[:])
|
||||||
|
|
||||||
|
// Bob should have settled his outgoing HTLC with Carol.
|
||||||
|
ht.AssertHTLCNotActive(bob, bcChanPoint, payHash[:])
|
||||||
|
|
||||||
|
// We'll now mine enough blocks to trigger Bob to force close channel
|
||||||
|
// Alice->Bob due to his incoming HTLC is about to timeout. With the
|
||||||
|
// default incoming broadcast delta of 10, this will be the same
|
||||||
|
// height as the incoming htlc's expiry height minus 10.
|
||||||
|
forceCloseHeight := htlc.ExpirationHeight - goToChainDelta
|
||||||
|
|
||||||
|
// Mine till the goToChainHeight is reached.
|
||||||
|
_, currentHeight := ht.Miner.GetBestBlock()
|
||||||
|
numBlocks := forceCloseHeight - uint32(currentHeight)
|
||||||
|
ht.MineEmptyBlocks(int(numBlocks))
|
||||||
|
|
||||||
|
// Assert Bob's force closing tx has been broadcast.
|
||||||
|
closeTxid := ht.Miner.AssertNumTxsInMempool(1)[0]
|
||||||
|
|
||||||
|
// Bob should have two pending sweeps,
|
||||||
|
// - anchor sweeping from his local commitment.
|
||||||
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
|
sweeps := ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// The two anchor sweeping should have the same deadline height.
|
||||||
|
deadlineHeight := forceCloseHeight + deadlineDeltaAnchor
|
||||||
|
require.Equal(ht, deadlineHeight, sweeps[0].DeadlineHeight)
|
||||||
|
require.Equal(ht, deadlineHeight, sweeps[1].DeadlineHeight)
|
||||||
|
|
||||||
|
// Remember the deadline height for the CPFP anchor.
|
||||||
|
anchorDeadline := sweeps[0].DeadlineHeight
|
||||||
|
|
||||||
|
// Mine a block so Bob's force closing tx stays in the mempool, which
|
||||||
|
// also triggers the CPFP anchor sweep.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Bob should still have two pending sweeps,
|
||||||
|
// - anchor sweeping from his local commitment.
|
||||||
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// We now check the expected fee and fee rate are used for Bob's anchor
|
||||||
|
// sweeping tx.
|
||||||
|
//
|
||||||
|
// We should see Bob's anchor sweeping tx triggered by the above
|
||||||
|
// block, along with his force close tx.
|
||||||
|
txns := ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx.
|
||||||
|
sweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
|
// Get the weight for Bob's anchor sweeping tx.
|
||||||
|
txWeight := ht.CalculateTxWeight(sweepTx)
|
||||||
|
|
||||||
|
// Bob should start with the initial fee rate of 2500 sat/kw.
|
||||||
|
startFeeAnchor := startFeeRateAnchor.FeeForWeight(txWeight)
|
||||||
|
|
||||||
|
// Calculate the fee and fee rate of Bob's sweeping tx.
|
||||||
|
fee := uint64(ht.CalculateTxFee(sweepTx))
|
||||||
|
feeRate := uint64(ht.CalculateTxFeeRate(sweepTx))
|
||||||
|
|
||||||
|
// feeFuncWidth is the width of the fee function. By the time we got
|
||||||
|
// here, we've already mined one block, and the fee function maxes
|
||||||
|
// out one block before the deadline, so the width is the original
|
||||||
|
// deadline minus 2.
|
||||||
|
feeFuncWidth := deadlineDeltaAnchor - 2
|
||||||
|
|
||||||
|
// Calculate the expected delta increased per block.
|
||||||
|
feeDelta := (cpfpBudget - startFeeAnchor).MulF64(
|
||||||
|
1 / float64(feeFuncWidth),
|
||||||
|
)
|
||||||
|
|
||||||
|
// We expect the startingFee and startingFeeRate being used. Allow some
|
||||||
|
// deviation because weight estimates during tx generation are
|
||||||
|
// estimates.
|
||||||
|
//
|
||||||
|
// TODO(yy): unify all the units and types re int vs uint!
|
||||||
|
require.InEpsilonf(ht, uint64(startFeeAnchor), fee, 0.01,
|
||||||
|
"want %d, got %d", startFeeAnchor, fee)
|
||||||
|
require.InEpsilonf(ht, uint64(startFeeRateAnchor), feeRate,
|
||||||
|
0.01, "want %d, got %d", startFeeRateAnchor, fee)
|
||||||
|
|
||||||
|
// We now mine deadline-2 empty blocks. For each block mined, Bob
|
||||||
|
// should perform an RBF on his CPFP anchor sweeping tx. By the end of
|
||||||
|
// this iteration, we expect Bob to use up his CPFP budget after one
|
||||||
|
// more block.
|
||||||
|
for i := uint32(1); i <= feeFuncWidth-1; i++ {
|
||||||
|
// Mine an empty block. Since the sweeping tx is not confirmed,
|
||||||
|
// Bob's fee bumper should increase its fees.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Bob should still have two pending sweeps,
|
||||||
|
// - anchor sweeping from his local commitment.
|
||||||
|
// - anchor sweeping from his remote commitment (invalid).
|
||||||
|
ht.AssertNumPendingSweeps(bob, 2)
|
||||||
|
|
||||||
|
// Make sure Bob's old sweeping tx has been removed from the
|
||||||
|
// mempool.
|
||||||
|
ht.Miner.AssertTxNotInMempool(sweepTx.TxHash())
|
||||||
|
|
||||||
|
// We expect to see two txns in the mempool,
|
||||||
|
// - Bob's force close tx.
|
||||||
|
// - Bob's anchor sweep tx.
|
||||||
|
ht.Miner.AssertNumTxsInMempool(2)
|
||||||
|
|
||||||
|
// We expect the fees to increase by i*delta.
|
||||||
|
expectedFee := startFeeAnchor + feeDelta.MulF64(float64(i))
|
||||||
|
expectedFeeRate := chainfee.NewSatPerKWeight(
|
||||||
|
expectedFee, uint64(txWeight),
|
||||||
|
)
|
||||||
|
|
||||||
|
// We should see Bob's anchor sweeping tx being fee bumped
|
||||||
|
// since it's not confirmed, along with his force close tx.
|
||||||
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx.
|
||||||
|
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
|
// Calculate the fee rate of Bob's new sweeping tx.
|
||||||
|
feeRate = uint64(ht.CalculateTxFeeRate(sweepTx))
|
||||||
|
|
||||||
|
// Calculate the fee of Bob's new sweeping tx.
|
||||||
|
fee = uint64(ht.CalculateTxFee(sweepTx))
|
||||||
|
|
||||||
|
ht.Logf("Bob(position=%v): txWeight=%v, expected: [fee=%d, "+
|
||||||
|
"feerate=%v], got: [fee=%v, feerate=%v]",
|
||||||
|
feeFuncWidth-i, txWeight, expectedFee,
|
||||||
|
expectedFeeRate, fee, feeRate)
|
||||||
|
|
||||||
|
// Assert Bob's tx has the expected fee and fee rate.
|
||||||
|
require.InEpsilonf(ht, uint64(expectedFee), fee, 0.01,
|
||||||
|
"deadline=%v, want %d, got %d", i, expectedFee, fee)
|
||||||
|
require.InEpsilonf(ht, uint64(expectedFeeRate), feeRate, 0.01,
|
||||||
|
"deadline=%v, want %d, got %d", i, expectedFeeRate,
|
||||||
|
feeRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now check the budget has been used up at the deadline-1 block.
|
||||||
|
//
|
||||||
|
// Once out of the above loop, we expect to be 2 blocks before the CPFP
|
||||||
|
// deadline.
|
||||||
|
_, currentHeight = ht.Miner.GetBestBlock()
|
||||||
|
require.Equal(ht, int(anchorDeadline-2), int(currentHeight))
|
||||||
|
|
||||||
|
// Mine one more block, we'd use up all the CPFP budget.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Make sure Bob's old sweeping tx has been removed from the mempool.
|
||||||
|
ht.Miner.AssertTxNotInMempool(sweepTx.TxHash())
|
||||||
|
|
||||||
|
// Get the last sweeping tx - we should see two txns here, Bob's anchor
|
||||||
|
// sweeping tx and his force close tx.
|
||||||
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx.
|
||||||
|
sweepTx = ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
|
// Calculate the fee of Bob's new sweeping tx.
|
||||||
|
fee = uint64(ht.CalculateTxFee(sweepTx))
|
||||||
|
|
||||||
|
// Assert the budget is now used up.
|
||||||
|
require.InEpsilonf(ht, uint64(cpfpBudget), fee, 0.01, "want %d, got %d",
|
||||||
|
cpfpBudget, fee)
|
||||||
|
|
||||||
|
// Mine one more block. Since Bob's budget has been used up, there
|
||||||
|
// won't be any more sweeping attempts. We now assert this by checking
|
||||||
|
// that the sweeping tx stayed unchanged.
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
|
||||||
|
// Get the current sweeping tx and assert it stays unchanged.
|
||||||
|
//
|
||||||
|
// We expect two txns here, one for the anchor sweeping, the other for
|
||||||
|
// the force close tx.
|
||||||
|
txns = ht.Miner.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
|
// Find the sweeping tx.
|
||||||
|
currentSweepTx := ht.FindSweepingTxns(txns, 1, *closeTxid)[0]
|
||||||
|
|
||||||
// Assert the anchor sweep tx stays unchanged.
|
// Assert the anchor sweep tx stays unchanged.
|
||||||
require.Equal(ht, sweepTx.TxHash(), currentSweepTx.TxHash())
|
require.Equal(ht, sweepTx.TxHash(), currentSweepTx.TxHash())
|
||||||
require.Equal(ht, fee, currentFee)
|
|
||||||
require.Equal(ht, feeRate, currentFeeRate)
|
|
||||||
|
|
||||||
// Mine a block to confirm Alice's sweeping and force close txns, this
|
// Mine a block to confirm Bob's sweeping and force close txns, this is
|
||||||
// is needed to clean up the mempool.
|
// needed to clean up the mempool.
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
|
||||||
// The above mined block should confirm Alice's force close tx, and her
|
// The above mined block should confirm Bob's force close tx, and his
|
||||||
// contractcourt will offer the HTLC to her sweeper. We are not testing
|
// contractcourt will offer the HTLC to his sweeper. We are not testing
|
||||||
// the HTLC sweeping behaviors so we just perform a simple check and
|
// the HTLC sweeping behaviors so we just perform a simple check and
|
||||||
// exit the test.
|
// exit the test.
|
||||||
ht.AssertNumPendingSweeps(alice, 1)
|
ht.AssertNumPendingSweeps(bob, 1)
|
||||||
|
|
||||||
// Finally, clean the mempool for the next test.
|
// Finally, clean the mempool for the next test.
|
||||||
ht.CleanShutDown()
|
ht.CleanShutDown()
|
||||||
|
|
Loading…
Add table
Reference in a new issue