mirror of
https://github.com/aljazceru/plugins.git
synced 2026-01-06 14:54:19 +01:00
Fix rebalance plugin
This commit is contained in:
committed by
Chris Guida
parent
4a04caff7e
commit
83a80d134e
@@ -1,101 +0,0 @@
|
||||
# Rebalance plugin
|
||||
|
||||
This plugin moves liquidity between your channels using circular payments
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
For general plugin installation instructions see the repos main
|
||||
[README.md](https://github.com/lightningd/plugins/blob/master/README.md#Installation)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin is installed and active, there are four additional methods for helping to rebalance channels:
|
||||
1) Either you can call `lightning-cli rebalanceall` to automatically fix all of your channels' liquidity.
|
||||
2) `lightning-cli rebalancestop` stops the ongoing `rebalanceall`.
|
||||
3) Or you can call `lightning-cli rebalance outgoing_scid incoming_scid` to rebalance individual channels.
|
||||
4) `lightning-cli rebalancereport` shows information: plugin settings, past rebalance stats, etc.
|
||||
|
||||
## Automatic rebalance
|
||||
|
||||
A lightning node usually has multiple channels of different sizes. The node can perform best if all channels have `{enough_liquidity}` for both directions. So the rebalance has multiple purposes with different priority:
|
||||
1) **The primary goal** is to ensure all channels have `{enough_liquidity}` for both direction, or if a given channel is too small for that, then it has a 50/50 liquidity ratio.
|
||||
2) **The secondary goal** is to distribute the remaining liquidity evenly between the big channels.
|
||||
3) For the long run, it is very important **to do this economically**. So the fees of fixing liquidity have to be cheaper than the fees of transaction forwards, which can ruin the liquidity again. (This assumes your node has some rational fee setting.) This way the automatic rebalance can run regularly, and your node can earn more on transaction forwarding than spend for rebalancing.
|
||||
|
||||
If the plugin cannot find a cheap enough circular route to rebalancing economically, then it does nothing by default. To not to cause a loss for users.
|
||||
|
||||
#### Rebalancing strategy
|
||||
|
||||
As a first step, depending on the actual situation, there is a need to get a value of `{enough_liquidity}`. The plugin searches for a maximum possible threshold. For which all channels theoretically can be balanced beyond this threshold. Or smaller than `threshold * 2` channels can be balanced to a 50/50 ratio. `{enough_liquidity}` will be half of this maximum threshold.
|
||||
|
||||
The next step is to calculate `{ideal_ratio}` for big channels. Beyond the `{enough_liquidity}` threshold, big channels should share the remaining liquidity evenly, so every big channels' liquidity ratio should be close to the `{ideal_ratio}`.
|
||||
|
||||
After we know the current `{enough_liquidity}` threshold and `{ideal_ratio}`, the plugin checks every possible channel pairs to seek a proper rebalance opportunity. If it finds a matching pair, it calls the individual rebalance method for them. If the rebalance fails, the plugin tries again with a lesser amount, until it reaches the minimum rebalancable amount, or the rebalance succeeds.
|
||||
|
||||
This process may take a while. Automatic rebalance can run for hours in the background, but you can stop it anytime with `lightning-cli rebalancestop`.
|
||||
|
||||
#### Parameters for rebalanceall
|
||||
|
||||
- OPTIONAL: The `min_amount` parameter sets the minimum rebalancable amount in millisatoshis. The parameter also can be specified in other denominations by appending a valid suffix, i. e. '1000000sat', '0.01btc' or '10mbtc'. The default value is '50000sat'.
|
||||
- OPTIONAL: The `feeratio` sets how much the rebalance may cost as a ratio of your default fee. Its default value is `0.5`, which means it can use a maximum of half of your node's default fee.
|
||||
|
||||
#### Tips and Tricks for automatic rebalance
|
||||
|
||||
- It may work only with well-connected nodes. You should have several different channels to use it with a good chance for success.
|
||||
- Your node should have some rational default fee setting. If you use cheaper fees than your neighbors, it probably cannot find a cheap enough circular route to rebalance.
|
||||
|
||||
## Individual channel rebalance
|
||||
You can use the `lightning-cli` to rebalance channels like this:
|
||||
|
||||
```
|
||||
lightning-cli rebalance outgoing_scid incoming_scid [msatoshi] [retry_for] [maxfeepercent] [exemptfee] [getroute_method]
|
||||
```
|
||||
def rebalance(plugin, outgoing_scid, incoming_scid, msatoshi: Millisatoshi = None,
|
||||
retry_for: int = 60, maxfeepercent: float = 0.5,
|
||||
exemptfee: Millisatoshi = Millisatoshi(5000),
|
||||
getroute_method=None):
|
||||
If you want to skip/default certain optional parameters but use others, you can
|
||||
use always the `lightning-cli -k` (key=value) syntax like this:
|
||||
|
||||
```bash
|
||||
lightning-cli rebalance -k outgoing_scid=1514942x51x0 incoming_scid=1515133x10x0 maxfeepercent=1
|
||||
```
|
||||
|
||||
#### Parameters for rebalance
|
||||
|
||||
- The `outgoing_scid` is the short_channel_id of the sending channel,
|
||||
- The `incoming_scid` is the short_channel_id of the receiving channel.
|
||||
- OPTIONAL: The `msatoshi` parameter sets the amount in milli-satoshis to be
|
||||
transferred. If the parameter is left out, the plugin will calucate an amount
|
||||
that will balance the channels 50%/50%. The parameter can also be given in
|
||||
other denominations by appending i.e. '1000000sat', '0.01btc' or '10mbtc'.
|
||||
- OPTIONAL: `retry_for` defines the number of seconds the plugin will retry to
|
||||
find a suitable route. Default: 60 seconds.
|
||||
- OPTIONAL: `maxfeepercent` is a perecentage limit of the money to be paid in
|
||||
fees and defaults to 0.5.
|
||||
- OPTIONAL: The `exemptfee` option can be used for tiny payments which would be
|
||||
dominated by the fee leveraged by forwarding nodes. Setting `exemptfee`
|
||||
allows the `maxfeepercent` check to be skipped on fees that are smaller than
|
||||
exemptfee (default: 5000 millisatoshi).
|
||||
- OPTIONAL: The `getroute_method` option can be for route search can be 'basic'
|
||||
or 'iterative'.
|
||||
'basic': Tries all routes sequentially.
|
||||
'iterative': Tries shorter and bigger routes first.
|
||||
|
||||
|
||||
#### Tips and Tricks for individual rebalance
|
||||
|
||||
- To find the correct channel IDs, you can use the `summary` plugin which can
|
||||
be found [here](https://github.com/lightningd/plugins/tree/master/summary).
|
||||
- The ideal amount is not too big, but not too small: it is difficult to find a
|
||||
route for a big payment, however some node refuses to forward too small
|
||||
amounts (i.e. less than a thousand msatoshi).
|
||||
- After some failed attempts, may worth checking the `lightningd` logs for
|
||||
further information.
|
||||
- Channels have a `channel_reserve_satoshis` value, which is usually 1% of the
|
||||
channel's total balance. Initially, this reserve may not be met, as only one
|
||||
side has funds; but the protocol ensures that there is always progress toward
|
||||
meeting this reserve, and once met, [it is maintained.](https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#rationale)
|
||||
Therefore you cannot rebalance a channel to be completely empty or full.
|
||||
@@ -1,23 +0,0 @@
|
||||
import re
|
||||
|
||||
|
||||
def cln_parse_rpcversion(string):
|
||||
"""
|
||||
Parse cln version string to determine RPC version.
|
||||
|
||||
cln switched from 'semver' alike `major.minor.sub[rcX][-mod]`
|
||||
to ubuntu style with version 22.11 `yy.mm[.patch][-mod]`
|
||||
make sure we can read all of them for (the next 80 years).
|
||||
"""
|
||||
rpcversion = string
|
||||
if rpcversion.startswith('v'): # strip leading 'v'
|
||||
rpcversion = rpcversion[1:]
|
||||
if rpcversion.find('-') != -1: # strip mods
|
||||
rpcversion = rpcversion[:rpcversion.find('-')]
|
||||
if re.search('.*(rc[\\d]*)$', rpcversion): # strip release candidates
|
||||
rpcversion = rpcversion[:rpcversion.find('rc')]
|
||||
if rpcversion.count('.') == 1: # imply patch version 0 if not given
|
||||
rpcversion = rpcversion + '.0'
|
||||
|
||||
# split and convert numeric string parts to actual integers
|
||||
return list(map(int, rpcversion.split('.')))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
pyln-client>=0.12
|
||||
@@ -1,147 +0,0 @@
|
||||
import os
|
||||
from pyln.testing.fixtures import * # noqa: F401,F403
|
||||
from pyln.client import Millisatoshi
|
||||
|
||||
plugin_path = os.path.join(os.path.dirname(__file__), "rebalance.py")
|
||||
plugin_opt = {'plugin': plugin_path}
|
||||
|
||||
|
||||
# waits for a bunch of nodes HTLCs to settle
|
||||
def wait_for_all_htlcs(nodes):
|
||||
for n in nodes:
|
||||
n.wait_for_htlcs()
|
||||
|
||||
|
||||
# waits for all nodes to have all scids gossip active
|
||||
def wait_for_all_active(nodes, scids):
|
||||
for n in nodes:
|
||||
for scid in scids:
|
||||
n.wait_channel_active(scid)
|
||||
|
||||
|
||||
def test_rebalance_starts(node_factory):
|
||||
l1 = node_factory.get_node()
|
||||
# Test dynamically
|
||||
l1.rpc.plugin_start(plugin_path)
|
||||
l1.rpc.plugin_stop(plugin_path)
|
||||
l1.rpc.plugin_start(plugin_path)
|
||||
l1.stop()
|
||||
# Then statically
|
||||
l1.daemon.opts["plugin"] = plugin_path
|
||||
l1.start()
|
||||
|
||||
|
||||
def test_rebalance_manual(node_factory, bitcoind):
|
||||
l1, l2, l3 = node_factory.line_graph(3, opts=plugin_opt)
|
||||
nodes = [l1, l2, l3]
|
||||
|
||||
# form a circle so we can do rebalancing
|
||||
l3.connect(l1)
|
||||
l3.fundchannel(l1)
|
||||
|
||||
# get scids
|
||||
scid12 = l1.get_channel_scid(l2)
|
||||
scid23 = l2.get_channel_scid(l3)
|
||||
scid31 = l3.get_channel_scid(l1)
|
||||
scids = [scid12, scid23, scid31]
|
||||
|
||||
# wait for each others gossip
|
||||
bitcoind.generate_block(6)
|
||||
for n in nodes:
|
||||
for scid in scids:
|
||||
n.wait_channel_active(scid)
|
||||
|
||||
# check we can do an auto amount rebalance
|
||||
result = l1.rpc.rebalance(scid12, scid31)
|
||||
print(result)
|
||||
assert result['status'] == 'complete'
|
||||
assert result['outgoing_scid'] == scid12
|
||||
assert result['incoming_scid'] == scid31
|
||||
assert result['hops'] == 3
|
||||
assert result['received'] == '500000000msat'
|
||||
|
||||
# wait until listpeers is up2date
|
||||
wait_for_all_htlcs(nodes)
|
||||
|
||||
# check that channels are now balanced
|
||||
c12 = l1.rpc.listpeerchannels(l2.info['id'])['channels'][0]
|
||||
c13 = l1.rpc.listpeerchannels(l3.info['id'])['channels'][0]
|
||||
assert abs(0.5 - (Millisatoshi(c12['to_us_msat']) / Millisatoshi(c12['total_msat']))) < 0.01
|
||||
assert abs(0.5 - (Millisatoshi(c13['to_us_msat']) / Millisatoshi(c13['total_msat']))) < 0.01
|
||||
|
||||
# check we can do a manual amount rebalance in the other direction
|
||||
result = l1.rpc.rebalance(scid31, scid12, '250000000msat')
|
||||
assert result['status'] == 'complete'
|
||||
assert result['outgoing_scid'] == scid31
|
||||
assert result['incoming_scid'] == scid12
|
||||
assert result['hops'] == 3
|
||||
assert result['received'] == '250000000msat'
|
||||
|
||||
# briefly check rebalancereport works
|
||||
report = l1.rpc.rebalancereport()
|
||||
assert report.get('rebalanceall_is_running') is False
|
||||
assert report.get('total_successful_rebalances') == 2
|
||||
|
||||
|
||||
def test_rebalance_all(node_factory, bitcoind):
|
||||
l1, l2, l3 = node_factory.line_graph(3, opts=plugin_opt)
|
||||
nodes = [l1, l2, l3]
|
||||
|
||||
# check we get an error if theres just one channel
|
||||
result = l1.rpc.rebalanceall()
|
||||
assert result['message'] == 'Error: Not enough open channels to rebalance anything'
|
||||
|
||||
# now we add another 100% outgoing liquidity to l1 which does not help
|
||||
l4 = node_factory.get_node()
|
||||
l1.connect(l4)
|
||||
l1.fundchannel(l4)
|
||||
|
||||
# test this is still not possible
|
||||
result = l1.rpc.rebalanceall()
|
||||
assert result['message'] == 'Error: Not enough liquidity to rebalance anything'
|
||||
|
||||
# remove l4 it does not distort further testing
|
||||
l1.rpc.close(l1.get_channel_scid(l4))
|
||||
|
||||
# now we form a circle so we can do actually rebalanceall
|
||||
l3.connect(l1)
|
||||
l3.fundchannel(l1)
|
||||
|
||||
# get scids
|
||||
scid12 = l1.get_channel_scid(l2)
|
||||
scid23 = l2.get_channel_scid(l3)
|
||||
scid31 = l3.get_channel_scid(l1)
|
||||
scids = [scid12, scid23, scid31]
|
||||
|
||||
# wait for each others gossip
|
||||
bitcoind.generate_block(6)
|
||||
wait_for_all_active(nodes, scids)
|
||||
|
||||
# check that theres nothing to stop when theres nothing to stop
|
||||
result = l1.rpc.rebalancestop()
|
||||
assert result['message'] == "No rebalance is running, nothing to stop."
|
||||
|
||||
# check the rebalanceall starts
|
||||
result = l1.rpc.rebalanceall(feeratio=5.0) # we need high fees to work
|
||||
assert result['message'].startswith('Rebalance started')
|
||||
l1.daemon.wait_for_logs([f"tries to rebalance: {scid12} -> {scid31}",
|
||||
f"Automatic rebalance finished"])
|
||||
|
||||
# check additional calls to stop return 'nothing to stop' + last message
|
||||
result = l1.rpc.rebalancestop()['message']
|
||||
assert result.startswith("No rebalance is running, nothing to stop. "
|
||||
"Last 'rebalanceall' gave: Automatic rebalance finished")
|
||||
|
||||
# wait until listpeers is up2date
|
||||
wait_for_all_htlcs(nodes)
|
||||
|
||||
# check that channels are now balanced
|
||||
c12 = l1.rpc.listpeerchannels(l2.info['id'])['channels'][0]
|
||||
c13 = l1.rpc.listpeerchannels(l3.info['id'])['channels'][0]
|
||||
assert abs(0.5 - (Millisatoshi(c12['to_us_msat']) / Millisatoshi(c12['total_msat']))) < 0.01
|
||||
assert abs(0.5 - (Millisatoshi(c13['to_us_msat']) / Millisatoshi(c13['total_msat']))) < 0.01
|
||||
|
||||
# briefly check rebalancereport works
|
||||
report = l1.rpc.rebalancereport()
|
||||
assert report.get('rebalanceall_is_running') is False
|
||||
assert report.get('total_successful_rebalances') == 2
|
||||
Reference in New Issue
Block a user