lightningnetwork / lnd

Lightning Network Daemon ⚡️
MIT License
7.58k stars 2.06k forks source link

[bug]: sweep: LinearFeeFunction off by one #8741

Closed morehouse closed 2 months ago

morehouse commented 2 months ago

LinearFeeFunction doesn't return the max fee rate until after the deadline has been missed.

Example

Current block height is H. An incoming HTLC has a cltv_expiry of H + 10, so the contractcourt sets deadline := H + 10 for the HTLC input.

We need to confirm our HTLC-Preimage no later than block H + 10, or else we'll get into a bidding war with the channel peer since they can spend the timeout path after block H + 10 confirms.

LinearFeeFunction should max out its fee rate after block H + 9 confirms since this is the last shot at meeting the deadline. Instead, LinearFeeFunction waits to max out the fee rate until block H + 10.

Severity

For typical deadlines 10+ blocks away, this is a minor problem. But in the extreme case of a next-block deadline, LinearFeeFunction will return the min feerate instead of the max, which is quite bad.

Crypt-iQ commented 2 months ago

@saubyk add to 18.0?

saubyk commented 2 months ago

@saubyk add to 18.0?

makes sense. added.

saubyk commented 2 months ago

cc: @yyforyongyu

yyforyongyu commented 2 months ago

But in the extreme case of a next-block deadline, LinearFeeFunction will return the min feerate instead of the max, which is quite bad.

What do you mean by saying next-block deadline? That we only have 1 block left till it times out? What is this min feerate?

morehouse commented 2 months ago

What do you mean by saying next-block deadline? That we only have 1 block left till it times out? What is this min feerate?

Here's an example graph of the LinearFeeFunction. The min feerate is the starting feerate. The max feerate is the ending feerate.

image

Now imagine we call SweepInput with a deadline 1 block away. The fee function will be initialized with a width of 1 and starting feerate at the minimum of the feerate line. After 1 block (i.e. deadline missed), the feerate will be immediately bumped to the maximum of the feerate line.

But for this next-block deadline case, we really don't want a sloped line at all -- we want to start right at the maximum.

ghost commented 2 months ago

func (l *LinearFeeFunction) feeRateAtPosition(p uint32) chainfee.SatPerKWeight { if p >= l.width - 1 { return l.endingFeeRate } ... } does this suggestion fix the issue located in /sweep/fee_function.go