This PR is designed for the use case of a Lightning node that provides liquidity of predefined amounts via liquidity ads.
Coin selection is currently optimized to reduce the size of the utxo set and create change optimized for privacy. A liquidity provider instead needs to service multiple liquidity requests by spending confirmed utxos of known sizes.
Ideally most liquidity transactions would be funded by a single input or a small set of inputs optimized to reduce fees. To minimize the number of unconfirmed transactions, inputs should also be sized in a range where most transactions do not produce change. When change is created, it should be divided into outputs of the sizes needed so that the wallet’s utxo set converges towards an ideal utxo set specified by the user.
I am opening this PR as a draft to get feedback and suggestions on the concept and my implementation to address this use case.
The algorithm described below can be implemented externally via RPC calls or directly in the wallet.
- Externally: use a new option to set the change target used for coin selection.
- Wallet (opportunistic): a new configuration file defines the desired utxo set which the wallet uses to compute the change target used for coin selection and to split change outputs (if any).
- Wallet (reactive/proactive): pre-select a large input to force coin selection to produce change when fees are below some specified threshold or the desired set of utxos falls below some threshold.
utxo targets file example:
0{
1 "buckets": [
2 {
3 "start_satoshis": 10000,
4 "end_satoshis": 25000,
5 "target_utxo_count": 150
6 },
7 {
8 "start_satoshis": 50000,
9 "end_satoshis": 75000,
10 "target_utxo_count": 50
11 },
12 {
13 "start_satoshis": 200000,
14 "end_satoshis": 250000,
15 "target_utxo_count": 20
16 },
17 {
18 "start_satoshis": 1000000,
19 "end_satoshis": 1400000,
20 "target_utxo_count": 5
21 }
22 ],
23 "bucket_refill_feerate": 30000
24}
- The
target_utxo_count
for a bucket should be larger than the anticipated number of liquidity requests ofbucket_start_satoshis
within the expected confirmation time of a liquidity transaction. - The range from
bucket_start_satoshis
tobucket_end_satoshis
should encompass expected fee variance. - The
bucket_refill_feerate
should be set to the expected median fee rate (?). - This file will be reloaded for every spend request to allow for on-the-fly updates
Algorithm steps
For each payment do the following:
- Calculate the current capacity of each target bucket from the wallet’s utxo set.
- Include outputs from both confirmed and unconfirmed transactions in the wallet to calculate capacity.
- Add our largest confirmed utxo as an input IF the capacity of the least full target bucket is below some threshold (eg. < 30% full) or less than some higher threshold (eg. < 70%) and fee rates are below the
bucket_refill_feerate
.- When the largest confirmed utxo is from one of our target buckets, then we should refill our wallet with a utxo from cold storage.
- Set the minimum change target
m_min_change_target
to a value from the target bucket with the lowest current capacity.- Generate a random change target of the amount: current
change_fee
(the fee for creating an output) + a random value in the range:bucket_start_satoshis
tobucket_end_satoshi
-change_fee
. - Currently the change target is set by
GenerateChangeTarget()
in a hard coded range. - This parameter is only used by the ‘knapsack’ and ‘coingrinder’ algorithms.
- Generate a random change target of the amount: current
- Call ‘SelectCoins()’ with the input from step 2 (if any) added to the
preset_inputs
parameter and with the minimum change target from step 3.- The
consolidatefeerate=0
configuration option should always be set so that utxos are not preemptively cosolidated. Coin selection sets the parameterm_long_term_feerate
to the walletsconsolidatefeerate
. - Ideally, only the ‘bnb’ and ‘cg’ coin selection algorithms should be used and the others disabled to optimize for low fees.
- The
- If the coin selection result includes a change output, then split the single change output amount into multiple outputs.
- Add the mimimum change target as an output first.
- If there is remaining value after paying the fee for a new output, then add a target from the next most empty target bucket.
- If there is not enough value to add a new output and fees, add remaining value to the last output added instead.