mirror of
https://github.com/accumulator/charge-lnd.git
synced 2026-08-13 12:33:01 +02:00
Consideration of the unsettled balance
We add the amounts of pending htlcs to the local and remote balance of a channel. Incoming htlcs are added to the remote balance and outgoing htlcs to the local balance.
This commit is contained in:
parent
2b103c8d57
commit
2499f743e5
3 changed files with 135 additions and 45 deletions
|
|
@ -5,6 +5,7 @@ import grpc
|
|||
import sys
|
||||
import re
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
from .grpc_generated import lightning_pb2_grpc as lnrpc, lightning_pb2 as ln
|
||||
from .grpc_generated import router_pb2_grpc as routerrpc, router_pb2 as router
|
||||
|
|
@ -19,6 +20,81 @@ def debug(message):
|
|||
sys.stderr.write(message + "\n")
|
||||
|
||||
|
||||
class ChannelMetrics(SimpleNamespace):
|
||||
local_balance_settled: int = 0
|
||||
local_balance_unsettled: int = 0
|
||||
|
||||
remote_balance_settled: int = 0
|
||||
remote_balance_unsettled: int = 0
|
||||
|
||||
def local_balance_total(self):
|
||||
return self.local_balance_settled + self.local_balance_unsettled
|
||||
|
||||
def remote_balance_total(self):
|
||||
return self.remote_balance_settled + self.remote_balance_unsettled
|
||||
|
||||
|
||||
class PeerMetrics(SimpleNamespace):
|
||||
channels_active: int = 0
|
||||
channels_inactive: int = 0
|
||||
|
||||
local_active_balance_settled: int = 0
|
||||
local_active_balance_unsettled: int = 0
|
||||
local_inactive_balance_settled: int = 0
|
||||
local_inactive_balance_unsettled: int = 0
|
||||
|
||||
remote_active_balance_settled: int = 0
|
||||
remote_active_balance_unsettled: int = 0
|
||||
remote_inactive_balance_settled: int = 0
|
||||
remote_inactive_balance_unsettled: int = 0
|
||||
|
||||
def local_active_balance_total(self):
|
||||
return self.local_active_balance_settled + self.local_active_balance_unsettled
|
||||
|
||||
def remote_active_balance_total(self):
|
||||
return self.remote_active_balance_settled + self.remote_active_balance_unsettled
|
||||
|
||||
def local_inactive_balance_total(self):
|
||||
return self.local_inactive_balance_settled + self.local_inactive_balance_unsettled
|
||||
|
||||
def remote_inactive_balance_total(self):
|
||||
return self.remote_inactive_balance_settled + self.remote_inactive_balance_unsettled
|
||||
|
||||
def active_balance_total(self):
|
||||
return self.local_active_balance_total() + self.remote_active_balance_total()
|
||||
|
||||
def inactive_balance_total(self):
|
||||
return self.local_inactive_balance_total() + self.remote_inactive_balance_total()
|
||||
|
||||
|
||||
def channel_metrics(channel):
|
||||
return ChannelMetrics(
|
||||
local_balance_settled=channel.local_balance,
|
||||
local_balance_unsettled=sum(h.amount for h in channel.pending_htlcs if not h.incoming),
|
||||
remote_balance_settled=channel.remote_balance,
|
||||
remote_balance_unsettled=sum(h.amount for h in channel.pending_htlcs if h.incoming)
|
||||
)
|
||||
|
||||
|
||||
def peer_metrics(channels):
|
||||
pm = PeerMetrics()
|
||||
for channel in channels:
|
||||
cm = channel_metrics(channel)
|
||||
if channel.active:
|
||||
pm.local_active_balance_settled += cm.local_balance_settled
|
||||
pm.local_active_balance_unsettled += cm.local_balance_unsettled
|
||||
pm.remote_active_balance_settled += cm.remote_balance_settled
|
||||
pm.remote_active_balance_unsettled += cm.remote_balance_unsettled
|
||||
pm.channels_active += 1
|
||||
else:
|
||||
pm.local_inactive_balance_settled += cm.local_balance_settled
|
||||
pm.local_inactive_balance_unsettled += cm.local_balance_unsettled
|
||||
pm.remote_inactive_balance_settled += cm.remote_balance_settled
|
||||
pm.remote_inactive_balance_unsettled += cm.remote_balance_unsettled
|
||||
pm.channels_inactive += 1
|
||||
return pm
|
||||
|
||||
|
||||
class Lnd:
|
||||
def __init__(self, lnd_dir, server, tls_cert_path=None, macaroon_path=None):
|
||||
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
|
||||
|
|
@ -41,7 +117,10 @@ class Lnd:
|
|||
self.chan_info = {}
|
||||
self.fwdhistory = {}
|
||||
self.valid = True
|
||||
self.dict_channels = None
|
||||
self.peer_channels = {}
|
||||
self.chan_metrics = {}
|
||||
self.peer_metrics = {}
|
||||
try:
|
||||
self.feereport = self.get_feereport()
|
||||
except grpc._channel._InactiveRpcError:
|
||||
|
|
@ -205,15 +284,31 @@ class Lnd:
|
|||
request = ln.ListChannelsRequest()
|
||||
self.channels = self.lnstub.ListChannels(request).channels
|
||||
return self.channels
|
||||
|
||||
|
||||
def get_dict_channels(self):
|
||||
if self.dict_channels is None:
|
||||
channels = self.get_channels()
|
||||
self.dict_channels = {}
|
||||
for c in channels:
|
||||
self.dict_channels[c.chan_id] = c
|
||||
return self.dict_channels
|
||||
|
||||
def get_chan_metrics(self, chanid):
|
||||
if not chanid in self.chan_metrics:
|
||||
self.chan_metrics[chanid] = channel_metrics(self.get_dict_channels()[chanid])
|
||||
return self.chan_metrics[chanid]
|
||||
|
||||
# Get all channels shared with a node
|
||||
def get_shared_channels(self, peerid):
|
||||
# See example: https://github.com/lightningnetwork/lnd/issues/3930#issuecomment-596041700
|
||||
byte_peerid=bytes.fromhex(peerid)
|
||||
if peerid not in self.peer_channels:
|
||||
request = ln.ListChannelsRequest(peer=byte_peerid)
|
||||
self.peer_channels[peerid] = self.lnstub.ListChannels(request).channels
|
||||
def get_peer_channels(self, peerid):
|
||||
if not peerid in self.peer_channels:
|
||||
channels = self.get_channels()
|
||||
self.peer_channels[peerid] = [c for c in channels if c.remote_pubkey == peerid]
|
||||
return self.peer_channels[peerid]
|
||||
|
||||
def get_peer_metrics(self, peerid):
|
||||
if not peerid in self.peer_metrics:
|
||||
self.peer_metrics[peerid] = peer_metrics(self.get_peer_channels(peerid))
|
||||
return self.peer_metrics[peerid]
|
||||
|
||||
def min_version(self, major, minor, patch=0):
|
||||
p = re.compile("(\d+)\.(\d+)\.(\d+).*")
|
||||
|
|
|
|||
|
|
@ -170,26 +170,17 @@ class Policies:
|
|||
# Consider multiple channels per node policies
|
||||
if any(map(lambda n: "node." + n in config, multiple_chans_props)):
|
||||
|
||||
shared_chans=self.lnd.get_shared_channels(channel.remote_pubkey)
|
||||
metrics = self.lnd.get_peer_metrics(channel.remote_pubkey)
|
||||
|
||||
local_active_balance = remote_active_balance = active_total = 0
|
||||
local_inactive_balance = remote_inactive_balance = inactive_total = 0
|
||||
channels_active = channels_inactive = 0
|
||||
|
||||
for chan in (shared_chans):
|
||||
if chan.active:
|
||||
local_active_balance += chan.local_balance
|
||||
remote_active_balance += chan.remote_balance
|
||||
channels_active += 1
|
||||
else:
|
||||
local_inactive_balance += chan.local_balance
|
||||
remote_inactive_balance += chan.remote_balance
|
||||
channels_inactive += 1
|
||||
|
||||
active_total = local_active_balance + remote_active_balance
|
||||
inactive_total = local_inactive_balance + remote_inactive_balance
|
||||
channels_active = metrics.channels_active
|
||||
channels_inactive = metrics.channels_inactive
|
||||
|
||||
local_active_balance = metrics.local_active_balance_total()
|
||||
local_inactive_balance = metrics.local_inactive_balance_total()
|
||||
active_total = metrics.active_balance_total()
|
||||
inactive_total = metrics.inactive_balance_total()
|
||||
|
||||
all_total = active_total + inactive_total
|
||||
|
||||
ratio_all = (local_active_balance + local_inactive_balance) / all_total
|
||||
|
||||
# Cannot calculate the active ratio if the active total is 0
|
||||
|
|
@ -284,7 +275,11 @@ class Policies:
|
|||
if 'chan.private' in config and not channel.private == config.getboolean('chan.private'):
|
||||
return False
|
||||
|
||||
ratio = channel.local_balance/(channel.local_balance + channel.remote_balance)
|
||||
metrics = self.lnd.get_chan_metrics(channel.chan_id)
|
||||
local_balance = metrics.local_balance_total()
|
||||
remote_balance = metrics.remote_balance_total()
|
||||
|
||||
ratio = local_balance/(local_balance + remote_balance)
|
||||
if 'chan.max_ratio' in config and not config.getfloat('chan.max_ratio') >= ratio:
|
||||
return False
|
||||
if 'chan.min_ratio' in config and not config.getfloat('chan.min_ratio') <= ratio:
|
||||
|
|
@ -293,13 +288,13 @@ class Policies:
|
|||
return False
|
||||
if 'chan.min_capacity' in config and not config.getint('chan.min_capacity') <= channel.capacity:
|
||||
return False
|
||||
if 'chan.max_local_balance' in config and not config.getint('chan.max_local_balance') >= channel.local_balance:
|
||||
if 'chan.max_local_balance' in config and not config.getint('chan.max_local_balance') >= local_balance:
|
||||
return False
|
||||
if 'chan.min_local_balance' in config and not config.getint('chan.min_local_balance') <= channel.local_balance:
|
||||
if 'chan.min_local_balance' in config and not config.getint('chan.min_local_balance') <= local_balance:
|
||||
return False
|
||||
if 'chan.max_remote_balance' in config and not config.getint('chan.max_remote_balance') >= channel.remote_balance:
|
||||
if 'chan.max_remote_balance' in config and not config.getint('chan.max_remote_balance') >= remote_balance:
|
||||
return False
|
||||
if 'chan.min_remote_balance' in config and not config.getint('chan.min_remote_balance') <= channel.remote_balance:
|
||||
if 'chan.min_remote_balance' in config and not config.getint('chan.min_remote_balance') <= remote_balance:
|
||||
return False
|
||||
|
||||
chan_info = self.lnd.get_chan_info(channel.chan_id)
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ def strategy_static(channel, policy, **kwargs):
|
|||
|
||||
@strategy(name = 'proportional')
|
||||
def strategy_proportional(channel, policy, **kwargs):
|
||||
lnd = kwargs['lnd']
|
||||
|
||||
if policy.getint('min_fee_ppm_delta',-1) < 0:
|
||||
policy.set('min_fee_ppm_delta', 10) # set delta to 10 if not defined
|
||||
ppm_min = policy.getint('min_fee_ppm')
|
||||
|
|
@ -128,27 +130,25 @@ def strategy_proportional(channel, policy, **kwargs):
|
|||
raise Exception('proportional strategy requires min_fee_ppm and max_fee_ppm properties')
|
||||
|
||||
if policy.getbool('sum_peer_chans', False):
|
||||
lnd = kwargs['lnd']
|
||||
shared_chans=lnd.get_shared_channels(channel.remote_pubkey)
|
||||
local_balance = 0
|
||||
remote_balance = 0
|
||||
for c in (shared_chans):
|
||||
# Include balance of all active channels with peer
|
||||
if c.active:
|
||||
local_balance += c.local_balance
|
||||
remote_balance += c.remote_balance
|
||||
metrics = lnd.get_peer_metrics(channel.remote_pubkey)
|
||||
|
||||
local_balance = metrics.local_active_balance_total()
|
||||
remote_balance = metrics.remote_active_balance_total()
|
||||
total_balance = local_balance + remote_balance
|
||||
if total_balance == 0:
|
||||
|
||||
if metrics.channels_active == 0:
|
||||
# Sum inactive channels because the node is likely offline with no active channels.
|
||||
# When they come back online their fees won't be changed.
|
||||
for c in (shared_chans):
|
||||
if not c.active:
|
||||
local_balance += c.local_balance
|
||||
remote_balance += c.remote_balance
|
||||
local_balance += metrics.local_inactive_balance_total()
|
||||
remote_balance += metrics.remote_inactive_balance_total()
|
||||
total_balance = local_balance + remote_balance
|
||||
ratio = local_balance/total_balance
|
||||
else:
|
||||
ratio = channel.local_balance/(channel.local_balance + channel.remote_balance)
|
||||
metrics = lnd.get_chan_metrics(channel.chan_id)
|
||||
|
||||
local_balance = metrics.local_balance_total()
|
||||
remote_balance = metrics.remote_balance_total()
|
||||
ratio = local_balance/(local_balance + remote_balance)
|
||||
|
||||
ppm = int(ppm_min + (1.0 - ratio) * (ppm_max - ppm_min))
|
||||
# clamp to 0..inf
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue