Skip to content

Commit 388183e

Browse files
committed
itest: add fee replacement test
1 parent db351e1 commit 388183e

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,10 @@ var allTestCases = []*lntest.TestCase{
662662
Name: "invoice migration",
663663
TestFunc: testInvoiceMigration,
664664
},
665+
{
666+
Name: "fee replacement",
667+
TestFunc: testFeeReplacement,
668+
},
665669
}
666670

667671
// appendPrefixed is used to add a prefix to each test name in the subtests

itest/lnd_sweep_test.go

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
1616
"github.com/lightningnetwork/lnd/lntest"
1717
"github.com/lightningnetwork/lnd/lntest/node"
18+
"github.com/lightningnetwork/lnd/lntest/rpc"
1819
"github.com/lightningnetwork/lnd/lntest/wait"
1920
"github.com/lightningnetwork/lnd/lntypes"
2021
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -2081,3 +2082,255 @@ func testBumpForceCloseFee(ht *lntest.HarnessTest) {
20812082
// This is needed to clean up the mempool.
20822083
ht.MineBlocksAndAssertNumTxes(1, 2)
20832084
}
2085+
2086+
// testFeeReplacement tests that when a sweeping txns aggregates multiple
2087+
// outgoing HTLCs, and one of the outgoing HTLCs has been spent via the direct
2088+
// preimage path by the remote peer, the remaining HTLCs will be grouped again
2089+
// and swept immediately.
2090+
//
2091+
// Setup:
2092+
// 1. Fund Alice with 1 UTXOs - she only needs one for the funding process,
2093+
// 2. Fund Bob with 3 UTXOs - he needs one for the funding process, one for
2094+
// his CPFP anchor sweeping, and one for sweeping his outgoing HTLCs.
2095+
// 3. Create a linear network from Alice -> Bob -> Carol.
2096+
// 4. Alice pays two invoices to Carol, with Carol holding the settlement.
2097+
// 5. Bob goes offline.
2098+
// 6. Carol settles one of the invoices, so she can later spend Bob's outgoing
2099+
// HTLC via the direct preimage path.
2100+
// 7. Carol goes offline and Bob comes online.
2101+
// 8. Mine enough blocks so Bob will force close Bob=>Carol to claim his
2102+
// outgoing HTLCs.
2103+
// 9. Carol comes online, sweeps one of Bob's outgoing HTLCs and it confirms.
2104+
// 10. Bob creates a new sweeping tx to sweep his remaining HTLC with a
2105+
// previous fee rate.
2106+
//
2107+
// Test:
2108+
// 1. Bob will immediately sweeps his remaining outgoing HTLC given that the
2109+
// other one has been spent by Carol.
2110+
// 2. Bob's new sweeping tx will use the previous fee rate instead of
2111+
// initializing a new starting fee rate.
2112+
func testFeeReplacement(ht *lntest.HarnessTest) {
2113+
// Set the min relay feerate to be 10 sat/vbyte so the non-CPFP anchor
2114+
// is never swept.
2115+
//
2116+
// TODO(yy): delete this line once the normal anchor sweeping is
2117+
// removed.
2118+
ht.SetMinRelayFeerate(10_000)
2119+
2120+
// Setup testing params.
2121+
//
2122+
// Invoice is 100k sats.
2123+
invoiceAmt := btcutil.Amount(100_000)
2124+
2125+
// Alice will send two payments.
2126+
numPayments := 2
2127+
2128+
// Use the smallest CLTV so we can mine fewer blocks.
2129+
cltvDelta := routing.MinCLTVDelta
2130+
2131+
// Prepare params.
2132+
cfg := []string{
2133+
"--protocol.anchors",
2134+
// Use a small CLTV to mine less blocks.
2135+
fmt.Sprintf("--bitcoin.timelockdelta=%d", cltvDelta),
2136+
// Use a very large CSV, this way to_local outputs are never
2137+
// swept so we can focus on testing HTLCs.
2138+
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", cltvDelta*10),
2139+
}
2140+
cfgs := [][]string{cfg, cfg, cfg}
2141+
2142+
openChannelParams := lntest.OpenChannelParams{
2143+
Amt: invoiceAmt * 100,
2144+
}
2145+
2146+
// Create a three hop network: Alice -> Bob -> Carol.
2147+
_, nodes := ht.CreateSimpleNetwork(cfgs, openChannelParams)
2148+
2149+
// Unwrap the results.
2150+
alice, bob, carol := nodes[0], nodes[1], nodes[2]
2151+
2152+
// Bob needs two more wallet utxos:
2153+
// - when sweeping anchors, he needs one utxo for each sweep.
2154+
// - when sweeping HTLCs, he needs one utxo for each sweep.
2155+
numUTXOs := 2
2156+
2157+
// Bob should have enough wallet UTXOs here to sweep the HTLC in the
2158+
// end of this test. However, due to a known issue, Bob's wallet may
2159+
// report there's no UTXO available. For details,
2160+
// - https://github.com/lightningnetwork/lnd/issues/8786
2161+
//
2162+
// TODO(yy): remove this extra UTXO once the issue is resolved.
2163+
numUTXOs++
2164+
2165+
// For neutrino backend, we need two more UTXOs for Bob to create his
2166+
// sweeping txns.
2167+
if ht.IsNeutrinoBackend() {
2168+
numUTXOs += 2
2169+
}
2170+
2171+
ht.FundNumCoins(bob, numUTXOs)
2172+
2173+
// We also give Carol 2 coins to create her sweeping txns.
2174+
ht.FundNumCoins(carol, 2)
2175+
2176+
// Create numPayments HTLCs on Bob's incoming and outgoing channels.
2177+
preimages := make([][]byte, 0, numPayments)
2178+
streams := make([]rpc.SingleInvoiceClient, 0, numPayments)
2179+
for i := 0; i < numPayments; i++ {
2180+
// Create the preimage.
2181+
var preimage lntypes.Preimage
2182+
copy(preimage[:], ht.Random32Bytes())
2183+
payHashHold := preimage.Hash()
2184+
preimages = append(preimages, preimage[:])
2185+
2186+
// Subscribe the invoices.
2187+
stream := carol.RPC.SubscribeSingleInvoice(payHashHold[:])
2188+
streams = append(streams, stream)
2189+
2190+
// Carol create the hold invoice.
2191+
invoiceReqHold := &invoicesrpc.AddHoldInvoiceRequest{
2192+
Value: int64(invoiceAmt),
2193+
CltvExpiry: finalCltvDelta,
2194+
Hash: payHashHold[:],
2195+
}
2196+
invoiceHold := carol.RPC.AddHoldInvoice(invoiceReqHold)
2197+
2198+
// Let Alice pay the invoices.
2199+
req := &routerrpc.SendPaymentRequest{
2200+
PaymentRequest: invoiceHold.PaymentRequest,
2201+
TimeoutSeconds: 60,
2202+
FeeLimitMsat: noFeeLimitMsat,
2203+
}
2204+
2205+
// Assert the payments are inflight.
2206+
ht.SendPaymentAndAssertStatus(
2207+
alice, req, lnrpc.Payment_IN_FLIGHT,
2208+
)
2209+
2210+
// Wait for Carol to mark invoice as accepted. There is a small
2211+
// gap to bridge between adding the htlc to the channel and
2212+
// executing the exit hop logic.
2213+
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
2214+
}
2215+
2216+
// At this point, all 3 nodes should now have an active channel with
2217+
// the created HTLCs pending on all of them.
2218+
//
2219+
// Alice should have numPayments outgoing HTLCs on channel Alice -> Bob.
2220+
ht.AssertNumActiveHtlcs(alice, numPayments)
2221+
2222+
// Bob should have 2 * numPayments HTLCs,
2223+
// - numPayments incoming HTLCs on channel Alice -> Bob.
2224+
// - numPayments outgoing HTLCs on channel Bob -> Carol.
2225+
ht.AssertNumActiveHtlcs(bob, numPayments*2)
2226+
2227+
// Carol should have numPayments incoming HTLCs on channel Bob -> Carol.
2228+
ht.AssertNumActiveHtlcs(carol, numPayments)
2229+
2230+
// Suspend Bob so he won't get the preimage from Carol.
2231+
restartBob := ht.SuspendNode(bob)
2232+
2233+
// Carol settles the first invoice.
2234+
carol.RPC.SettleInvoice(preimages[0])
2235+
ht.AssertInvoiceState(streams[0], lnrpc.Invoice_SETTLED)
2236+
2237+
// Carol goes offline so the preimage won't be sent to Bob.
2238+
restartCarol := ht.SuspendNode(carol)
2239+
2240+
// Bob comes online.
2241+
require.NoError(ht, restartBob())
2242+
2243+
// We'll now mine enough blocks to trigger Bob to force close channel
2244+
// Bob->Carol due to his outgoing HTLC is about to timeout. With the
2245+
// default outgoing broadcast delta of zero, this will be the same
2246+
// height as the outgoing htlc's expiry height.
2247+
numBlocks := padCLTV(uint32(
2248+
finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta,
2249+
))
2250+
ht.MineEmptyBlocks(int(numBlocks))
2251+
2252+
// Assert Bob's force closing tx has been broadcast. We should see two
2253+
// txns in the mempool:
2254+
// 1. Bob's force closing tx.
2255+
// 2. Bob's anchor sweeping tx CPFPing the force close tx.
2256+
ht.AssertForceCloseAndAnchorTxnsInMempool()
2257+
2258+
// Mine a block to confirm Bob's force close tx and anchor sweeping tx
2259+
// so we can focus on testing his outgoing HTLCs.
2260+
ht.MineBlocksAndAssertNumTxes(1, 2)
2261+
2262+
// Bob should have numPayments pending sweep for the outgoing HTLCs.
2263+
ht.AssertNumPendingSweeps(bob, numPayments)
2264+
2265+
// Bob should have one sweeping tx in the mempool, which sweeps all his
2266+
// outgoing HTLCs.
2267+
outgoingSweep0 := ht.GetNumTxsFromMempool(1)[0]
2268+
2269+
// We now mine one empty block so Bob will perform one fee bump, after
2270+
// which his sweeping tx should be updated with a new fee rate. We do
2271+
// this so we can test later when Bob sweeps his remaining HTLC, the new
2272+
// sweeping tx will start with the current fee rate.
2273+
//
2274+
// Calculate Bob's initial sweeping fee rate.
2275+
initialFeeRate := ht.CalculateTxFeeRate(outgoingSweep0)
2276+
2277+
// Mine one block to trigger Bob's RBF.
2278+
ht.MineEmptyBlocks(1)
2279+
2280+
// Make sure Bob's old sweeping tx has been removed from the mempool.
2281+
ht.AssertTxNotInMempool(outgoingSweep0.TxHash())
2282+
2283+
// Get the feerate of Bob's current sweeping tx.
2284+
outgoingSweep1 := ht.GetNumTxsFromMempool(1)[0]
2285+
currentFeeRate := ht.CalculateTxFeeRate(outgoingSweep1)
2286+
2287+
// Assert the Bob has updated the fee rate.
2288+
require.Greater(ht, currentFeeRate, initialFeeRate)
2289+
2290+
delta := currentFeeRate - initialFeeRate
2291+
2292+
// Check the shape of the sweeping tx - we expect it to be
2293+
// 3-input-3-output as a wallet utxo is used and a required output is
2294+
// made.
2295+
require.Len(ht, outgoingSweep1.TxIn, numPayments+1)
2296+
require.Len(ht, outgoingSweep1.TxOut, numPayments+1)
2297+
2298+
// Restart Carol, once she is online, she will try to settle the HTLCs
2299+
// via the direct preimage spend.
2300+
require.NoError(ht, restartCarol())
2301+
2302+
// Carol should have 1 incoming HTLC and 1 anchor output to sweep.
2303+
ht.AssertNumPendingSweeps(carol, 2)
2304+
2305+
// Assert Bob's sweeping tx has been replaced by Carol's.
2306+
ht.AssertTxNotInMempool(outgoingSweep1.TxHash())
2307+
carolSweepTx := ht.GetNumTxsFromMempool(1)[0]
2308+
2309+
// Assume the miner is now happy with Carol's fee, and it gets included
2310+
// in the next block.
2311+
ht.MineBlockWithTx(carolSweepTx)
2312+
2313+
// Upon receiving the above block, Bob should immediately create a
2314+
// sweeping tx and broadcast it using the remaining outgoing HTLC.
2315+
//
2316+
// Bob should have numPayments-1 pending sweep for the outgoing HTLCs.
2317+
ht.AssertNumPendingSweeps(bob, numPayments-1)
2318+
2319+
// Assert Bob immediately sweeps his remaining HTLC with the previous
2320+
// fee rate.
2321+
outgoingSweep2 := ht.GetNumTxsFromMempool(1)[0]
2322+
2323+
// Calculate the fee rate.
2324+
feeRate := ht.CalculateTxFeeRate(outgoingSweep2)
2325+
2326+
// We expect the current fee rate to be equal to the last fee rate he
2327+
// used plus the delta, as we expect the fee rate to stay on the initial
2328+
// line given by his fee function.
2329+
expectedFeeRate := currentFeeRate + delta
2330+
require.InEpsilonf(ht, uint64(expectedFeeRate),
2331+
uint64(feeRate), 0.02, "want %d, got %d in tx=%v",
2332+
currentFeeRate, feeRate, outgoingSweep2.TxHash())
2333+
2334+
// Finally, clean the mempol.
2335+
ht.MineBlocksAndAssertNumTxes(1, 1)
2336+
}

0 commit comments

Comments
 (0)