def initialize_ignored_channels(self, routes, fee_limit_msat, min_fee_last_hop): if self.reckless: self.output.print_line( format_error( "Also considering economically unviable channels for routes." )) for chan_id in self.excluded: self.output.print_line( f"Channel {format_channel_id(chan_id)} is excluded:") routes.ignore_channel(chan_id) if self.first_hop_channel: if min_fee_last_hop and not self.reckless: self.ignore_cheap_channels_for_last_hop( min_fee_last_hop, routes) 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 chan_id = self.first_hop_channel.chan_id from_pub_key = self.first_hop_channel.remote_pubkey to_pub_key = self.lnd.get_own_pubkey() routes.ignore_edge_from_to(chan_id, from_pub_key, to_pub_key, show_message=False) if self.last_hop_channel: 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() to_pub_key = self.last_hop_channel.remote_pubkey 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 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(): try: fee_rate = self.lnd.get_ppm_to(channel.chan_id) if fee_rate > max_fee_rate_first_hop and self.first_hop_channel != channel: routes.ignore_first_hop(channel, show_message=False) except: pass for channel in self.lnd.get_channels(): if self.low_outbound_liquidity_after_sending(channel, self.amount): routes.ignore_first_hop(channel, show_message=False)
def handle_error(self, response, route, routes): code = response.failure.code failure_source_pubkey = Logic.get_failure_source_pubkey( response, route) if code == 15: self.output.print_line(format_warning("Temporary channel failure")) routes.ignore_edge_on_route(failure_source_pubkey, route) elif code == 18: self.output.print_line(format_warning("Unknown next peer")) routes.ignore_edge_on_route(failure_source_pubkey, route) elif code == 12: self.output.print_line(format_warning("Fee insufficient")) elif code == 14: self.output.print_line(format_warning("Channel disabled")) routes.ignore_edge_on_route(failure_source_pubkey, route) elif code == 13: self.output.print_line(format_warning("Incorrect CLTV expiry")) routes.ignore_edge_on_route(failure_source_pubkey, route) else: self.output.print_line( format_error(f"Unknown error code {repr(code)}:")) self.output.print_line(format_error(repr(response)))
def get_amount(self): amount = None if self.arguments.amount: if self.arguments.reckless and self.arguments.amount > MAX_SATOSHIS_PER_TRANSACTION: self.output.print_line( format_error("Trying to send wumbo transaction")) return self.arguments.amount else: amount = min(self.arguments.amount, MAX_SATOSHIS_PER_TRANSACTION) if not self.arguments.adjust_amount_to_limits: return amount should_send = 0 can_send = 0 if self.first_hop_channel: should_send = -self.get_rebalance_amount(self.first_hop_channel) can_send = self.get_amount_can_send(self.first_hop_channel) if can_send < 0: from_alias = self.lnd.get_node_alias( self.first_hop_channel.remote_pubkey) print( f"Error: source channel {format_channel_id(self.first_hop_channel.chan_id)} to " f"{format_alias(from_alias)} needs to {chalk.green('receive')} funds to be within bounds," f" you want it to {chalk.red('send')} funds. " "Specify amount manually if this was intended.") return 0 should_receive = 0 can_receive = 0 if self.last_hop_channel: should_receive = self.get_rebalance_amount(self.last_hop_channel) can_receive = self.get_amount_can_receive(self.last_hop_channel) if can_receive < 0: to_alias = self.lnd.get_node_alias( self.last_hop_channel.remote_pubkey) print( f"Error: target channel {format_channel_id(self.last_hop_channel.chan_id)} to " f"{format_alias(to_alias)} needs to {chalk.green('send')} funds to be within bounds, " f"you want it to {chalk.red('receive')} funds." f" Specify amount manually if this was intended.") return 0 if self.first_hop_channel and self.last_hop_channel: computed_amount = max(min(can_receive, should_send), min(can_send, should_receive)) elif self.first_hop_channel: computed_amount = should_send else: computed_amount = should_receive computed_amount = int(computed_amount) if computed_amount >= 0: computed_amount = min(computed_amount, MAX_SATOSHIS_PER_TRANSACTION) computed_amount = max(computed_amount, -MAX_SATOSHIS_PER_TRANSACTION) if amount is not None: if computed_amount >= 0: computed_amount = min(amount, computed_amount) else: computed_amount = max(-amount, computed_amount) return computed_amount
def start(self): if self.arguments.list_candidates and self.arguments.show_only: channel_id = self.parse_channel_id(self.arguments.show_only) channel = self.get_channel_for_channel_id(channel_id) self.show_channel(channel) sys.exit(0) if self.arguments.listcompact: self.list_channels_compact() sys.exit(0) if self.arguments.list_candidates: incoming = self.arguments.incoming is None or self.arguments.incoming if incoming: self.list_channels(reverse=False) else: self.list_channels(reverse=True) sys.exit(0) if self.first_hop_channel_id == -1: self.first_hop_channel = random.choice( self.get_first_hop_candidates()) else: self.first_hop_channel = self.get_channel_for_channel_id( self.first_hop_channel_id) if self.last_hop_channel_id == -1: self.last_hop_channel = random.choice( self.get_last_hop_candidates()) else: self.last_hop_channel = self.get_channel_for_channel_id( self.last_hop_channel_id) amount = self.get_amount() if self.arguments.percentage: new_amount = int(round(amount * self.arguments.percentage / 100)) print( f"Using {self.arguments.percentage}% of amount {format_amount(amount)}: {format_amount(new_amount)}" ) amount = new_amount if amount == 0: print(f"Amount is {format_amount(0)} sat, nothing to do") sys.exit(1) if amount < self.min_amount: print( f"Amount {format_amount(amount)} sat is below limit of {format_amount(self.min_amount)} sat, " f"nothing to do (see --min-amount)") sys.exit(1) if self.arguments.reckless: self.output.print_line(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 excluded = [] if self.arguments.exclude: for chan_id in self.arguments.exclude: excluded.append(self.parse_channel_id(chan_id)) return Logic(self.lnd, self.first_hop_channel, self.last_hop_channel, amount, excluded, fee_factor, fee_limit_sat, fee_ppm_limit, self.min_local, self.min_remote, self.output, self.arguments.reckless).rebalance()
def fees_too_high(self, route, routes): policy_first_hop = self.lnd.get_policy_to(route.hops[0].chan_id) amount_msat = route.total_amt_msat missed_fee_msat = self.compute_fee( amount_msat / 1_000, policy_first_hop.fee_rate_milli_msat, policy_first_hop) * 1_000 policy_last_hop = self.lnd.get_policy_to(route.hops[-1].chan_id) fee_rate_last_hop = policy_last_hop.fee_rate_milli_msat original_fee_rate_last_hop = fee_rate_last_hop if fee_rate_last_hop > MAX_FEE_RATE: fee_rate_last_hop = MAX_FEE_RATE expected_income_msat = self.fee_factor * self.compute_fee( amount_msat / 1_000, fee_rate_last_hop, policy_last_hop) * 1_000 rebalance_fee_msat = route.total_fees_msat high_fees = rebalance_fee_msat + missed_fee_msat > expected_income_msat if high_fees: if self.reckless: self.output.print_line( format_error("Considering route with high fees")) return False difference_msat = -rebalance_fee_msat - missed_fee_msat + expected_income_msat first_hop_alias = format_alias( self.lnd.get_node_alias(route.hops[0].pub_key)) last_hop_alias = format_alias( self.lnd.get_node_alias(route.hops[-2].pub_key)) self.output.print_line("") if fee_rate_last_hop != original_fee_rate_last_hop: self.output.print_line( f"Calculating using capped fee rate {MAX_FEE_RATE} for inbound channel " f"(with {last_hop_alias}, original fee rate {original_fee_rate_last_hop})" ) route_ppm = int(route.total_fees_msat * 1_000_000 / route.total_amt_msat) route_fee_formatted = format_fee_msat(rebalance_fee_msat) route_ppm_formatted = format_ppm(route_ppm) self.output.print_line( f"Skipping route due to high fees " f"(fee {route_fee_formatted}, {route_ppm_formatted})") self.output.print_route(route) future_income_formatted = format_earning( math.floor(expected_income_msat), 8) self.output.print_without_linebreak( f" {future_income_formatted}: " f"expected future fee income for inbound channel (with {last_hop_alias})" ) if self.fee_factor != 1.0: self.output.print_line(f" (factor {self.fee_factor})") else: self.output.print_line("") transaction_fees_formatted = format_fee_msat( int(rebalance_fee_msat), 8) missed_fee_formatted = format_fee_msat(math.ceil(missed_fee_msat), 8) difference_formatted = format_fee_msat_red( math.ceil(difference_msat), 8) self.output.print_line( f"- {transaction_fees_formatted}: rebalance transaction fees") self.output.print_line( f"- {missed_fee_formatted}: " f"missing out on future fees for outbound channel (with {first_hop_alias})" ) self.output.print_line(f"= {difference_formatted}") routes.ignore_high_fee_hops(route) return high_fees