mirror of
https://github.com/cryptosharks131/lndg.git
synced 2026-08-16 13:00:40 +02:00
599 lines
No EOL
40 KiB
Python
599 lines
No EOL
40 KiB
Python
import django
|
|
from django.db.models import Max, Sum, Avg, Count
|
|
from django.db.models.functions import TruncDay
|
|
from datetime import datetime, timedelta
|
|
from gui.lnd_deps import lightning_pb2 as ln
|
|
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
|
|
from gui.lnd_deps import signer_pb2 as lns
|
|
from gui.lnd_deps import signer_pb2_grpc as lnsigner
|
|
from gui.lnd_deps.lnd_connect import lnd_connect
|
|
from lndg import settings
|
|
from os import environ
|
|
from pandas import DataFrame
|
|
from requests import get
|
|
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
|
|
django.setup()
|
|
from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels, HistFailedHTLC, PeerEvents
|
|
from gui import af
|
|
|
|
def update_payments(stub):
|
|
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
|
|
inflight_payments = Payments.objects.filter(status=1).order_by('index')
|
|
for payment in inflight_payments:
|
|
payment_data = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=payment.index-1, max_payments=1)).payments
|
|
#Ignore inflight payments before 30 days
|
|
if len(payment_data) > 0 and payment.payment_hash == payment_data[0].payment_hash and payment.creation_date > (datetime.now() - timedelta(days=30)):
|
|
update_payment(stub, payment_data[0], self_pubkey)
|
|
else:
|
|
payment.status = 3
|
|
payment.save()
|
|
last_index = Payments.objects.aggregate(Max('index'))['index__max'] if Payments.objects.exists() else 0
|
|
payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
|
|
for payment in payments:
|
|
#print(f"{datetime.now().strftime('%c')} : Processing New {payment.payment_index=} {payment.status=} {payment.payment_hash=}")
|
|
try:
|
|
new_payment = Payments(creation_date=datetime.fromtimestamp(payment.creation_date), payment_hash=payment.payment_hash, value=round(payment.value_msat/1000, 3), fee=round(payment.fee_msat/1000, 3), status=payment.status, index=payment.payment_index)
|
|
new_payment.save()
|
|
except Exception as e:
|
|
#Error inserting, try to update instead
|
|
print(f"{datetime.now().strftime('%c')} : Error processing {new_payment=} : {str(e)=}")
|
|
update_payment(stub, payment, self_pubkey)
|
|
|
|
def update_payment(stub, payment, self_pubkey):
|
|
db_payment = Payments.objects.filter(payment_hash=payment.payment_hash)[0]
|
|
db_payment.creation_date = datetime.fromtimestamp(payment.creation_date)
|
|
db_payment.value = round(payment.value_msat/1000, 3)
|
|
db_payment.fee = round(payment.fee_msat/1000, 3)
|
|
db_payment.status = payment.status
|
|
db_payment.index = payment.payment_index
|
|
if payment.status == 2 or payment.status == 1:
|
|
PaymentHops.objects.filter(payment_hash=db_payment).delete()
|
|
db_payment.chan_out = None
|
|
db_payment.rebal_chan = None
|
|
db_payment.save()
|
|
for attempt in payment.htlcs:
|
|
if attempt.status == 1 or attempt.status == 0:
|
|
hops = attempt.route.hops
|
|
hop_count = 0
|
|
cost_to = 0
|
|
total_hops = len(hops)
|
|
for hop in hops:
|
|
hop_count += 1
|
|
try:
|
|
alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=hop.pub_key, include_channels=False)).node.alias
|
|
except:
|
|
alias = ''
|
|
fee = hop.fee_msat/1000
|
|
if hop_count == total_hops:
|
|
# Add additional HTLC information in last hop alias
|
|
alias += f'[ {payment.status}-{attempt.status}-{attempt.failure.code}-{attempt.failure.failure_source_index} ]'
|
|
#if hop_count == total_hops:
|
|
#print(f"{datetime.now().strftime('%c')} : Debug Hop {attempt.attempt_id=} {attempt.route.total_amt=} {hop.mpp_record.payment_addr.hex()=} {hop.mpp_record.total_amt_msat=} {hop.amp_record=} {db_payment.payment_hash=}")
|
|
if attempt.status == 1 or attempt.status == 0 or (attempt.status == 2 and attempt.failure.code in (1,2,12)):
|
|
PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save()
|
|
cost_to += fee
|
|
if hop_count == 1 and attempt.status == 1:
|
|
if db_payment.chan_out is None:
|
|
db_payment.chan_out = hop.chan_id
|
|
db_payment.chan_out_alias = alias
|
|
else:
|
|
db_payment.chan_out = 'MPP'
|
|
db_payment.chan_out_alias = 'MPP'
|
|
if hop_count == total_hops and 5482373484 in hop.custom_records and db_payment.keysend_preimage is None:
|
|
records = hop.custom_records
|
|
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
|
db_payment.keysend_preimage = records[5482373484].hex()
|
|
db_payment.message = message
|
|
if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None:
|
|
db_payment.rebal_chan = hop.chan_id
|
|
db_payment.save()
|
|
|
|
def update_invoices(stub):
|
|
open_invoices = Invoices.objects.filter(state=0).order_by('index')
|
|
for open_invoice in open_invoices:
|
|
#print(f"{datetime.now().strftime('%c')} : Processing open invoice {open_invoice.index=} {open_invoice.state=} {open_invoice.r_hash=}")
|
|
invoice_data = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=open_invoice.index-1, num_max_invoices=1)).invoices
|
|
if len(invoice_data) > 0 and open_invoice.r_hash == invoice_data[0].r_hash.hex():
|
|
update_invoice(stub, invoice_data[0], open_invoice)
|
|
else:
|
|
open_invoice.state = 2
|
|
open_invoice.save()
|
|
last_index = Invoices.objects.aggregate(Max('index'))['index__max'] if Invoices.objects.exists() else 0
|
|
invoices = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=last_index, num_max_invoices=100)).invoices
|
|
for invoice in invoices:
|
|
db_invoice = Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index)
|
|
db_invoice.save()
|
|
update_invoice(stub, invoice, db_invoice)
|
|
|
|
def update_invoice(stub, invoice, db_invoice):
|
|
if invoice.state == 1:
|
|
if len(invoice.htlcs) > 0:
|
|
chan_in_id = invoice.htlcs[0].chan_id
|
|
alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
|
|
records = invoice.htlcs[0].custom_records
|
|
keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
|
|
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
|
if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
|
|
signerstub = lnsigner.SignerStub(lnd_connect())
|
|
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
|
|
try:
|
|
valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
|
|
except:
|
|
print(f"{datetime.now().strftime('%c')} : Unable to validate signature on invoice: {invoice.r_hash.hex()}")
|
|
valid = False
|
|
sender = records[34349339].hex() if valid == True else None
|
|
try:
|
|
sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
|
|
except:
|
|
sender_alias = None
|
|
else:
|
|
sender = None
|
|
sender_alias = None
|
|
else:
|
|
chan_in_id = None
|
|
alias = None
|
|
keysend_preimage = None
|
|
message = None
|
|
sender = None
|
|
sender_alias = None
|
|
db_invoice.state = invoice.state
|
|
db_invoice.amt_paid = invoice.amt_paid_sat
|
|
db_invoice.settle_date = datetime.fromtimestamp(invoice.settle_date)
|
|
db_invoice.chan_in = chan_in_id
|
|
db_invoice.chan_in_alias = alias
|
|
db_invoice.keysend_preimage = keysend_preimage
|
|
db_invoice.message = message
|
|
db_invoice.sender = sender
|
|
db_invoice.sender_alias = sender_alias
|
|
else:
|
|
db_invoice.state = invoice.state
|
|
db_invoice.save()
|
|
|
|
def update_forwards(stub):
|
|
records = Forwards.objects.count()
|
|
forwards = stub.ForwardingHistory(ln.ForwardingHistoryRequest(start_time=1420070400, index_offset=records, num_max_events=100)).forwarding_events
|
|
for forward in forwards:
|
|
incoming_peer_alias = Channels.objects.filter(chan_id=forward.chan_id_in)[0].alias if Channels.objects.filter(chan_id=forward.chan_id_in).exists() else None
|
|
incoming_peer_alias = Channels.objects.filter(chan_id=forward.chan_id_in)[0].remote_pubkey[:12] if incoming_peer_alias == '' else incoming_peer_alias
|
|
outgoing_peer_alias = Channels.objects.filter(chan_id=forward.chan_id_out)[0].alias if Channels.objects.filter(chan_id=forward.chan_id_out).exists() else None
|
|
outgoing_peer_alias = Channels.objects.filter(chan_id=forward.chan_id_out)[0].remote_pubkey[:12] if outgoing_peer_alias == '' else outgoing_peer_alias
|
|
Forwards(forward_date=datetime.fromtimestamp(forward.timestamp), chan_id_in=forward.chan_id_in, chan_id_out=forward.chan_id_out, chan_in_alias=incoming_peer_alias, chan_out_alias=outgoing_peer_alias, amt_in_msat=forward.amt_in_msat, amt_out_msat=forward.amt_out_msat, fee=round(forward.fee_msat/1000, 3)).save()
|
|
|
|
def update_channels(stub):
|
|
counter = 0
|
|
chan_list = []
|
|
channels = stub.ListChannels(ln.ListChannelsRequest()).channels
|
|
PendingHTLCs.objects.all().delete()
|
|
for channel in channels:
|
|
if Channels.objects.filter(chan_id=channel.chan_id).exists():
|
|
#Update the channel record with the most current data
|
|
db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
|
|
pending_channel = None
|
|
else:
|
|
#Create a record for this new channel
|
|
try:
|
|
alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=channel.remote_pubkey, include_channels=False)).node.alias
|
|
except:
|
|
alias = ''
|
|
channel_point = channel.channel_point
|
|
txid, index = channel_point.split(':')
|
|
db_channel = Channels()
|
|
db_channel.remote_pubkey = channel.remote_pubkey
|
|
db_channel.chan_id = channel.chan_id
|
|
db_channel.short_chan_id = str(channel.chan_id >> 40) + 'x' + str(channel.chan_id >> 16 & 0xFFFFFF) + 'x' + str(channel.chan_id & 0xFFFF)
|
|
db_channel.initiator = channel.initiator
|
|
db_channel.alias = alias
|
|
db_channel.funding_txid = txid
|
|
db_channel.output_index = index
|
|
db_channel.capacity = channel.capacity
|
|
db_channel.private = channel.private
|
|
db_channel.push_amt = channel.push_amount_sat
|
|
db_channel.close_address = channel.close_address
|
|
pending_channel = PendingChannels.objects.filter(funding_txid=txid, output_index=index)[0] if PendingChannels.objects.filter(funding_txid=txid, output_index=index).exists() else None
|
|
# Update basic channel data
|
|
db_channel.local_balance = channel.local_balance
|
|
db_channel.remote_balance = channel.remote_balance
|
|
db_channel.unsettled_balance = channel.unsettled_balance
|
|
db_channel.local_commit = channel.commit_fee
|
|
db_channel.local_chan_reserve = channel.local_chan_reserve_sat
|
|
db_channel.num_updates = channel.num_updates
|
|
db_channel.is_open = True
|
|
db_channel.total_sent = channel.total_satoshis_sent
|
|
db_channel.total_received = channel.total_satoshis_received
|
|
pending_out = 0
|
|
pending_in = 0
|
|
htlc_counter = 0
|
|
if len(channel.pending_htlcs) > 0:
|
|
for htlc in channel.pending_htlcs:
|
|
pending_htlc = PendingHTLCs()
|
|
pending_htlc.chan_id = db_channel.chan_id
|
|
pending_htlc.alias = db_channel.alias
|
|
pending_htlc.incoming = htlc.incoming
|
|
pending_htlc.amount = htlc.amount
|
|
pending_htlc.hash_lock = htlc.hash_lock.hex()
|
|
pending_htlc.expiration_height = htlc.expiration_height
|
|
pending_htlc.forwarding_channel = htlc.forwarding_channel
|
|
pending_htlc.forwarding_alias = Channels.objects.filter(chan_id=htlc.forwarding_channel)[0].alias if Channels.objects.filter(chan_id=htlc.forwarding_channel).exists() else '---'
|
|
pending_htlc.save()
|
|
if htlc.incoming == True:
|
|
pending_in += htlc.amount
|
|
else:
|
|
pending_out += htlc.amount
|
|
htlc_counter += 1
|
|
db_channel.pending_outbound = pending_out
|
|
db_channel.pending_inbound = pending_in
|
|
db_channel.htlc_count = htlc_counter
|
|
# Check for peer events
|
|
if db_channel.is_active != channel.active:
|
|
db_channel.last_update = datetime.now()
|
|
peer_alias = Peers.objects.filter(pubkey=db_channel.remote_pubkey)[0].alias if Peers.objects.filter(pubkey=db_channel.remote_pubkey).exists() else None
|
|
db_channel.alias = '' if peer_alias is None else peer_alias
|
|
if db_channel.is_active is None:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Connection', old_value=None, new_value=(1 if channel.active else 0), out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
elif channel.active:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Connection', old_value=0, new_value=1, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
else:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Connection', old_value=1, new_value=0, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.is_active = channel.active
|
|
try:
|
|
chan_data = stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel.chan_id))
|
|
if chan_data.node1_pub == channel.remote_pubkey:
|
|
local_policy = chan_data.node2_policy
|
|
remote_policy = chan_data.node1_policy
|
|
else:
|
|
local_policy = chan_data.node1_policy
|
|
remote_policy = chan_data.node2_policy
|
|
old_fee_rate = db_channel.local_fee_rate if db_channel.local_fee_rate is not None else 0
|
|
db_channel.local_base_fee = local_policy.fee_base_msat
|
|
db_channel.local_fee_rate = local_policy.fee_rate_milli_msat
|
|
db_channel.local_cltv = local_policy.time_lock_delta
|
|
db_channel.local_disabled = local_policy.disabled
|
|
db_channel.local_min_htlc_msat = local_policy.min_htlc
|
|
db_channel.local_max_htlc_msat = local_policy.max_htlc_msat
|
|
if db_channel.remote_cltv == -1:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='BaseFee', old_value=None, new_value=remote_policy.fee_base_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_base_fee = remote_policy.fee_base_msat
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='FeeRate', old_value=None, new_value=remote_policy.fee_rate_milli_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_fee_rate = remote_policy.fee_rate_milli_msat
|
|
if remote_policy.disabled:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Disabled', old_value=None, new_value=1, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
else:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Disabled', old_value=None, new_value=0, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_disabled = remote_policy.disabled
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='CTLV', old_value=None, new_value=remote_policy.time_lock_delta, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_cltv = remote_policy.time_lock_delta
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='MinHTLC', old_value=None, new_value=remote_policy.min_htlc, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_min_htlc_msat = remote_policy.min_htlc
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='MaxHTLC', old_value=None, new_value=remote_policy.max_htlc_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_max_htlc_msat = remote_policy.max_htlc_msat
|
|
else:
|
|
if db_channel.remote_base_fee != remote_policy.fee_base_msat:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='BaseFee', old_value=db_channel.remote_base_fee, new_value=remote_policy.fee_base_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_base_fee = remote_policy.fee_base_msat
|
|
if db_channel.remote_fee_rate != remote_policy.fee_rate_milli_msat:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='FeeRate', old_value=db_channel.remote_fee_rate, new_value=remote_policy.fee_rate_milli_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_fee_rate = remote_policy.fee_rate_milli_msat
|
|
if db_channel.remote_disabled != remote_policy.disabled:
|
|
if db_channel.remote_disabled is None:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Disabled', old_value=None, new_value=(1 if remote_policy.disabled else 0), out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
elif remote_policy.disabled:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Disabled', old_value=0, new_value=1, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
else:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='Disabled', old_value=1, new_value=0, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_disabled = remote_policy.disabled
|
|
if db_channel.remote_cltv != remote_policy.time_lock_delta:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='CTLV', old_value=db_channel.remote_cltv, new_value=remote_policy.time_lock_delta, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_cltv = remote_policy.time_lock_delta
|
|
if db_channel.remote_min_htlc_msat != remote_policy.min_htlc:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='MinHTLC', old_value=db_channel.remote_min_htlc_msat, new_value=remote_policy.min_htlc, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_min_htlc_msat = remote_policy.min_htlc
|
|
if db_channel.remote_max_htlc_msat != remote_policy.max_htlc_msat:
|
|
PeerEvents(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, event='MaxHTLC', old_value=db_channel.remote_max_htlc_msat, new_value=remote_policy.max_htlc_msat, out_liq=(db_channel.local_balance + db_channel.pending_outbound)).save()
|
|
db_channel.remote_max_htlc_msat = remote_policy.max_htlc_msat
|
|
except:
|
|
old_fee_rate = None
|
|
db_channel.local_base_fee = -1 if db_channel.local_base_fee is None else db_channel.local_base_fee
|
|
db_channel.local_fee_rate = -1 if db_channel.local_fee_rate is None else db_channel.local_fee_rate
|
|
db_channel.local_cltv = -1 if db_channel.local_cltv is None else db_channel.local_cltv
|
|
db_channel.local_disabled = False if db_channel.local_disabled is None else db_channel.local_disabled
|
|
db_channel.local_min_htlc_msat = -1 if db_channel.local_min_htlc_msat is None else db_channel.local_min_htlc_msat
|
|
db_channel.local_max_htlc_msat = -1 if db_channel.local_max_htlc_msat is None else db_channel.local_max_htlc_msat
|
|
db_channel.remote_base_fee = -1 if db_channel.remote_base_fee is None else db_channel.remote_base_fee
|
|
db_channel.remote_fee_rate = -1 if db_channel.remote_fee_rate is None else db_channel.remote_fee_rate
|
|
db_channel.remote_cltv = -1 if db_channel.remote_cltv is None else db_channel.remote_cltv
|
|
db_channel.remote_disabled = False if db_channel.remote_disabled is None else db_channel.remote_disabled
|
|
db_channel.remote_min_htlc_msat = -1 if db_channel.remote_min_htlc_msat is None else db_channel.remote_min_htlc_msat
|
|
db_channel.remote_max_htlc_msat = -1 if db_channel.remote_max_htlc_msat is None else db_channel.remote_max_htlc_msat
|
|
# Check for pending settings to be applied
|
|
if pending_channel:
|
|
if pending_channel.local_base_fee or pending_channel.local_fee_rate or pending_channel.local_cltv:
|
|
base_fee = pending_channel.local_base_fee if pending_channel.local_base_fee else db_channel.local_base_fee
|
|
fee_rate = pending_channel.local_fee_rate if pending_channel.local_fee_rate else db_channel.local_fee_rate
|
|
cltv = pending_channel.local_cltv if pending_channel.local_cltv else db_channel.local_cltv
|
|
channel_point = ln.ChannelPoint()
|
|
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
|
|
channel_point.funding_txid_str = db_channel.funding_txid
|
|
channel_point.output_index = int(db_channel.output_index)
|
|
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=base_fee, fee_rate=(fee_rate/1000000), time_lock_delta=cltv))
|
|
db_channel.local_base_fee = base_fee
|
|
db_channel.local_fee_rate = fee_rate
|
|
db_channel.local_cltv = cltv
|
|
db_channel.fees_updated = datetime.now()
|
|
if pending_channel.auto_rebalance is not None:
|
|
db_channel.auto_rebalance = pending_channel.auto_rebalance
|
|
if pending_channel.ar_amt_target:
|
|
db_channel.ar_amt_target = pending_channel.ar_amt_target
|
|
if pending_channel.ar_in_target:
|
|
db_channel.ar_in_target = pending_channel.ar_in_target
|
|
if pending_channel.ar_out_target:
|
|
db_channel.ar_out_target = pending_channel.ar_out_target
|
|
if pending_channel.ar_max_cost:
|
|
db_channel.ar_max_cost = pending_channel.ar_max_cost
|
|
if pending_channel.auto_fees is not None:
|
|
db_channel.auto_fees = pending_channel.auto_fees
|
|
pending_channel.delete()
|
|
if old_fee_rate is not None and old_fee_rate != local_policy.fee_rate_milli_msat:
|
|
print(f"{datetime.now().strftime('%c')} : Ext Fee Change Detected {db_channel.chan_id=} {db_channel.alias=} {old_fee_rate=} {db_channel.local_fee_rate=}")
|
|
#External Fee change detected, update auto fee log
|
|
db_channel.fees_updated = datetime.now()
|
|
Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Ext"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save()
|
|
db_channel.save()
|
|
counter += 1
|
|
chan_list.append(channel.chan_id)
|
|
records = Channels.objects.filter(is_open=True).count()
|
|
if records > counter:
|
|
#A channel must have been closed, mark it as closed
|
|
channels = Channels.objects.filter(is_open=True).exclude(chan_id__in=chan_list)
|
|
for channel in channels:
|
|
channel.last_update = datetime.now()
|
|
channel.is_active = False
|
|
channel.is_open = False
|
|
channel.save()
|
|
|
|
def update_peers(stub):
|
|
counter = 0
|
|
peer_list = []
|
|
peers = stub.ListPeers(ln.ListPeersRequest(latest_error=True)).peers
|
|
for peer in peers:
|
|
exists = 1 if Peers.objects.filter(pubkey=peer.pub_key).count() == 1 else 0
|
|
if exists == 1:
|
|
db_peer = Peers.objects.filter(pubkey=peer.pub_key)[0]
|
|
db_peer.pubkey = peer.pub_key
|
|
db_peer.address = peer.address
|
|
db_peer.sat_sent = peer.sat_sent
|
|
db_peer.sat_recv = peer.sat_recv
|
|
db_peer.inbound = peer.inbound
|
|
db_peer.ping_time = round(peer.ping_time/1000)
|
|
if db_peer.connected == False:
|
|
try:
|
|
db_peer.alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=peer.pub_key, include_channels=False)).node.alias
|
|
except:
|
|
db_peer.alias = ''
|
|
db_peer.connected = True
|
|
db_peer.save()
|
|
elif exists == 0:
|
|
try:
|
|
alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=peer.pub_key, include_channels=False)).node.alias
|
|
except:
|
|
alias = ''
|
|
Peers(pubkey = peer.pub_key, address = peer.address, sat_sent = peer.sat_sent, sat_recv = peer.sat_recv, inbound = peer.inbound, ping_time = round(peer.ping_time/1000), alias=alias, connected = True).save()
|
|
counter += 1
|
|
peer_list.append(peer.pub_key)
|
|
records = Peers.objects.filter(connected=True).count()
|
|
if records > counter:
|
|
disconnected = Peers.objects.filter(connected=True).exclude(pubkey__in=peer_list)
|
|
for peer in disconnected:
|
|
peer.connected = False
|
|
peer.save()
|
|
|
|
def update_onchain(stub):
|
|
Onchain.objects.filter(block_height=0).delete()
|
|
last_block = 0 if Onchain.objects.aggregate(Max('block_height'))['block_height__max'] == None else Onchain.objects.aggregate(Max('block_height'))['block_height__max'] + 1
|
|
onchain_txs = stub.GetTransactions(ln.GetTransactionsRequest(start_height=last_block)).transactions
|
|
for tx in onchain_txs:
|
|
Onchain(tx_hash=tx.tx_hash, time_stamp=datetime.fromtimestamp(tx.time_stamp), amount=tx.amount, fee=tx.total_fees, block_hash=tx.block_hash, block_height=tx.block_height, label=tx.label[:100]).save()
|
|
|
|
def network_links():
|
|
if LocalSettings.objects.filter(key='GUI-NetLinks').exists():
|
|
network_links = str(LocalSettings.objects.filter(key='GUI-NetLinks')[0].value)
|
|
else:
|
|
LocalSettings(key='GUI-NetLinks', value='https://mempool.space').save()
|
|
network_links = 'https://mempool.space'
|
|
return network_links
|
|
|
|
def get_tx_fees(txid):
|
|
base_url = network_links() + ('/testnet' if settings.LND_NETWORK == 'testnet' else '') + '/api/tx/'
|
|
try:
|
|
request_data = get(base_url + txid).json()
|
|
fee = request_data['fee']
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : Error getting closure fees {txid=} {str(e)=}")
|
|
fee = 0
|
|
return fee
|
|
|
|
def update_closures(stub):
|
|
closures = stub.ClosedChannels(ln.ClosedChannelsRequest()).channels
|
|
if len(closures) > Closures.objects.all().count():
|
|
counter = 0
|
|
skip = Closures.objects.all().count()
|
|
for closure in closures:
|
|
counter += 1
|
|
if counter > skip:
|
|
channel = Channels.objects.filter(chan_id=closure.chan_id)[0] if Channels.objects.filter(chan_id=closure.chan_id).exists() else None
|
|
resolution_count = len(closure.resolutions)
|
|
txid, index = closure.channel_point.split(':')
|
|
closing_costs = get_tx_fees(closure.closing_tx_hash) if (closure.open_initiator != 2 and closure.close_type not in [4, 5]) else 0
|
|
db_closure = Closures(chan_id=closure.chan_id, funding_txid=txid, funding_index=index, closing_tx=closure.closing_tx_hash, remote_pubkey=closure.remote_pubkey, capacity=closure.capacity, close_height=closure.close_height, settled_balance=closure.settled_balance, time_locked_balance=closure.time_locked_balance, close_type=closure.close_type, open_initiator=closure.open_initiator, close_initiator=closure.close_initiator, resolution_count=resolution_count)
|
|
try:
|
|
db_closure.save()
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : Error inserting closure: {str(e)}")
|
|
Closures.objects.filter(funding_txid=txid,funding_index=index).delete()
|
|
return
|
|
if resolution_count > 0:
|
|
Resolutions.objects.filter(chan_id=closure.chan_id).delete()
|
|
for resolution in closure.resolutions:
|
|
if resolution.resolution_type != 2 and not Resolutions.objects.filter(sweep_txid=resolution.sweep_txid).exists():
|
|
closing_costs += get_tx_fees(resolution.sweep_txid)
|
|
Resolutions(chan_id=closure.chan_id, resolution_type=resolution.resolution_type, outcome=resolution.outcome, outpoint_tx=resolution.outpoint.txid_str, outpoint_index=resolution.outpoint.output_index, amount_sat=resolution.amount_sat, sweep_txid=resolution.sweep_txid).save()
|
|
db_closure.closing_costs = closing_costs
|
|
db_closure.save()
|
|
|
|
def reconnect_peers(stub):
|
|
inactive_peers = Channels.objects.filter(is_open=True, is_active=False, private=False).values_list('remote_pubkey', flat=True).distinct()
|
|
if len(inactive_peers) > 0:
|
|
peers = Peers.objects.all()
|
|
for inactive_peer in inactive_peers:
|
|
if peers.filter(pubkey=inactive_peer).exists():
|
|
peer = peers.filter(pubkey=inactive_peer)[0]
|
|
if peer.last_reconnected == None or (int((datetime.now() - peer.last_reconnected).total_seconds() / 60) > 2):
|
|
print(f"{datetime.now().strftime('%c')} : Reconnecting peer {peer.alias} {peer.pubkey=} {peer.last_reconnected=}")
|
|
if peer.connected == True:
|
|
print(f"{datetime.now().strftime('%c')} : ... Inactive channel is still connected to peer, disconnecting peer. {peer.alias=} {inactive_peer=}")
|
|
try:
|
|
response = stub.DisconnectPeer(ln.DisconnectPeerRequest(pub_key=inactive_peer))
|
|
print(f"{datetime.now().strftime('%c')} : .... Disconnected peer {peer.alias} {inactive_peer=} {response=}")
|
|
peer.connected = False
|
|
peer.save()
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : .... Error disconnecting peer {peer.alias} {inactive_peer=} {str(e)=}")
|
|
|
|
try:
|
|
node = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=inactive_peer, include_channels=False)).node
|
|
host = node.addresses[0].addr
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : ... Unable to find node info on graph, using last known value for {peer.alias} {peer.pubkey=} {peer.address=} {str(e)=}")
|
|
host = peer.address
|
|
#address = ln.LightningAddress(pubkey=inactive_peer, host=host)
|
|
print(f"{datetime.now().strftime('%c')} : ... Attempting connection to {peer.alias} {inactive_peer=} {host=}")
|
|
try:
|
|
#try both the graph value and last know value
|
|
stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=ln.LightningAddress(pubkey=inactive_peer, host=host), perm=True, timeout=5))
|
|
if host != peer.address and peer.address[:9] != '127.0.0.1':
|
|
stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=ln.LightningAddress(pubkey=inactive_peer, host=peer.address), perm=True, timeout=5))
|
|
except Exception as e:
|
|
error = str(e)
|
|
details_index = error.find('details =') + 11
|
|
debug_error_index = error.find('debug_error_string =') - 3
|
|
error_msg = error[details_index:debug_error_index]
|
|
print(f"{datetime.now().strftime('%c')} : .... Error reconnecting {peer.alias} {inactive_peer=} {error_msg=}")
|
|
peer.last_reconnected = datetime.now()
|
|
peer.save()
|
|
|
|
def clean_payments(stub):
|
|
if LocalSettings.objects.filter(key='LND-CleanPayments').exists():
|
|
enabled = int(LocalSettings.objects.filter(key='LND-CleanPayments')[0].value)
|
|
else:
|
|
LocalSettings(key='LND-CleanPayments', value='0').save()
|
|
LocalSettings(key='LND-RetentionDays', value='30').save()
|
|
enabled = 0
|
|
if enabled == 1:
|
|
if LocalSettings.objects.filter(key='LND-RetentionDays').exists():
|
|
retention_days = int(LocalSettings.objects.filter(key='LND-RetentionDays')[0].value)
|
|
else:
|
|
LocalSettings(key='LND-RetentionDays', value='30').save()
|
|
retention_days = 30
|
|
time_filter = datetime.now() - timedelta(days=retention_days)
|
|
target_payments = Payments.objects.exclude(status=1).filter(cleaned=False).filter(creation_date__lte=time_filter).order_by('index')[:10]
|
|
for payment in target_payments:
|
|
payment_hash = bytes.fromhex(payment.payment_hash)
|
|
htlcs_only = True if payment.status == 2 else False
|
|
try:
|
|
stub.DeletePayment(ln.DeletePaymentRequest(payment_hash=payment_hash, failed_htlcs_only=htlcs_only))
|
|
except Exception as e:
|
|
error = str(e)
|
|
details_index = error.find('details =') + 11
|
|
debug_error_index = error.find('debug_error_string =') - 3
|
|
error_msg = error[details_index:debug_error_index]
|
|
print(f"{datetime.now().strftime('%c')} : Error {payment.index=} {payment.status=} {payment.payment_hash=} {error_msg=}")
|
|
finally:
|
|
payment.cleaned = True
|
|
payment.save()
|
|
|
|
def auto_fees(stub):
|
|
if LocalSettings.objects.filter(key='AF-Enabled').exists():
|
|
if int(LocalSettings.objects.filter(key='AF-Enabled')[0].value) == 0: #disabled
|
|
return
|
|
else:
|
|
LocalSettings(key='AF-Enabled', value='0').save()
|
|
return
|
|
|
|
try:
|
|
channels = Channels.objects.filter(is_open=True, is_active=True, private=False, auto_fees=True)
|
|
results_df = af.main(channels)
|
|
if not results_df.empty:
|
|
update_df = results_df[results_df['eligible'] == True]
|
|
update_df = update_df[update_df['adjustment']!=0]
|
|
if not update_df.empty:
|
|
for target_channel in update_df.to_dict(orient='records'):
|
|
print(f"{datetime.now().strftime('%c')} : Updating fees for channel {str(target_channel['chan_id'])} to a value of: {str(target_channel['new_rate'])}")
|
|
channel = Channels.objects.filter(chan_id=target_channel['chan_id'])[0]
|
|
channel_point = ln.ChannelPoint()
|
|
channel_point.funding_txid_bytes = bytes.fromhex(channel.funding_txid)
|
|
channel_point.funding_txid_str = channel.funding_txid
|
|
channel_point.output_index = channel.output_index
|
|
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=channel.local_base_fee, fee_rate=(target_channel['new_rate']/1000000), time_lock_delta=channel.local_cltv))
|
|
channel.local_fee_rate = target_channel['new_rate']
|
|
channel.fees_updated = datetime.now()
|
|
channel.save()
|
|
Autofees(chan_id=channel.chan_id, peer_alias=channel.alias, setting=(f"AF [ {target_channel['net_routed_7day']}:{target_channel['in_percent']}:{target_channel['out_percent']} ]"), old_value=target_channel['local_fee_rate'], new_value=target_channel['new_rate']).save()
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : Error processing auto_fees: {str(e)}")
|
|
|
|
|
|
def agg_htlcs(target_htlcs, category):
|
|
try:
|
|
target_ids = target_htlcs.values_list('id')
|
|
agg_htlcs = FailedHTLCs.objects.filter(id__in=target_ids).annotate(day=TruncDay('timestamp')).values('day', 'chan_id_in', 'chan_id_out').annotate(amount=Sum('amount'), fee=Sum('missed_fee'), liq=Avg('chan_out_liq'), pending=Avg('chan_out_pending'), count=Count('id'), chan_in_alias=Max('chan_in_alias'), chan_out_alias=Max('chan_out_alias'))
|
|
for htlc in agg_htlcs:
|
|
if HistFailedHTLC.objects.filter(date=htlc['day'],chan_id_in=htlc['chan_id_in'],chan_id_out=htlc['chan_id_out']).exists():
|
|
htlc_itm = HistFailedHTLC.objects.filter(date=htlc['day'],chan_id_in=htlc['chan_id_in'],chan_id_out=htlc['chan_id_out']).get()
|
|
else:
|
|
htlc_itm = HistFailedHTLC(htlc_count=0, amount_sum=0, fee_sum=0, liq_avg=0, pending_avg=0, balance_count=0, downstream_count=0, other_count=0)
|
|
htlc_itm.date = htlc['day']
|
|
htlc_itm.chan_id_in = htlc['chan_id_in']
|
|
htlc_itm.chan_id_out = htlc['chan_id_out']
|
|
htlc_itm.chan_in_alias = htlc['chan_in_alias']
|
|
htlc_itm.chan_out_alias = htlc['chan_out_alias']
|
|
htlc_itm.htlc_count += htlc['count']
|
|
htlc_itm.amount_sum += htlc['amount']
|
|
htlc_itm.fee_sum += htlc['fee']
|
|
htlc_itm.liq_avg += (htlc['count']/htlc_itm.htlc_count)*((0 if htlc['liq'] is None else htlc['liq'])-htlc_itm.liq_avg)
|
|
htlc_itm.pending_avg += (htlc['count']/htlc_itm.htlc_count)*((0 if htlc['pending'] is None else htlc['pending'])-htlc_itm.pending_avg)
|
|
if category == 'balance':
|
|
htlc_itm.balance_count += htlc['count']
|
|
elif category == 'downstream':
|
|
htlc_itm.downstream_count += htlc['count']
|
|
elif category == 'other':
|
|
htlc_itm.other_count += htlc['count']
|
|
htlc_itm.save()
|
|
FailedHTLCs.objects.filter(id__in=target_ids, chan_id_in=htlc['chan_id_in'], chan_id_out=htlc['chan_id_out']).annotate(day=TruncDay('timestamp')).filter(day=htlc['day']).delete()
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : Error processing agg_htlcs: {str(e)}")
|
|
|
|
def agg_failed_htlcs():
|
|
time_filter = datetime.now() - timedelta(days=30)
|
|
agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter, failure_detail=6)[:100], 'balance')
|
|
agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter, failure_detail=99)[:100], 'downstream')
|
|
agg_htlcs(FailedHTLCs.objects.filter(timestamp__lte=time_filter).exclude(failure_detail__in=[6, 99])[:100], 'other')
|
|
|
|
def main():
|
|
try:
|
|
stub = lnrpc.LightningStub(lnd_connect())
|
|
#Update data
|
|
update_peers(stub)
|
|
update_channels(stub)
|
|
update_invoices(stub)
|
|
update_payments(stub)
|
|
update_forwards(stub)
|
|
update_onchain(stub)
|
|
update_closures(stub)
|
|
reconnect_peers(stub)
|
|
clean_payments(stub)
|
|
auto_fees(stub)
|
|
agg_failed_htlcs()
|
|
except Exception as e:
|
|
print(f"{datetime.now().strftime('%c')} : Error processing background data: {str(e)}")
|
|
|
|
if __name__ == '__main__':
|
|
main() |