@@ -2154,3 +2154,157 @@ func runBumpFee(ht *lntest.HarnessTest, alice *node.HarnessNode) {
21542154 // Clean up the mempol.
21552155 ht .MineBlocksAndAssertNumTxes (1 , 2 )
21562156}
2157+
2158+ // testBumpForceCloseFee tests that when a force close transaction, in
2159+ // particular a commitment which has no HTLCs at stake, can be bumped via the
2160+ // rpc endpoint `BumpForceCloseFee`.
2161+ //
2162+ // NOTE: This test does not check for a specific fee rate because channel force
2163+ // closures should be bumped taking a budget into account not a specific
2164+ // fee rate.
2165+ func testBumpForceCloseFee (ht * lntest.HarnessTest ) {
2166+ // Skip this test for neutrino, as it's not aware of mempool
2167+ // transactions.
2168+ if ht .IsNeutrinoBackend () {
2169+ ht .Skipf ("skipping BumpForceCloseFee test for neutrino backend" )
2170+ }
2171+ // fundAmt is the funding amount.
2172+ fundAmt := btcutil .Amount (1_000_000 )
2173+
2174+ // We add a push amount because otherwise no anchor for the counter
2175+ // party will be created which influences the commitment fee
2176+ // calculation.
2177+ pushAmt := btcutil .Amount (50_000 )
2178+
2179+ openChannelParams := lntest.OpenChannelParams {
2180+ Amt : fundAmt ,
2181+ PushAmt : pushAmt ,
2182+ }
2183+
2184+ // Bumping the close fee rate is only possible for anchor channels.
2185+ cfg := []string {
2186+ "--protocol.anchors" ,
2187+ }
2188+
2189+ // Create a two hop network: Alice -> Bob.
2190+ chanPoints , nodes := createSimpleNetwork (ht , cfg , 2 , openChannelParams )
2191+
2192+ // Unwrap the results.
2193+ chanPoint := chanPoints [0 ]
2194+ alice := nodes [0 ]
2195+
2196+ // We need to fund alice with 2 wallet inputs so that we can test to
2197+ // increase the fee rate of the anchor cpfp via two subsequent calls of
2198+ // the`BumpForceCloseFee` rpc cmd.
2199+ //
2200+ // TODO (ziggie): Make sure we use enough wallet inputs so that both
2201+ // anchor transactions (local, remote commitment tx) can be created and
2202+ // broadcasted. Not sure if we really need this, because we can be sure
2203+ // as soon as one anchor transactions makes it into the mempool that the
2204+ // others will fail anyways?
2205+ ht .FundCoinsP2TR (btcutil .SatoshiPerBitcoin , alice )
2206+
2207+ // Alice force closes the channel which has no HTLCs at stake.
2208+ _ , closingTxID := ht .CloseChannelAssertPending (alice , chanPoint , true )
2209+ require .NotNil (ht , closingTxID )
2210+
2211+ // Alice should see one waiting close channel.
2212+ ht .AssertNumWaitingClose (alice , 1 )
2213+
2214+ // Alice should have 2 registered sweep inputs. The anchor of the local
2215+ // commitment tx and the anchor of the remote commitment tx.
2216+ ht .AssertNumPendingSweeps (alice , 2 )
2217+
2218+ // Calculate the commitment tx fee rate.
2219+ closingTx := ht .AssertTxInMempool (closingTxID )
2220+ require .NotNil (ht , closingTx )
2221+
2222+ // The default commitment fee for anchor channels is capped at 2500
2223+ // sat/kw but there might be some inaccuracies because of the witness
2224+ // signature length therefore we calculate the exact value here.
2225+ closingFeeRate := ht .CalculateTxFeeRate (closingTx )
2226+
2227+ // We increase the fee rate of the fee function by 100% to make sure
2228+ // we trigger a cpfp-transaction.
2229+ newFeeRate := closingFeeRate * 2
2230+
2231+ // We need to make sure that the budget can cover the fees for bumping.
2232+ // However we also want to make sure that the budget is not too large
2233+ // so that the delta of the fee function does not increase the feerate
2234+ // by a single sat hence NOT rbfing the anchor sweep every time a new
2235+ // block is found and a new sweep broadcast is triggered.
2236+ //
2237+ // NOTE:
2238+ // We expect an anchor sweep with 2 inputs (anchor input + a wallet
2239+ // input) and 1 p2tr output. This transaction has a weight of approx.
2240+ // 725 wu. This info helps us to calculate the delta of the fee
2241+ // function.
2242+ // EndFeeRate: 100_000 sats/725 wu * 1000 = 137931 sat/kw
2243+ // StartingFeeRate: 5000 sat/kw
2244+ // delta = (137931-5000)/1008 = 132 sat/kw (which is lower than
2245+ // 250 sat/kw) => hence we are violating BIP 125 Rule 4, which is
2246+ // exactly what we want here to test the subsequent calling of the
2247+ // bumpclosefee rpc.
2248+ cpfpBudget := 100_000
2249+
2250+ bumpFeeReq := & walletrpc.BumpForceCloseFeeRequest {
2251+ ChanPoint : chanPoint ,
2252+ StartingFeerate : uint64 (newFeeRate .FeePerVByte ()),
2253+ Budget : uint64 (cpfpBudget ),
2254+ // We use a force param to create the sweeping tx immediately.
2255+ Immediate : true ,
2256+ }
2257+ alice .RPC .BumpForceCloseFee (bumpFeeReq )
2258+
2259+ // We expect the initial closing transaction and the local anchor cpfp
2260+ // transaction because alice force closed the channel.
2261+ //
2262+ // NOTE: We don't compare a feerate but only make sure that a cpfp
2263+ // transaction was triggered. The sweeper increases the fee rate
2264+ // periodically with every new incoming block and the selected fee
2265+ // function.
2266+ ht .AssertNumTxsInMempool (2 )
2267+
2268+ // Identify the cpfp anchor sweep.
2269+ txns := ht .GetNumTxsFromMempool (2 )
2270+ cpfpSweep1 := ht .FindSweepingTxns (txns , 1 , closingTx .TxHash ())[0 ]
2271+
2272+ // Mine an empty block and make sure the anchor cpfp is still in the
2273+ // mempool hence the new block did not let the sweeper subsystem rbf
2274+ // this anchor sweep transaction (because of the small fee delta).
2275+ ht .MineEmptyBlocks (1 )
2276+ cpfpHash1 := cpfpSweep1 .TxHash ()
2277+ ht .AssertTxInMempool (& cpfpHash1 )
2278+
2279+ // Now Bump the fee rate again with a bigger starting fee rate of the
2280+ // fee function.
2281+ newFeeRate = closingFeeRate * 3
2282+
2283+ bumpFeeReq = & walletrpc.BumpForceCloseFeeRequest {
2284+ ChanPoint : chanPoint ,
2285+ StartingFeerate : uint64 (newFeeRate .FeePerVByte ()),
2286+ // The budget needs to be high enough to pay for the fee because
2287+ // the anchor does not have an output value high enough to pay
2288+ // for itself.
2289+ Budget : uint64 (cpfpBudget ),
2290+ // We use a force param to create the sweeping tx immediately.
2291+ Immediate : true ,
2292+ }
2293+ alice .RPC .BumpForceCloseFee (bumpFeeReq )
2294+
2295+ // Make sure the old sweep is not in the mempool anymore, which proofs
2296+ // that a new cpfp transaction replaced the old one paying higher fees.
2297+ ht .AssertTxNotInMempool (cpfpHash1 )
2298+
2299+ // Identify the new cpfp transaction.
2300+ // Both anchor sweeps result from the same closing tx (the local
2301+ // commitment) hence proofing that the remote commitment transaction
2302+ // and its cpfp transaction is invalid and not accepted into the
2303+ // mempool.
2304+ txns = ht .GetNumTxsFromMempool (2 )
2305+ ht .FindSweepingTxns (txns , 1 , closingTx .TxHash ())
2306+
2307+ // Mine both transactions, the closing tx and the anchor cpfp tx.
2308+ // This is needed to clean up the mempool.
2309+ ht .MineBlocksAndAssertNumTxes (1 , 2 )
2310+ }
0 commit comments