READM.md
1 # Linear Fee Function 2 3 This is a model of the default [Linear Fee 4 Function](https://github.com/lightningnetwork/lnd/blob/b7c59b36a74975c4e710a02ea42959053735402e/sweep/fee_function.go#L66-L109) 5 fee bumping mechanism in lnd. 6 7 This model was inspired by a bug fix, due to an off-by-one error in the 8 original code: https://github.com/lightningnetwork/lnd/issues/8741. 9 10 The bug in the original code was fixed in this PR: 11 https://github.com/lightningnetwork/lnd/pull/8751. 12 13 14 ## Model & Bug Fix Walk-through 15 16 The model includes an assertion that captures the essence of the bug: 17 `max_fee_rate_before_deadline`: 18 ```alloy 19 // max_fee_rate_before_deadline is the main assertion in this model. This 20 // captures a model violation for our fee function, but only if the line in 21 // fee_rate_at_position is uncommented. 22 // 23 // In this assertion, we declare that if we have a fee function that has a conf 24 // target of 4 (we want a few fee bumps), and we bump to the final block, then 25 // at that point our current fee rate is the ending fee rate. In the original 26 // code, assertion isn't upheld, due to an off by one error. 27 assert max_fee_rate_before_deadline { 28 always req_num_blocks_to_conf[4] => bump_to_final_block => eventually ( 29 all f: LinearFeeFunction | f.position = f.width.sub[1] && 30 f.currentFeeRate = f.endingFeeRate 31 ) 32 } 33 ``` 34 35 We can modify the model to find the bug described in the original issue. 36 1. First, we modify the model by forcing a `check` on the 37 `max_fee_rate_before_deadline` assertion: 38 ```alloy 39 check max_fee_rate_before_deadline 40 ``` 41 42 2. Next, we'll modify the `fee_rate_at_position` predicate to re-introduce 43 the off by one error: 44 ```alloy 45 p >= f.width => f.endingFeeRate // -- NOTE: Uncomment this to re-introduce the original bug. 46 ``` 47 48 If we hit `Execute` in the Alloy Analyzer, then we get a counter example: 49  50 51 52 We can hit `Show` in the analyzer to visualize it: 53  54 55 We can see that even though we're one block (`position`) before the deadline 56 (`width`), our fee rate isn't at the ending fee rate yet. 57 58 If we modify the `fee_rate_at_position` to have the correct logic: 59 ```alloy 60 p >= f.width.sub[1] => f.endingFeeRate 61 ``` 62 63 Then Alloy is unable to find any counterexamples: 64 