diff --git a/README.md b/README.md index b770912..ae20a20 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,8 @@ However, keep in mind that you will only earn those fees if your node actually f The rebalance transaction is not performed if the transaction fees plus the implicit costs (1) are higher than the possible future earnings (2). +**If you really want to, you may disable these safety checks with `--reckless`.** + ### Example You have lots of funds in channel `11111111` and nothing in channel `22222222`. You would like to send funds through channel `11111111` (source channel) through the lightning network, and finally @@ -237,7 +239,8 @@ Please make sure to set realistic fee rates, which at best are already known to ### Command line arguments ``` -usage: rebalance.py [-h] [--lnddir LNDDIR] [--grpc GRPC] [-l] [--show-all] [-o | -i] [-f CHANNEL] [-t CHANNEL] [-a AMOUNT] [--min-amount MIN_AMOUNT] [-e EXCLUDE] [--fee-factor FEE_FACTOR | --fee-limit FEE_LIMIT | --fee-ppm-limit FEE_PPM_LIMIT] +usage: rebalance.py [-h] [--lnddir LNDDIR] [--grpc GRPC] [-l] [--show-all] [-o | -i] [-f CHANNEL] [-t CHANNEL] [-a AMOUNT | -p PERCENTAGE] [--min-amount MIN_AMOUNT] [--min-local MIN_LOCAL] [--min-remote MIN_REMOTE] [-e EXCLUDE] [--reckless] + [--fee-factor FEE_FACTOR | --fee-limit FEE_LIMIT | --fee-ppm-limit FEE_PPM_LIMIT] optional arguments: -h, --help show this help message and exit @@ -262,13 +265,20 @@ rebalance: Channel ID of the incoming channel (funds will be sent to this channel). You may also use -1 to choose a random candidate. -a AMOUNT, --amount AMOUNT Amount of the rebalance, in satoshis. If not specified, the amount computed for a perfect rebalance will be used (up to the maximum of 4,294,967 satoshis) + -p PERCENTAGE, --percentage PERCENTAGE + Set the amount to a percentage of the computed amount. As an example, if this is set to 50, half of the computed amount will be used. See --amount. --min-amount MIN_AMOUNT (Default: 10,000) If the given or computed rebalance amount is below this limit, nothing is done. + --min-local MIN_LOCAL + (Default: 1,000,000) Ensure that the channels have at least this amount as outbound liquidity. + --min-remote MIN_REMOTE + (Default: 1,000,000) Ensure that the channels have at least this amount as inbound liquidity. -e EXCLUDE, --exclude EXCLUDE Exclude the given channel ID as the outgoing channel (no funds will be taken out of excluded channels) + --reckless Allow rebalance transactions that are not economically viable. You might also want to set --min-local 0 and --min-local 0. If set, you also need to set --amount and either --fee-limit or --fee-ppm-limit. --fee-factor FEE_FACTOR (default: 1.0) Compare the costs against the expected income, scaled by this factor. As an example, with --fee-factor 1.5, routes that cost at most 150% of the expected earnings are tried. Use values smaller than 1.0 to restrict - routes to only consider those earning more/costing less. + routes to only consider those earning more/costing less. This factor is ignored with --reckless. --fee-limit FEE_LIMIT If set, only consider rebalance transactions that cost up to the given number of satoshis. --fee-ppm-limit FEE_PPM_LIMIT diff --git a/logic.py b/logic.py index 384ed88..abc5068 100644 --- a/logic.py +++ b/logic.py @@ -1,5 +1,6 @@ import math +import output from output import Output, format_alias, format_fee_msat, format_ppm, format_amount, format_boring_string, \ format_fee_sat, format_warning, format_error, format_earning, format_fee_msat_red from routes import Routes @@ -24,7 +25,8 @@ def __init__( fee_ppm_limit, min_local, min_remote, - output: Output + output: Output, + reckless ): self.lnd = lnd self.first_hop_channel = first_hop_channel @@ -38,6 +40,7 @@ def __init__( self.min_local = min_local self.min_remote = min_remote self.output = output + self.reckless = reckless if not self.fee_factor: self.fee_factor = 1.0 @@ -93,7 +96,10 @@ def get_fee_limit_msat(self): if self.fee_limit_sat: fee_limit_msat = self.fee_limit_sat * 1_000 elif self.fee_ppm_limit: - fee_limit_msat = max(1_000, self.fee_ppm_limit * self.amount / 1_000) + if self.reckless: + fee_limit_msat = self.fee_ppm_limit * self.amount / 1_000 + else: + fee_limit_msat = max(1_000, self.fee_ppm_limit * self.amount / 1_000) elif self.last_hop_channel: fee_rate = self.lnd.get_ppm_to(self.last_hop_channel.chan_id) last_hop_alias = self.lnd.get_node_alias(self.last_hop_channel.remote_pubkey) @@ -322,10 +328,12 @@ def get_channel_for_channel_id(self, channel_id): self.output.print_line(f"Unable to find channel with id {channel_id}!") def initialize_ignored_channels(self, routes, fee_limit_msat, min_ppm_last_hop): + if self.reckless: + self.output.print_line(output.format_error("Not ignoring economically unviable channels.")) if self.first_hop_channel: - if min_ppm_last_hop: + if min_ppm_last_hop and not self.reckless: self.ignore_low_ppm_channels_for_last_hop(min_ppm_last_hop, routes) - if not self.last_hop_channel: + if not self.last_hop_channel and not self.reckless: self.ignore_last_hops_with_low_inbound(routes) # avoid me - X - me via the same channel/peer @@ -336,7 +344,8 @@ def initialize_ignored_channels(self, routes, fee_limit_msat, min_ppm_last_hop): chan_id, from_pub_key, to_pub_key, show_message=False ) if self.last_hop_channel: - self.ignore_first_hops_with_fee_rate_higher_than_last_hop(routes) + if not self.reckless: + self.ignore_first_hops_with_fee_rate_higher_than_last_hop(routes) # avoid me - X - me via the same channel/peer chan_id = self.last_hop_channel.chan_id from_pub_key = self.lnd.get_own_pubkey() @@ -344,7 +353,7 @@ def initialize_ignored_channels(self, routes, fee_limit_msat, min_ppm_last_hop): routes.ignore_edge_from_to( chan_id, from_pub_key, to_pub_key, show_message=False ) - if self.last_hop_channel and fee_limit_msat: + if self.last_hop_channel and fee_limit_msat and not self.reckless: # ignore first hops with high fee rate configured by our node (causing high missed future fees) max_fee_rate_first_hop = math.ceil(fee_limit_msat * 1_000 / self.amount) for channel in self.lnd.get_channels(): diff --git a/rebalance.py b/rebalance.py index d70d843..13a9e38 100755 --- a/rebalance.py +++ b/rebalance.py @@ -66,7 +66,11 @@ def get_rebalance_amount(self, channel): def get_amount(self): if self.arguments.amount: - return min(self.arguments.amount, MAX_SATOSHIS_PER_TRANSACTION) + if self.arguments.reckless and self.arguments.amount > MAX_SATOSHIS_PER_TRANSACTION: + self.output.print_line(output.format_error("Trying to send wumbo transaction")) + return self.arguments.amount + else: + return min(self.arguments.amount, MAX_SATOSHIS_PER_TRANSACTION) should_send = 0 can_send = 0 @@ -195,6 +199,9 @@ def start(self): f"nothing to do (see --min-amount)") sys.exit(0) + if self.arguments.reckless: + self.output.print_line(output.format_error("Reckless mode enabled!")) + fee_factor = self.arguments.fee_factor fee_limit_sat = self.arguments.fee_limit fee_ppm_limit = self.arguments.fee_ppm_limit @@ -210,7 +217,8 @@ def start(self): fee_ppm_limit, self.min_local, self.min_remote, - self.output + self.output, + self.arguments.reckless ).rebalance() def get_first_hop_candidates(self): @@ -243,6 +251,16 @@ def main(): argument_parser.print_help() sys.exit(1) + if arguments.reckless and not arguments.amount: + print("You need to specify an amount for --reckless") + argument_parser.print_help() + sys.exit(1) + + if arguments.reckless and not arguments.fee_limit and not arguments.fee_ppm_limit: + print("You need to specify a fee limit (-fee-limit or --fee-ppm-limit) for --reckless") + argument_parser.print_help() + sys.exit(1) + first_hop_channel_id = vars(arguments)["from"] last_hop_channel_id = arguments.to if not arguments.list_candidates and last_hop_channel_id is None and first_hop_channel_id is None: @@ -366,6 +384,14 @@ def get_argument_parser(): help="Exclude the given channel ID as the outgoing channel (no funds will be taken " "out of excluded channels)", ) + rebalance_group.add_argument( + "--reckless", + action="store_true", + default=False, + help="Allow rebalance transactions that are not economically viable. " + "You might also want to set --min-local 0 and --min-local 0. " + "If set, you also need to set --amount and either --fee-limit or --fee-ppm-limit." + ) fee_group = rebalance_group.add_mutually_exclusive_group() fee_group.add_argument( "--fee-factor", @@ -375,7 +401,7 @@ def get_argument_parser(): "income, scaled by this factor. As an example, with --fee-factor 1.5, " "routes that cost at most 150%% of the expected earnings are tried. Use values " "smaller than 1.0 to restrict routes to only consider those earning " - "more/costing less.", + "more/costing less. This factor is ignored with --reckless.", ) fee_group.add_argument( "--fee-limit",