diff --git a/delete_payments.py b/delete_payments.py
index 22c039d6..c69666b7 100644
--- a/delete_payments.py
+++ b/delete_payments.py
@@ -1,10 +1,9 @@
-from lndg import settings
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps.lnd_connect import lnd_connect
def main():
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
try:
stub.DeleteAllPayments(ln.DeleteAllPaymentsRequest(failed_payments_only=False, failed_htlcs_only=True))
stub.DeleteAllPayments(ln.DeleteAllPaymentsRequest(failed_payments_only=True, failed_htlcs_only=False))
diff --git a/gui/forms.py b/gui/forms.py
index 4b188420..2460e0c6 100644
--- a/gui/forms.py
+++ b/gui/forms.py
@@ -99,7 +99,8 @@ class AutoRebalanceForm(forms.Form):
(7, 'channel_state'),
(8, 'auto_fees'),
(9, 'cltv'),
- (10, 'closing_costs'),
+ (10, 'min_htlc'),
+ (11, 'max_htlc'),
]
class UpdateChannel(forms.Form):
@@ -107,6 +108,21 @@ class UpdateChannel(forms.Form):
target = forms.IntegerField(label='target')
update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
+class UpdateClosing(forms.Form):
+ funding_txid = forms.CharField(label='funding_txid', max_length=64)
+ funding_index = forms.IntegerField(label='funding_index')
+ target = forms.IntegerField(label='target')
+
+class UpdateKeysend(forms.Form):
+ r_hash = forms.CharField(label='r_hash', max_length=64)
+
+class AddAvoid(forms.Form):
+ pubkey = forms.CharField(label='avoid_pubkey', max_length=66)
+ notes = forms.CharField(label='avoid_notes', max_length=1000, required=False)
+
+class RemoveAvoid(forms.Form):
+ pubkey = forms.CharField(label='avoid_pubkey', max_length=66)
+
class UpdatePending(forms.Form):
funding_txid = forms.CharField(label='funding_txid', max_length=64)
output_index = forms.IntegerField(label='output_index')
diff --git a/gui/lnd_deps/lnd_connect.py b/gui/lnd_deps/lnd_connect.py
index 45b6c064..6593a89d 100644
--- a/gui/lnd_deps/lnd_connect.py
+++ b/gui/lnd_deps/lnd_connect.py
@@ -1,18 +1,19 @@
import os, codecs, grpc
+from lndg import settings
-def lnd_connect(LND_DIR_PATH, LND_NETWORK, LND_RPC_SERVER):
+def lnd_connect():
#Open connection with lnd via grpc
- with open(os.path.expanduser(LND_DIR_PATH + '/data/chain/bitcoin/' + LND_NETWORK + '/admin.macaroon'), 'rb') as f:
+ with open(os.path.expanduser(settings.LND_MACAROON_PATH), 'rb') as f:
macaroon_bytes = f.read()
macaroon = codecs.encode(macaroon_bytes, 'hex')
def metadata_callback(context, callback):
callback([('macaroon', macaroon)], None)
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
- cert = open(os.path.expanduser(LND_DIR_PATH + '/tls.cert'), 'rb').read()
+ cert = open(os.path.expanduser(settings.LND_TLS_PATH), 'rb').read()
cert_creds = grpc.ssl_channel_credentials(cert)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
creds = grpc.composite_channel_credentials(cert_creds, auth_creds)
- channel = grpc.secure_channel(LND_RPC_SERVER, creds, options=[('grpc.max_send_message_length', 29999999), ('grpc.max_receive_message_length', 29999999),])
+ channel = grpc.secure_channel(settings.LND_RPC_SERVER, creds, options=[('grpc.max_send_message_length', 29999999), ('grpc.max_receive_message_length', 29999999),])
return channel
def main():
diff --git a/gui/migrations/0021_auto_20220221_1309.py b/gui/migrations/0021_auto_20220221_1309.py
index 7395eeff..4d385392 100644
--- a/gui/migrations/0021_auto_20220221_1309.py
+++ b/gui/migrations/0021_auto_20220221_1309.py
@@ -7,15 +7,14 @@
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
def update_messages(apps, schedma_editor):
invoices = apps.get_model('gui', 'invoices')
try:
messages = invoices.objects.exclude(message=None)
if len(messages) > 0:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
+ signerstub = lnsigner.SignerStub(lnd_connect())
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
for message in messages:
records = stub.LookupInvoice(ln.PaymentHash(r_hash=bytes.fromhex(message.r_hash))).htlcs[0].custom_records
@@ -46,7 +45,7 @@ def update_rebal_channel(apps, schedma_editor):
payments = apps.get_model('gui', 'payments')
hops = apps.get_model('gui', 'paymenthops')
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
for payment in payments.objects.filter(status=2).iterator():
last_hop = hops.objects.filter(payment_hash=payment.payment_hash).order_by('-step')[0] if hops.objects.filter(payment_hash=payment.payment_hash).exists() else None
diff --git a/gui/migrations/0032_auto_20220913_1035.py b/gui/migrations/0032_auto_20220913_1035.py
new file mode 100644
index 00000000..1f8ed3e4
--- /dev/null
+++ b/gui/migrations/0032_auto_20220913_1035.py
@@ -0,0 +1,35 @@
+# Generated by Django 3.2.7 on 2022-09-13 10:35
+
+from django.db import migrations, models
+
+def migrate_close_fees(apps, schedma_editor):
+ channels = apps.get_model('gui', 'channels')
+ closures = apps.get_model('gui', 'closures')
+ close_fees = channels.objects.filter(closing_costs__gt=0)
+ for close_fee in close_fees:
+ closure = closures.objects.filter(funding_txid=close_fee.funding_txid, funding_index=close_fee.output_index)[0] if closures.objects.filter(funding_txid=close_fee.funding_txid, funding_index=close_fee.output_index).exists() else None
+ if closure:
+ closure.closing_costs = close_fee.closing_costs
+ closure.save()
+
+def revert_close_fees(apps, schedma_editor):
+ pass
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0031_pendingchannels'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='closures',
+ name='closing_costs',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.RunPython(migrate_close_fees, revert_close_fees),
+ migrations.RemoveField(
+ model_name='channels',
+ name='closing_costs',
+ ),
+ ]
diff --git a/gui/migrations/0033_auto_20220926_1658.py b/gui/migrations/0033_auto_20220926_1658.py
new file mode 100644
index 00000000..23c74271
--- /dev/null
+++ b/gui/migrations/0033_auto_20220926_1658.py
@@ -0,0 +1,51 @@
+# Generated by Django 3.2.7 on 2022-09-27 01:38
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0032_auto_20220913_1035'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AvoidNodes',
+ fields=[
+ ('pubkey', models.CharField(max_length=66, primary_key=True, serialize=False)),
+ ('notes', models.CharField(max_length=1000, null=True)),
+ ('updated', models.DateTimeField(default=django.utils.timezone.now)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='local_max_htlc_msat',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='local_min_htlc_msat',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='remote_max_htlc_msat',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='remote_min_htlc_msat',
+ field=models.BigIntegerField(default=0),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='invoices',
+ name='is_revenue',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/gui/models.py b/gui/models.py
index 1011dd7d..7d21019e 100644
--- a/gui/models.py
+++ b/gui/models.py
@@ -47,6 +47,7 @@ class Invoices(models.Model):
sender = models.CharField(null=True, max_length=66)
sender_alias = models.CharField(null=True, max_length=32)
index = models.IntegerField()
+ is_revenue = models.BooleanField(default=False)
class Meta:
app_label = 'gui'
@@ -86,10 +87,14 @@ class Channels(models.Model):
local_fee_rate = models.IntegerField()
local_disabled = models.BooleanField()
local_cltv = models.IntegerField()
+ local_min_htlc_msat = models.BigIntegerField()
+ local_max_htlc_msat = models.BigIntegerField()
remote_base_fee = models.IntegerField()
remote_fee_rate = models.IntegerField()
remote_disabled = models.BooleanField()
remote_cltv = models.IntegerField()
+ remote_min_htlc_msat = models.BigIntegerField()
+ remote_max_htlc_msat = models.BigIntegerField()
is_active = models.BooleanField()
is_open = models.BooleanField()
last_update = models.DateTimeField()
@@ -100,7 +105,6 @@ class Channels(models.Model):
ar_max_cost = models.IntegerField()
fees_updated = models.DateTimeField(default=timezone.now)
auto_fees = models.BooleanField()
- closing_costs = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.auto_fees is None:
@@ -203,6 +207,7 @@ class Closures(models.Model):
open_initiator = models.IntegerField()
close_initiator = models.IntegerField()
resolution_count = models.IntegerField()
+ closing_costs = models.IntegerField(default=0)
class Meta:
app_label = 'gui'
unique_together = (('funding_txid', 'funding_index'),)
@@ -279,4 +284,11 @@ class PendingChannels(models.Model):
auto_fees = models.BooleanField(null=True, default=None)
class Meta:
app_label = 'gui'
- unique_together = (('funding_txid', 'output_index'),)
\ No newline at end of file
+ unique_together = (('funding_txid', 'output_index'),)
+
+class AvoidNodes(models.Model):
+ pubkey = models.CharField(max_length=66, primary_key=True)
+ notes = models.CharField(null=True, max_length=1000)
+ updated = models.DateTimeField(default=timezone.now)
+ class Meta:
+ app_label = 'gui'
\ No newline at end of file
diff --git a/gui/static/favicon.ico b/gui/static/favicon.ico
new file mode 100644
index 00000000..ec8ee03d
Binary files /dev/null and b/gui/static/favicon.ico differ
diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html
index 7b9eabe7..dd32b11e 100644
--- a/gui/templates/advanced.html
+++ b/gui/templates/advanced.html
@@ -30,7 +30,14 @@
Advanced Channel Settings
- |
+
+
+ |
+ |
|
+
+
+ |
+
+
+ |
{{ channel.remote_fee_rate|intcomma }} |
{{ channel.remote_base_fee|intcomma }} |
@@ -204,7 +229,7 @@ Update Local Settings
{% csrf_token %}
{% if settings.key == 'AR-Target%' %}
- {% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod' or settings.key == 'AR-APDays' %}
+ {% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod' or settings.key == 'AR-APDays' or settings.key == 'AF-UpdateHours' %}
{% elif settings.key == 'AR-Time' %}
diff --git a/gui/templates/autofees.html b/gui/templates/autofees.html
index b40d47bc..11baef63 100644
--- a/gui/templates/autofees.html
+++ b/gui/templates/autofees.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if autofees %}
- Autofees Logs
+
Timestamp |
@@ -18,7 +18,7 @@ Autofees Logs
{{ log.timestamp|naturaltime }} |
{{ log.chan_id }} |
- {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
+ {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
{{ log.setting }} |
{{ log.old_value }} |
log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.new_value }} |
@@ -29,8 +29,8 @@ Autofees Logs
{% endif %}
{% if not autofees %}
- No autofees logs to see here yet!
+ No our fees logs to see here yet!
Experimental. This will allow LNDg to automatically act upon the suggestions found here.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/autopilot.html b/gui/templates/autopilot.html
index e47e8375..f65817ba 100644
--- a/gui/templates/autopilot.html
+++ b/gui/templates/autopilot.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if autopilot %}
- Autopilot Logs
+
Timestamp |
@@ -18,7 +18,7 @@ Autopilot Logs
{{ log.timestamp|naturaltime }} |
{{ log.chan_id }} |
- {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
+ {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
{{ log.setting }} |
{{ log.old_value }} |
{{ log.new_value }} |
@@ -33,4 +33,4 @@ Autopilot Logs
Experimental. This will allow LNDg to automatically act upon the suggestions found here.
{% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/gui/templates/base.html b/gui/templates/base.html
index bf24086b..fdb717de 100644
--- a/gui/templates/base.html
+++ b/gui/templates/base.html
@@ -4,6 +4,7 @@
+ {% block meta %}{% endblock %}
{% block title %}LNDg{% endblock %}
{% load static %}
{% load qr_code %}
@@ -28,7 +29,7 @@
diff --git a/gui/templates/channel.html b/gui/templates/channel.html
index 62601208..b8e672e3 100644
--- a/gui/templates/channel.html
+++ b/gui/templates/channel.html
@@ -6,6 +6,7 @@
Details For Channel: {{ chan_id }} | Peer: {% if channel.alias != "" %}{{ channel.alias }} ({{ channel.remote_pubkey }}){% else %}{{ channel.remote_pubkey }}{% endif %}
Capacity: {{ channel.capacity|intcomma }} | Is Open: {{ channel.is_open }} | Is Active: {{ channel.is_active }} | Is Private: {{ channel.private }} | Channel Updates: {{ channel.num_updates|intcomma }}
+ {% if peer_info %} Peer Connected: {{ peer_info.connected }} | Peer Address: {{ peer_info.address }} | {% if not channel.is_open %}Marked Closed:{% elif channel.is_active %}Uptime:{% else %}Downtime:{% endif %} {{ channel.last_update|naturaltime|slice:":-4" }} (since {{ channel.last_update }}){% endif %}
Outbound Liquidity: {{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%) | Inbound Liquidity: {{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%)
@@ -166,7 +167,7 @@ Outgoing HTLCs
{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
{{ htlc.amount|intcomma }} |
{{ htlc.expiration_height|intcomma }} |
- {{ htlc.hash_lock }} |
+ {{ htlc.hash_lock }} |
{% endfor %}
@@ -195,6 +196,33 @@ Incoming HTLCs
{% endif %}
+{% if autofees %}
+
+
+
+
+ Timestamp |
+ Channel ID |
+ Peer Alias |
+ Setting |
+ Old Value |
+ New Value |
+ Change |
+
+ {% for log in autofees %}
+
+ {{ log.timestamp|naturaltime }} |
+ {{ log.chan_id }} |
+ {% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
+ {{ log.setting }} |
+ {{ log.old_value }} |
+ log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.new_value }} |
+ log.old_value %}style="background-color: #d5fadb"{% else %}style="background-color: #fadbd5"{% endif %}>{{ log.change }}% |
+
+ {% endfor %}
+
+
+{% endif %}
{% if forwards %}
@@ -242,6 +270,7 @@
Fees Paid |
Last Hop Alias |
Status |
+ Hash |
{% for rebalance in rebalances %}
@@ -254,8 +283,9 @@
{{ rebalance.fee_limit|intcomma }} |
{{ rebalance.ppm|intcomma }} |
{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} |
- {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.target_alias == '' %}---{% else %}{{ rebalance.target_alias }}{% endif %} |
{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+ {% if rebalance.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %} |
{% endfor %}
@@ -280,7 +310,7 @@
{% for payment in payments %}
{{ payment.creation_date|naturaltime }} |
- {{ payment.payment_hash }} |
+ {{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
{{ payment.ppm|intcomma }} |
@@ -313,7 +343,7 @@
{{ invoice.creation_date|naturaltime }} |
{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %} |
- {{ invoice.r_hash }} |
+ {{ invoice.r_hash }} |
{{ invoice.value|add:"0"|intcomma }} |
{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %} |
{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
@@ -346,13 +376,13 @@
{{ failed_htlc.timestamp|naturaltime }} |
{{ failed_htlc.chan_id_in }} |
{{ failed_htlc.chan_id_out }} |
- {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
- {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
+ {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
+ {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
{{ failed_htlc.amount|intcomma }} |
{{ failed_htlc.chan_out_liq|intcomma }} ({{ failed_htlc.chan_out_pending|intcomma }}) |
{{ failed_htlc.missed_fee|intcomma }} |
{% if failed_htlc.wire_failure == 15 %}Temporary Channel Failure{% elif failed_htlc.wire_failure == 18 %}Unknown Next Peer{% elif failed_htlc.wire_failure == 12 %}Fee Insufficient{% else %}{{ failed_htlc.wire_failure }}{% endif %} |
- {% if failed_htlc.failure_detail == 1 %}---{% elif failed_htlc.failure_detail == 5 %}HTLC Exceeds Max{% elif failed_htlc.failure_detail == 6 %}Insufficient Balance{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %} |
+ {% if failed_htlc.failure_detail == 1 %}---{% elif failed_htlc.failure_detail == 5 %}HTLC Exceeds Max{% elif failed_htlc.failure_detail == 6 %}Insufficient Balance{% elif failed_htlc.failure_detail == 13 %}Invoice Not Open{% elif failed_htlc.failure_detail == 20 %}Invalid Keysend{% elif failed_htlc.failure_detail == 22 %}Circular Route{% else %}{{ failed_htlc.failure_detail }}{% endif %} |
{% endfor %}
diff --git a/gui/templates/channels.html b/gui/templates/channels.html
index f1242bff..6a8bcb85 100644
--- a/gui/templates/channels.html
+++ b/gui/templates/channels.html
@@ -34,7 +34,7 @@ Channel Performance
{{ channel.chan_id }} |
{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
- {{ channel.mil_capacity }} M |
+ {{ channel.mil_capacity }} M |
{{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }}) | {{ channel.amt_rebal_in_7day|intcomma }} M ({{ channel.rebal_in_7day }}) |
{{ channel.apy_7day }}% | {{ channel.cv_7day }}% |
{{ channel.revenue_7day|intcomma }} [{{ channel.profits_7day|intcomma }}] | {{ channel.revenue_assist_7day|intcomma }} |
diff --git a/gui/templates/closures.html b/gui/templates/closures.html
index dce4c67c..e6f90656 100644
--- a/gui/templates/closures.html
+++ b/gui/templates/closures.html
@@ -117,7 +117,7 @@ Closures
Opener |
Closer |
Resolutions |
- Costs |
+ Costs 🗘 |
{% for closure in closures %}
@@ -133,16 +133,16 @@ Closures
{% if closure.close_initiator == 0 %}Unknown{% elif closure.close_initiator == 1 %}Local{% elif closure.close_initiator == 2 %}Remote{% elif closure.close_initiator == 3 %}Both{% else %}{{ closure.close_initiator }}{% endif %} |
{% if closure.resolution_count > 0 %}Details{% else %}---{% endif %} |
- {% if closure.closing_costs == '' %}
+ {% if closure.close_type == 4 or closure.close_type == 5 %}
---
- {% elif closure.open_initiator == 2 and closure.close_type == 0 %}
+ {% elif closure.open_initiator == 2 and closure.resolution_count == 0 %}
---
{% else %}
-
{% endif %}
|
diff --git a/gui/templates/failed_htlcs.html b/gui/templates/failed_htlcs.html
index 8288ab3f..cb55420a 100644
--- a/gui/templates/failed_htlcs.html
+++ b/gui/templates/failed_htlcs.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if failed_htlcs %}
- Last 150 Failed HTLCs
+
@@ -491,7 +499,7 @@
{% for payment in payments %}
{{ payment.creation_date|naturaltime }} |
- {{ payment.payment_hash }} |
+ {{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
{{ payment.ppm|intcomma }} |
@@ -524,7 +532,7 @@
{{ invoice.creation_date|naturaltime }} |
{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %} |
- {{ invoice.r_hash }} |
+ {{ invoice.r_hash }} |
{{ invoice.value|add:"0"|intcomma }} |
{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %} |
{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
@@ -557,8 +565,8 @@
{{ failed_htlc.timestamp|naturaltime }} |
{{ failed_htlc.chan_id_in }} |
{{ failed_htlc.chan_id_out }} |
- {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
- {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
+ {% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %} |
+ {% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %} |
{{ failed_htlc.amount|intcomma }} |
{{ failed_htlc.chan_out_liq|intcomma }} ({{ failed_htlc.chan_out_pending|intcomma }}) |
{{ failed_htlc.missed_fee|intcomma }} |
diff --git a/gui/templates/invoices.html b/gui/templates/invoices.html
index fa799aa2..08109181 100644
--- a/gui/templates/invoices.html
+++ b/gui/templates/invoices.html
@@ -21,7 +21,7 @@ Last 150 Invoices
{{ invoice.creation_date|naturaltime }} |
{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %} |
- {{ invoice.r_hash }} |
+ {{ invoice.r_hash }} |
{{ invoice.value|add:"0"|intcomma }} |
{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %} |
{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
diff --git a/gui/templates/keysends.html b/gui/templates/keysends.html
index 9f229362..ffd7ef10 100644
--- a/gui/templates/keysends.html
+++ b/gui/templates/keysends.html
@@ -7,6 +7,7 @@
Received Keysends
+ Revenue? |
Settle Date |
Channel In Alias |
Amount |
@@ -14,9 +15,16 @@ Received Keysends
{% for keysend in keysends %}
- {{ keysend.settle_date|naturaltime }} |
- {% if keysend.chan_in_alias == '' %}---{% else %}{{ keysend.chan_in_alias }}{% endif %} |
- {{ keysend.amt_paid|intcomma }} |
+
+
+ |
+ {{ keysend.settle_date|naturaltime }} |
+ {% if keysend.chan_in_alias == '' %}---{% else %}{{ keysend.chan_in_alias }}{% endif %} |
+ {{ keysend.amt_paid|intcomma }} |
{{ keysend.message }}{% if keysend.sender != None %} | Signed By: {% if keysend.sender_alias != None %}{{ keysend.sender_alias }}{% else %}{{ keysend.sender }}{% endif %}{% endif %} |
{% endfor %}
diff --git a/gui/templates/open_list.html b/gui/templates/open_list.html
index e023d32b..7960415c 100644
--- a/gui/templates/open_list.html
+++ b/gui/templates/open_list.html
@@ -32,10 +32,53 @@ Suggested Open List
-{% endif %}
-{% if not open_list %}
+{% else %}
No potential peers can be calculated yet, try waiting until you have some payment data.
{% endif %}
+{% if avoid_list %}
+
+ Avoid/Exclude List
+
+
+
+ Updated |
+ Node Pubkey |
+ Notes |
+ Remove |
+
+ {% for node in avoid_list %}
+
+ {{ node.updated|naturaltime }} |
+ {{ node.pubkey }} |
+ {% if node.notes == '' %}---{% else %}{{ node.notes }}{% endif %} |
+
+
+ |
+
+ {% endfor %}
+
+
+
+{% else %}
+
+ No node added to the exclusion list yet. Add nodes here you want to avoid connecting to in the future.
+
+{% endif %}
+
+ Add Node To Exclusion List Or Update Existing Notes
+
+
{% endblock %}
diff --git a/gui/templates/payments.html b/gui/templates/payments.html
index bf7ad3de..42f0f244 100644
--- a/gui/templates/payments.html
+++ b/gui/templates/payments.html
@@ -21,7 +21,7 @@ Last 150 Payments
{% for payment in payments %}
{{ payment.creation_date|naturaltime }} |
- {{ payment.payment_hash }} |
+ {{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
{{ payment.ppm|intcomma }} |
diff --git a/gui/templates/pending_htlcs.html b/gui/templates/pending_htlcs.html
index babce0e5..b32b572a 100644
--- a/gui/templates/pending_htlcs.html
+++ b/gui/templates/pending_htlcs.html
@@ -23,7 +23,7 @@ Outgoing HTLCs
{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
{{ htlc.amount|intcomma }} |
{{ htlc.hours_til_expiration }} hours |
- {{ htlc.hash_lock }} |
+ {{ htlc.hash_lock }} |
{% endfor %}
@@ -50,7 +50,7 @@ Incoming HTLCs
{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
{{ htlc.amount|intcomma }} |
{{ htlc.hours_til_expiration }} hours |
- {{ htlc.hash_lock }} |
+ {{ htlc.hash_lock }} |
{% endfor %}
diff --git a/gui/templates/rebalances.html b/gui/templates/rebalances.html
index 2e23f7e2..b2f3dda2 100644
--- a/gui/templates/rebalances.html
+++ b/gui/templates/rebalances.html
@@ -2,6 +2,43 @@
{% block title %} {{ block.super }} - Rebalances{% endblock %}
{% block content %}
{% load humanize %}
+{% if rebalances_success %}
+
+ Last Successful Rebalances
+
+
+ Requested |
+ Start |
+ Stop |
+ Scheduled Duration |
+ Actual Duration |
+ Value |
+ Fee Limit |
+ Target PPM |
+ Fees Paid |
+ Last Hop Alias |
+ Status |
+ Hash |
+
+ {% for rebalance in rebalances_success %}
+
+ {{ rebalance.requested|naturaltime }} |
+ ---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} |
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %} |
+ {{ rebalance.duration }} minutes |
+ {% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %} |
+ {{ rebalance.value|intcomma }} |
+ {{ rebalance.fee_limit|intcomma }} |
+ {{ rebalance.ppm|intcomma }} |
+ {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} |
+ {% if rebalance.target_alias == '' %}---{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+ {% if rebalance.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
{% if rebalances %}
Last 150 Rebalances
@@ -18,6 +55,7 @@ Last 150 Rebalances
Fees Paid |
Last Hop Alias |
Status |
+ Hash |
{% for rebalance in rebalances %}
@@ -30,8 +68,9 @@ Last 150 Rebalances
{{ rebalance.fee_limit|intcomma }} |
{{ rebalance.ppm|intcomma }} |
{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} |
- {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.target_alias == '' %}---{% else %}{{ rebalance.target_alias }}{% endif %} |
{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+ {% if rebalance.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %} |
{% endfor %}
diff --git a/gui/templates/rebalancing.html b/gui/templates/rebalancing.html
index 284d605b..bb626f26 100644
--- a/gui/templates/rebalancing.html
+++ b/gui/templates/rebalancing.html
@@ -4,7 +4,7 @@
{% load humanize %}
{% if channels %}
- Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)
+ Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)
@@ -14,14 +14,14 @@ Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled
Outbound Liquidity |
|
Inbound Liquidity |
- Rebal Out? |
- Enabled? |
+ Rebal Out? |
+ Enabled? |
Fee Ratio |
Rebal In? |
Target Amt |
Max Cost % |
oTarget% |
- iTarget% |
+ iTarget% |
AR |
7-Day Rate |
Active |
@@ -108,6 +108,7 @@
Fees Paid |
Last Hop Alias |
Status |
+ Hash |
{% for rebalance in rebalancer %}
@@ -120,8 +121,9 @@
{{ rebalance.fee_limit|intcomma }} |
{{ rebalance.ppm|intcomma }} |
{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} |
- {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.target_alias == '' %}---{% else %}{{ rebalance.target_alias }}{% endif %} |
{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+ {% if rebalance.payment_hash == '' %}---{% else %}{{ rebalance.payment_hash }}{% endif %} |
{% endfor %}
diff --git a/gui/templates/route.html b/gui/templates/route.html
index ddefd138..5094f1f5 100644
--- a/gui/templates/route.html
+++ b/gui/templates/route.html
@@ -4,9 +4,10 @@
{% load humanize %}
{% if route %}
- Route For : {{ payment_hash }}
+ Route For : {{ payment_hash }}{% if total_cost %} | Total Costs: {{ total_cost }} [{{ total_ppm }}]{% endif %}
+ Attempt Id |
Step |
Amount |
Fee |
@@ -19,6 +20,7 @@ Route For : {{ payment_hash }}
{% for hop in route %}
+ {{ hop.attempt_id }} |
{{ hop.step }} |
{{ hop.amt|intcomma }} |
{{ hop.fee|intcomma }} |
@@ -38,4 +40,124 @@ Route For : {{ payment_hash }}
A route was not found for this payment hash!
{% endif %}
+{% if rebalances %}
+
+ Associated Rebalances
+
+
+ Requested |
+ Start |
+ Stop |
+ Scheduled Duration |
+ Actual Duration |
+ Value |
+ Fee Limit |
+ Target PPM |
+ Fees Paid |
+ Last Hop Alias |
+ Status |
+
+ {% for rebalance in rebalances %}
+
+ {{ rebalance.requested|naturaltime }} |
+ ---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %} |
+ 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %} |
+ {{ rebalance.duration }} minutes |
+ {% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %} |
+ {{ rebalance.value|intcomma }} |
+ {{ rebalance.fee_limit|intcomma }} |
+ {{ rebalance.ppm|intcomma }} |
+ {% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %} |
+ {% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %} |
+ {% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}Successful{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if invoices %}
+
+ Linked Invoice
+
+
+ Created |
+ Settled |
+ Payment Hash |
+ Value |
+ Amount Paid |
+ State |
+ Channel In Alias |
+ Channel In |
+ Keysend |
+
+ {% for invoice in invoices %}
+
+ {{ invoice.creation_date|naturaltime }} |
+ {% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %} |
+ {{ invoice.r_hash }} |
+ {{ invoice.value|add:"0"|intcomma }} |
+ {% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %} |
+ {% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %} |
+ {% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %} |
+ {% if invoice.state == 1 and invoice.chan_in != None %}{{ invoice.chan_in }}{% else %}---{% endif %} |
+ {% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if outgoing_htlcs %}
+
+ Outgoing HTLCs
+
+
+ Channel ID |
+ Channel Alias |
+ Forwarding Channel |
+ Forwarding Alias |
+ Amount |
+ Expiration |
+ Hash Lock |
+
+ {% for htlc in outgoing_htlcs %}
+
+ {{ htlc.chan_id }} |
+ {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %} |
+ {% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }}{% endif %} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
+ {{ htlc.amount|intcomma }} |
+ {{ htlc.hours_til_expiration }} hours |
+ {{ htlc.hash_lock }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if incoming_htlcs %}
+
+ Incoming HTLCs
+
+
+ Channel ID |
+ Channel Alias |
+ Forwarding Channel |
+ Forwarding Alias |
+ Amount |
+ Expiration |
+ Hash Lock |
+
+ {% for htlc in incoming_htlcs %}
+
+ {{ htlc.chan_id }} |
+ {% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %} |
+ {% if htlc.forwarding_channel == 0 %}---{% else %}{{ htlc.forwarding_channel }}{% endif %} |
+ {% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %} |
+ {{ htlc.amount|intcomma }} |
+ {{ htlc.hours_til_expiration }} hours |
+ {{ htlc.hash_lock }} |
+
+ {% endfor %}
+
+
+{% endif %}
{% endblock %}
diff --git a/gui/urls.py b/gui/urls.py
index 8666d5d6..c163864e 100644
--- a/gui/urls.py
+++ b/gui/urls.py
@@ -52,6 +52,11 @@
path('update_channel/', views.update_channel, name='update-channel'),
path('update_pending/', views.update_pending, name='update-pending'),
path('update_setting/', views.update_setting, name='update-setting'),
+ path('update_closing/', views.update_closing, name='update-closing'),
+ path('update_keysend/', views.update_keysend, name='update-keysend'),
+ path('add_avoid/', views.add_avoid, name='add-avoid'),
+ path('remove_avoid/', views.remove_avoid, name='remove-avoid'),
+ path('get_fees/', views.get_fees, name='get-fees'),
path('opens/', views.opens, name='opens'),
path('actions/', views.actions, name='actions'),
path('fees/', views.fees, name='fees'),
@@ -70,6 +75,7 @@
path('api/updatealias/', views.update_alias, name='update-alias'),
path('api/getinfo/', views.get_info, name='get-info'),
path('api/balances/', views.api_balances, name='api-balances'),
+ path('api/income/', views.api_income, name='api-income'),
path('api/pendingchannels/', views.pending_channels, name='pending-channels'),
path('lndg-admin/', admin.site.urls),
]
diff --git a/gui/views.py b/gui/views.py
index 68540294..1daa2219 100644
--- a/gui/views.py
+++ b/gui/views.py
@@ -3,13 +3,13 @@
from django.db.models import Sum, IntegerField, Count, F, Q
from django.db.models.functions import Round
from django.contrib.auth.decorators import login_required
-from django.conf import settings
from datetime import datetime, timedelta
from rest_framework import viewsets
from rest_framework.response import Response
-from rest_framework.decorators import api_view
-from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending
-from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels
+from rest_framework.decorators import api_view, permission_classes
+from rest_framework.permissions import IsAuthenticated
+from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending, UpdateClosing, UpdateKeysend, AddAvoid, RemoveAvoid
+from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels, AvoidNodes
from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
@@ -17,10 +17,11 @@
from gui.lnd_deps import router_pb2_grpc as lnrouter
from gui.lnd_deps import wtclient_pb2 as wtrpc
from gui.lnd_deps import wtclient_pb2_grpc as wtstub
-from .lnd_deps.lnd_connect import lnd_connect
-from lndg.settings import LND_NETWORK, LND_DIR_PATH
+from gui.lnd_deps.lnd_connect import lnd_connect
+from lndg import settings
from os import path
from pandas import DataFrame, merge
+from requests import get
def graph_links():
if LocalSettings.objects.filter(key='GUI-GraphLinks').exists():
@@ -38,11 +39,28 @@ def network_links():
network_links = 'https://mempool.space'
return network_links
-@login_required(login_url='/lndg-admin/login/?next=/')
+def get_tx_fees(txid):
+ base_url = network_links() + ('/testnet' if settings.LND_NETWORK == 'testnet' else '') + '/api/tx/'
+ request_data = get(base_url + txid).json()
+ fee = request_data['fee']
+ return fee
+
+class is_login_required(object):
+ def __init__(self, dec, condition):
+ self.decorator = dec
+ self.condition = condition
+
+ def __call__(self, func):
+ if not self.condition:
+ # No login required
+ return func
+ return self.decorator(func)
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def home(request):
if request.method == 'GET':
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
#Get balance and general node information
node_info = stub.GetInfo(ln.GetInfoRequest())
balances = stub.WalletBalance(ln.WalletBalanceRequest())
@@ -156,6 +174,8 @@ def home(request):
pending_outbound = channels.filter(is_open=True).aggregate(Sum('pending_outbound'))['pending_outbound__sum'] if channels.filter(is_open=True).exists() else 0
pending_inbound = channels.filter(is_open=True).aggregate(Sum('pending_inbound'))['pending_inbound__sum'] if channels.filter(is_open=True).exists() else 0
num_updates = channels.filter(is_open=True).aggregate(Sum('num_updates'))['num_updates__sum'] if channels.filter(is_open=True).exists() else 0
+ eligible_count = 0
+ available_count = 0
detailed_active_channels = []
for channel in active_channels:
detailed_channel = {}
@@ -189,6 +209,13 @@ def home(request):
detailed_channel['htlc_count'] = channel.htlc_count
detailed_channel['auto_rebalance'] = channel.auto_rebalance
detailed_channel['ar_in_target'] = channel.ar_in_target
+ detailed_channel['inbound_can'] = (detailed_channel['remote_balance']/channel.capacity)*100
+ detailed_channel['outbound_can'] = (detailed_channel['local_balance']/channel.capacity)*100
+ detailed_channel['fee_ratio'] = 100 if channel.local_fee_rate == 0 else (channel.remote_fee_rate/channel.local_fee_rate)*100
+ if channel.auto_rebalance == True and detailed_channel['inbound_can'] >= channel.ar_in_target and detailed_channel['fee_ratio'] <= channel.ar_max_cost:
+ eligible_count += 1
+ if channel.auto_rebalance == False and detailed_channel['outbound_can'] >= channel.ar_out_target:
+ available_count += 1
detailed_active_channels.append(detailed_channel)
#Get current inactive channels
inactive_channels = channels.filter(is_active=False, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).order_by('outbound_percent')
@@ -205,8 +232,8 @@ def home(request):
onchain_txs = Onchain.objects.all()
onchain_costs_7day = 0 if onchain_txs.filter(time_stamp__gte=filter_7day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_7day).aggregate(Sum('fee'))['fee__sum']
onchain_costs_1day = 0 if onchain_txs.filter(time_stamp__gte=filter_1day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_1day).aggregate(Sum('fee'))['fee__sum']
- closures_7day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 1008)).values('chan_id'))
- closures_1day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 144)).values('chan_id'))
+ closures_7day = Closures.objects.filter(close_height__gte=(node_info.block_height - 1008))
+ closures_1day = Closures.objects.filter(close_height__gte=(node_info.block_height - 144))
close_fees_7day = closures_7day.aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_7day.exists() else 0
close_fees_1day = closures_1day.aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_1day.exists() else 0
onchain_costs_7day += close_fees_7day
@@ -215,15 +242,17 @@ def home(request):
total_costs_1day = total_1day_fees + onchain_costs_1day
#Get list of recent rebalance requests
rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')
+ active_count = node_info.num_active_channels - active_private
total_channels = node_info.num_active_channels + node_info.num_inactive_channels - private_count
local_settings = LocalSettings.objects.filter(key__contains='AR-').order_by('key')
try:
- db_size = round(path.getsize(path.expanduser(LND_DIR_PATH + '/data/graph/' + LND_NETWORK + '/channel.db'))*0.000000001, 3)
+ db_size = round(path.getsize(path.expanduser(settings.LND_DATABASE_PATH))*0.000000001, 3)
except:
db_size = 0
#Build context for front-end and render page
context = {
'node_info': node_info,
+ 'active_count': active_count,
'total_channels': total_channels,
'balances': balances,
'total_balance': balances.total_balance + sum_outbound + pending_open_balance + limbo_balance + private_outbound,
@@ -280,10 +309,10 @@ def home(request):
'1day_payments_ppm': 0 if payments_1day_amt == 0 else int((total_1day_fees/payments_1day_amt)*1000000),
'7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000),
'liq_ratio': 0 if sum_outbound == 0 else int((sum_inbound/sum_outbound)*100),
- 'eligible_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=True).annotate(inbound_can=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(),
+ 'eligible_count': eligible_count,
'enabled_count': channels.filter(is_open=True, auto_rebalance=True).count(),
- 'available_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=False).annotate(outbound_can=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).filter(outbound_can__gte=F('ar_out_target')).count(),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'available_count': available_count,
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links(),
'db_size': db_size,
@@ -299,7 +328,7 @@ def home(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def channels(request):
if request.method == 'GET':
filter_7day = datetime.now() - timedelta(days=7)
@@ -386,7 +415,7 @@ def channels(request):
'channels': [] if channels_df.empty else channels_df.sort_values(by=['cv_30day'], ascending=False).to_dict(orient='records'),
'apy_7day': apy_7day,
'apy_30day': apy_30day,
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
@@ -394,7 +423,7 @@ def channels(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def fees(request):
if request.method == 'GET':
filter_1day = datetime.now() - timedelta(days=1)
@@ -427,6 +456,11 @@ def fees(request):
else:
LocalSettings(key='AF-FailedHTLCs', value='25').save()
failed_htlc_limit = 25
+ if LocalSettings.objects.filter(key='AF-UpdateHours').exists():
+ update_hours = int(LocalSettings.objects.filter(key='AF-UpdateHours')[0].value)
+ else:
+ LocalSettings(key='AF-UpdateHours', value='24').save()
+ update_hours = 24
failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(timestamp__gte=filter_1day).order_by('-id').values())
if failed_htlc_df.shape[0] > 0:
failed_htlc_df = failed_htlc_df[(failed_htlc_df['wire_failure']==15) & (failed_htlc_df['failure_detail']==6) & (failed_htlc_df['amount']>failed_htlc_df['chan_out_liq']+failed_htlc_df['chan_out_pending'])]
@@ -475,11 +509,11 @@ def fees(request):
channels_df['new_rate'] = channels_df.apply(lambda row: max_rate if max_rate < row['new_rate'] else row['new_rate'], axis=1)
channels_df['new_rate'] = channels_df.apply(lambda row: min_rate if min_rate > row['new_rate'] else row['new_rate'], axis=1)
channels_df['adjustment'] = channels_df.apply(lambda row: int(row['new_rate']-row['local_fee_rate']), axis=1)
- channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > 86400, axis=1)
+ channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > (update_hours*3600), axis=1)
context = {
'channels': [] if channels_df.empty else channels_df.sort_values(by=['out_percent']).to_dict(orient='records'),
'local_settings': LocalSettings.objects.filter(key__contains='AF-').order_by('key'),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
@@ -487,7 +521,7 @@ def fees(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def advanced(request):
if request.method == 'GET':
channels = Channels.objects.filter(is_open=True).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('-is_active', 'outbound_percent')
@@ -498,10 +532,12 @@ def advanced(request):
channels_df['local_balance'] = channels_df.apply(lambda row: row.local_balance + row.pending_outbound, axis=1)
channels_df['remote_balance'] = channels_df.apply(lambda row: row.remote_balance + row.pending_inbound, axis=1)
channels_df['fee_ratio'] = channels_df.apply(lambda row: 100 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1)
+ channels_df['local_min_htlc'] = channels_df['local_min_htlc_msat']/1000
+ channels_df['local_max_htlc'] = channels_df['local_max_htlc_msat']/1000
context = {
'channels': channels_df.to_dict(orient='records'),
'local_settings': LocalSettings.objects.all().order_by('key'),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
@@ -509,19 +545,34 @@ def advanced(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def route(request):
if request.method == 'GET':
- payment_hash = request.GET.urlencode()[1:]
- context = {
- 'payment_hash': payment_hash,
- 'route': PaymentHops.objects.filter(payment_hash=payment_hash).annotate(ppm=Round((Sum('fee')/Sum('amt'))*1000000, output_field=IntegerField()))
- }
- return render(request, 'route.html', context)
+ try:
+ stub = lnrpc.LightningStub(lnd_connect())
+ block_height = stub.GetInfo(ln.GetInfoRequest()).block_height
+ payment_hash = request.GET.urlencode()[1:]
+ route = PaymentHops.objects.filter(payment_hash=payment_hash).annotate(ppm=Round((Sum('fee')/Sum('amt'))*1000000, output_field=IntegerField())) if PaymentHops.objects.filter(payment_hash=payment_hash).exists() else None
+ total_cost = round(route.aggregate(Sum('fee'))['fee__sum'],3) if route is not None else 0
+ total_ppm = int(total_cost*1000000/route.filter(step=1).aggregate(Sum('amt'))['amt__sum']) if route is not None else 0
+ context = {
+ 'payment_hash': payment_hash,
+ 'total_cost': total_cost,
+ 'total_ppm': total_ppm,
+ 'route': route,
+ 'rebalances': Rebalancer.objects.filter(payment_hash=payment_hash).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())),
+ 'invoices': Invoices.objects.filter(r_hash=payment_hash),
+ 'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'),
+ 'outgoing_htlcs': PendingHTLCs.objects.filter(incoming=False, hash_lock=payment_hash).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock')
+ }
+ return render(request, 'route.html', context)
+ except Exception as e:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def routes(request):
if request.method == 'GET':
try:
@@ -537,39 +588,39 @@ def routes(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def peers(request):
if request.method == 'GET':
peers = Peers.objects.filter(connected=True)
context = {
'peers': peers,
'num_peers': len(peers),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'peers.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def balances(request):
if request.method == 'GET':
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
context = {
'utxos': stub.ListUnspent(ln.ListUnspentRequest(min_confs=0, max_confs=9999999)).utxos,
'transactions': list(Onchain.objects.filter(block_height=0)) + list(Onchain.objects.exclude(block_height=0).order_by('-block_height')),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'network_links': network_links()
}
return render(request, 'balances.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def closures(request):
if request.method == 'GET':
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
pending_channels = stub.PendingChannels(ln.PendingChannelsRequest())
channels = Channels.objects.all()
pending_closed = None
@@ -588,20 +639,19 @@ def closures(request):
if closures_df.empty:
merged = DataFrame()
else:
- channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias', 'closing_costs'))
+ channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias'))
if channels_df.empty:
merged = closures_df
merged['alias'] = ''
else:
merged = merge(closures_df, channels_df, on='chan_id', how='left')
merged['alias'] = merged['alias'].fillna('')
- merged['closing_costs'] = merged['closing_costs'].fillna('')
context = {
'pending_closed': pending_closed,
'pending_force_closed': pending_force_closed,
'waiting_for_close': waiting_for_close,
'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'network_links': network_links(),
'graph_links': graph_links()
}
@@ -625,11 +675,11 @@ def find_next_block_maturity(force_closing_channel):
return pending_htlc.blocks_til_maturity
return -1
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def towers(request):
if request.method == 'GET':
try:
- stub = wtstub.WatchtowerClientStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = wtstub.WatchtowerClientStub(lnd_connect())
towers = stub.ListTowers(wtrpc.ListTowersRequest(include_sessions=False)).towers
active_towers = []
inactive_towers = []
@@ -657,13 +707,13 @@ def towers(request):
else:
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def add_tower_form(request):
if request.method == 'POST':
form = AddTowerForm(request.POST)
if form.is_valid():
try:
- stub = wtstub.WatchtowerClientStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = wtstub.WatchtowerClientStub(lnd_connect())
tower = form.cleaned_data['tower']
if tower.count('@') == 1 and len(tower.split('@')[0]) == 66:
peer_pubkey, host = tower.split('@')
@@ -681,13 +731,13 @@ def add_tower_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def delete_tower_form(request):
if request.method == 'POST':
form = DeleteTowerForm(request.POST)
if form.is_valid():
try:
- stub = wtstub.WatchtowerClientStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = wtstub.WatchtowerClientStub(lnd_connect())
pubkey = bytes.fromhex(form.cleaned_data['pubkey'])
address = form.cleaned_data['address']
response = stub.RemoveTower(wtrpc.RemoveTowerRequest(pubkey=pubkey, address=address))
@@ -702,13 +752,13 @@ def delete_tower_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def remove_tower_form(request):
if request.method == 'POST':
form = RemoveTowerForm(request.POST)
if form.is_valid():
try:
- stub = wtstub.WatchtowerClientStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = wtstub.WatchtowerClientStub(lnd_connect())
pubkey = bytes.fromhex(form.cleaned_data['pubkey'])
response = stub.RemoveTower(wtrpc.RemoveTowerRequest(pubkey=pubkey))
messages.success(request, 'Tower removal successful!' + str(response))
@@ -722,30 +772,34 @@ def remove_tower_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def resolutions(request):
if request.method == 'GET':
chan_id = request.GET.urlencode()[1:]
context = {
'chan_id': chan_id,
'resolutions': Resolutions.objects.filter(chan_id=chan_id),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'network_links': network_links()
}
return render(request, 'resolutions.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def income(request):
if request.method == 'GET':
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
filter_90day = datetime.now() - timedelta(days=90)
filter_30day = datetime.now() - timedelta(days=30)
filter_7day = datetime.now() - timedelta(days=7)
filter_1day = datetime.now() - timedelta(days=1)
node_info = stub.GetInfo(ln.GetInfoRequest())
- channels = Channels.objects.all()
+ invoices = Invoices.objects.filter(state=1, is_revenue=True)
+ invoices_90day = invoices.filter(settle_date__gte=filter_90day)
+ invoices_30day = invoices.filter(settle_date__gte=filter_30day)
+ invoices_7day = invoices.filter(settle_date__gte=filter_7day)
+ invoices_1day = invoices.filter(settle_date__gte=filter_1day)
payments = Payments.objects.filter(status=2)
payments_90day = payments.filter(creation_date__gte=filter_90day)
payments_30day = payments.filter(creation_date__gte=filter_30day)
@@ -756,11 +810,11 @@ def income(request):
onchain_txs_30day = onchain_txs.filter(time_stamp__gte=filter_30day)
onchain_txs_7day = onchain_txs.filter(time_stamp__gte=filter_7day)
onchain_txs_1day = onchain_txs.filter(time_stamp__gte=filter_1day)
- closures = channels.filter(chan_id__in=Closures.objects.all().values('chan_id'))
- closures_90day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 12960)).values('chan_id'))
- closures_30day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 4320)).values('chan_id'))
- closures_7day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 1008)).values('chan_id'))
- closures_1day = channels.filter(chan_id__in=Closures.objects.filter(close_height__gte=(node_info.block_height - 144)).values('chan_id'))
+ closures = Closures.objects.all()
+ closures_90day = Closures.objects.filter(close_height__gte=(node_info.block_height - 12960))
+ closures_30day = Closures.objects.filter(close_height__gte=(node_info.block_height - 4320))
+ closures_7day = Closures.objects.filter(close_height__gte=(node_info.block_height - 1008))
+ closures_1day = Closures.objects.filter(close_height__gte=(node_info.block_height - 144))
forwards = Forwards.objects.all()
forwards_90day = forwards.filter(forward_date__gte=filter_90day)
forwards_30day = forwards.filter(forward_date__gte=filter_30day)
@@ -781,6 +835,16 @@ def income(request):
total_revenue_30day = 0 if forward_count_30day == 0 else int(forwards_30day.aggregate(Sum('fee'))['fee__sum'])
total_revenue_7day = 0 if forward_count_7day == 0 else int(forwards_7day.aggregate(Sum('fee'))['fee__sum'])
total_revenue_1day = 0 if forward_count_1day == 0 else int(forwards_1day.aggregate(Sum('fee'))['fee__sum'])
+ total_received = 0 if invoices.count() == 0 else int(invoices.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_received_90day = 0 if invoices_90day.count() == 0 else int(invoices_90day.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_received_30day = 0 if invoices_30day.count() == 0 else int(invoices_30day.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_received_7day = 0 if invoices_7day.count() == 0 else int(invoices_7day.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_received_1day = 0 if invoices_1day.count() == 0 else int(invoices_1day.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_revenue += total_received
+ total_revenue_90day += total_received_90day
+ total_revenue_30day += total_received_30day
+ total_revenue_7day += total_received_7day
+ total_revenue_1day += total_received_1day
total_revenue_ppm = 0 if forward_amount == 0 else int(total_revenue/(forward_amount/1000000))
total_revenue_ppm_90day = 0 if forward_amount_90day == 0 else int(total_revenue_90day/(forward_amount_90day/1000000))
total_revenue_ppm_30day = 0 if forward_amount_30day == 0 else int(total_revenue_30day/(forward_amount_30day/1000000))
@@ -873,14 +937,14 @@ def income(request):
'percent_cost_30day': 0 if total_revenue_30day == 0 else int(((total_fees_30day+onchain_costs_30day)/total_revenue_30day)*100),
'percent_cost_7day': 0 if total_revenue_7day == 0 else int(((total_fees_7day+onchain_costs_7day)/total_revenue_7day)*100),
'percent_cost_1day': 0 if total_revenue_1day == 0 else int(((total_fees_1day+onchain_costs_1day)/total_revenue_1day)*100),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'income.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def channel(request):
if request.method == 'GET':
chan_id = request.GET.urlencode()[1:]
@@ -901,6 +965,7 @@ def channel(request):
channels_df = DataFrame.from_records(Channels.objects.filter(chan_id=chan_id).values())
rebalancer_df = DataFrame.from_records(Rebalancer.objects.filter(last_hop_pubkey=channels_df['remote_pubkey'][0]).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id').values())
failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values())
+ peer_info_df = DataFrame.from_records(Peers.objects.filter(pubkey=channels_df['remote_pubkey'][0]).values())
channels_df['local_balance'] = channels_df['local_balance'] + channels_df['pending_outbound']
channels_df['remote_balance'] = channels_df['remote_balance'] + channels_df['pending_inbound']
channels_df['in_percent'] = int(round((channels_df['remote_balance']/channels_df['capacity'])*100, 0))
@@ -1132,20 +1197,20 @@ def channel(request):
channels_df['amt_rebal_in_30day'] = int(invoices_df_30d_sum.loc[chan_id].amt_paid) if invoices_df_30d_count.empty == False else 0
channels_df['amt_rebal_in_7day'] = int(invoices_df_7d_sum.loc[chan_id].amt_paid) if invoices_df_7d_count.empty == False else 0
channels_df['amt_rebal_in_1day'] = int(invoices_df_1d_sum.loc[chan_id].amt_paid) if invoices_df_1d_count.empty == False else 0
- rebal_payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(rebal_chan=chan_id).values())
+ rebal_payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(value__gte=1000.0).filter(rebal_chan=chan_id).values())
if rebal_payments_df.shape[0] > 0:
rebal_payments_df_30d = rebal_payments_df.loc[rebal_payments_df['creation_date'] >= filter_30day]
rebal_payments_df_7d = rebal_payments_df_30d.loc[rebal_payments_df_30d['creation_date'] >= filter_7day]
rebal_payments_df_1d = rebal_payments_df_7d.loc[rebal_payments_df_7d['creation_date'] >= filter_1day]
- invoice_hashes = DataFrame() if invoices_df.empty else invoices_df.groupby('chan_in', as_index=True)['r_hash'].apply(list)
- invoice_hashes_30d = DataFrame() if invoices_df_30d.empty else invoices_df_30d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
- invoice_hashes_7d = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
- invoice_hashes_1d = DataFrame() if invoices_df_1d.empty else invoices_df_1d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ invoice_hashes = DataFrame() if invoices_df.empty else invoices_df.loc[invoices_df['value'] >= 1000].groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ invoice_hashes_30d = DataFrame() if invoices_df_30d.empty else invoices_df_30d.loc[invoices_df_30d['value'] >= 1000].groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ invoice_hashes_7d = DataFrame() if invoices_df_7d.empty else invoices_df_7d.loc[invoices_df_7d['value'] >= 1000].groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ invoice_hashes_1d = DataFrame() if invoices_df_1d.empty else invoices_df_1d.loc[invoices_df_1d['value'] >= 1000].groupby('chan_in', as_index=True)['r_hash'].apply(list)
channels_df['costs'] = 0 if channels_df['rebal_in'][0] == 0 or invoice_hashes.empty == True else int(rebal_payments_df.set_index('payment_hash', inplace=False).loc[invoice_hashes[chan_id]]['fee'].sum())
channels_df['costs_30day'] = 0 if channels_df['rebal_in_30day'][0] == 0 or invoice_hashes_30d.empty == True else int(rebal_payments_df_30d.set_index('payment_hash', inplace=False).loc[invoice_hashes_30d[chan_id]]['fee'].sum())
channels_df['costs_7day'] = 0 if channels_df['rebal_in_7day'][0] == 0 or invoice_hashes_7d.empty == True else int(rebal_payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[chan_id]]['fee'].sum())
channels_df['costs_1day'] = 0 if channels_df['rebal_in_1day'][0] == 0 or invoice_hashes_1d.empty == True else int(rebal_payments_df_1d.set_index('payment_hash', inplace=False).loc[invoice_hashes_1d[chan_id]]['fee'].sum())
- channels_df['costs'] += channels_df['closing_costs']
+ channels_df['costs'] += Closures.objects.filter(funding_txid=channels_df['funding_txid'][0],funding_index=channels_df['output_index'][0])[0].closing_costs if Closures.objects.filter(funding_txid=channels_df['funding_txid'][0],funding_index=channels_df['output_index'][0]).exists() else 0
channels_df['profits'] = channels_df['revenue'] - channels_df['costs']
channels_df['profits_30day'] = channels_df['revenue_30day'] - channels_df['costs_30day']
channels_df['profits_7day'] = channels_df['revenue_7day'] - channels_df['costs_7day']
@@ -1238,6 +1303,9 @@ def channel(request):
channels_df['cv_30day'] = round((channels_df['revenue_30day']*1216.6667)/(channels_df['capacity']*outbound_ratio) + channels_df['assisted_apy_30day'], 2)
channels_df['cv_7day'] = round((channels_df['revenue_7day']*5214.2857)/(channels_df['capacity']*outbound_ratio) + channels_df['assisted_apy_7day'], 2)
channels_df['cv_1day'] = round((channels_df['revenue_1day']*36500)/(channels_df['capacity']*outbound_ratio) + channels_df['assisted_apy_1day'], 2)
+ autofees_df = DataFrame.from_records(Autofees.objects.filter(chan_id=chan_id).filter(timestamp__gte=filter_30day).order_by('-id').values())
+ if autofees_df.shape[0]> 0:
+ autofees_df['change'] = autofees_df.apply(lambda row: 0 if row.old_value == 0 else round((row.new_value-row.old_value)*100/row.old_value, 1), axis=1)
else:
channels_df = DataFrame()
forwards_df = DataFrame()
@@ -1245,6 +1313,8 @@ def channel(request):
invoices_df = DataFrame()
rebalancer_df = DataFrame()
failed_htlc_df = DataFrame()
+ peer_info_df = DataFrame()
+ autofees_df = DataFrame()
context = {
'chan_id': chan_id,
'channel': [] if channels_df.empty else channels_df.to_dict(orient='records')[0],
@@ -1255,33 +1325,46 @@ def channel(request):
'invoices': [] if invoices_df.empty else invoices_df.sort_values(by=['settle_date'], ascending=False).to_dict(orient='records')[:5],
'rebalances': [] if rebalancer_df.empty else rebalancer_df.to_dict(orient='records')[:5],
'failed_htlcs': [] if failed_htlc_df.empty else failed_htlc_df.to_dict(orient='records')[:5],
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'peer_info': [] if peer_info_df.empty else peer_info_df.to_dict(orient='records')[0],
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
- 'network_links': network_links()
+ 'network_links': network_links(),
+ 'autofees': [] if autofees_df.empty else autofees_df.to_dict(orient='records')
}
- return render(request, 'channel.html', context)
+ try:
+ return render(request, 'channel.html', context)
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def opens(request):
if request.method == 'GET':
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
current_peers = Channels.objects.filter(is_open=True).values_list('remote_pubkey')
+ exlcude_list = AvoidNodes.objects.values_list('pubkey')
filter_60day = datetime.now() - timedelta(days=60)
payments_60day = Payments.objects.filter(creation_date__gte=filter_60day).values_list('payment_hash')
- open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).values('node_pubkey', 'alias').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).exclude(score=0).order_by('-score', 'ppm')[:21]
+ open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).exclude(node_pubkey__in=exlcude_list).values('node_pubkey').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).exclude(score=0).order_by('-score', 'ppm')[:21]
+ for open in open_list:
+ open['alias'] = PaymentHops.objects.filter(node_pubkey=open['node_pubkey']).order_by('-id')[0].alias
context = {
'open_list': open_list,
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'avoid_list': AvoidNodes.objects.all(),
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'open_list.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def actions(request):
if request.method == 'GET':
channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity'))
@@ -1334,7 +1417,7 @@ def actions(request):
action_list.append(result)
context = {
'action_list': action_list,
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
}
@@ -1342,10 +1425,10 @@ def actions(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def pending_htlcs(request):
if request.method == 'GET':
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
block_height = stub.GetInfo(ln.GetInfoRequest()).block_height
context = {
'incoming_htlcs': PendingHTLCs.objects.filter(incoming=True).annotate(blocks_til_expiration=Sum('expiration_height')-block_height).annotate(hours_til_expiration=((Sum('expiration_height')-block_height)*10)/60).order_by('hash_lock'),
@@ -1355,27 +1438,41 @@ def pending_htlcs(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def failed_htlcs(request):
if request.method == 'GET':
- context = {
- 'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:150],
- }
- return render(request, 'failed_htlcs.html', context)
+ try:
+ #print (f"{datetime.now().strftime('%c')} : {request.GET.urlencode()=}")
+ query = None if request.GET.urlencode()[1:] == '' else request.GET.urlencode()[1:].split('_')
+ chan_id = None if query is None or len(query) < 1 else query[0]
+ direction = None if query is None or len(query) < 2 else query[1]
+ #print (f"{datetime.now().strftime('%c')} : {query=} {chan_id=} {direction=}")
+ failed_htlcs=FailedHTLCs.objects.all().order_by('-id')[:150] if chan_id is None else (FailedHTLCs.objects.filter(chan_id_out=chan_id).order_by('-id')[:150] if direction == "O" else FailedHTLCs.objects.filter(chan_id_in=chan_id).order_by('-id')[:150])
+
+ context = {
+ 'failed_htlcs': failed_htlcs
+ }
+ return render(request, 'failed_htlcs.html', context)
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def payments(request):
if request.method == 'GET':
context = {
- 'payments': Payments.objects.filter(status=2).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:150],
+ 'payments': Payments.objects.exclude(status=3).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:150],
}
return render(request, 'payments.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def invoices(request):
if request.method == 'GET':
context = {
@@ -1385,17 +1482,27 @@ def invoices(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def rebalances(request):
if request.method == 'GET':
- context = {
- 'rebalances': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:150],
- }
- return render(request, 'rebalances.html', context)
+ try:
+ rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')
+ rebalances_success = rebalances.filter(status=2)
+ context = {
+ 'rebalances': rebalances[:150],
+ 'rebalances_success' : rebalances_success[:69]
+ }
+ return render(request, 'rebalances.html', context)
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def batch(request):
if request.method == 'GET':
context = {
@@ -1418,7 +1525,7 @@ def open_peer(peer_pubkey, stub):
except:
return False
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def batch_open(request):
if request.method == 'POST':
form = BatchOpenForm(request.POST)
@@ -1426,7 +1533,7 @@ def batch_open(request):
count = 0
fail = False
open_list = []
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
if form.cleaned_data['pubkey1'] and form.cleaned_data['amt1'] and len(form.cleaned_data['pubkey1']) == 66:
count += 1
peer_pubkey = form.cleaned_data['pubkey1']
@@ -1542,7 +1649,7 @@ def batch_open(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('batch')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def forwards(request):
if request.method == 'GET':
context = {
@@ -1552,14 +1659,16 @@ def forwards(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def rebalancing(request):
if request.method == 'GET':
+ channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True, private=False).annotate(percent_inbound=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).order_by('-is_active', 'percent_outbound').values())
filter_7day = datetime.now() - timedelta(days=7)
rebalancer_7d_df = DataFrame.from_records(Rebalancer.objects.filter(stop__gte=filter_7day).order_by('-id').values())
- channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True, private=False).annotate(percent_inbound=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).order_by('-is_active', 'percent_outbound').values())
if channels_df.shape[0] > 0:
channels_df['inbound_can'] = channels_df['percent_inbound'] / channels_df['ar_in_target']
+ channels_df['local_balance'] = channels_df['local_balance'] + channels_df['pending_outbound']
+ channels_df['remote_balance'] = channels_df['remote_balance'] + channels_df['pending_inbound']
channels_df['fee_ratio'] = channels_df.apply(lambda row: 100 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1)
channels_df['fee_check'] = channels_df.apply(lambda row: 1 if row['ar_max_cost'] == 0 else int(round(((row['fee_ratio']/row['ar_max_cost'])*1000)/10, 0)), axis=1)
channels_df['steps'] = channels_df.apply(lambda row: 0 if row['inbound_can'] < 1 else int(((row['percent_inbound']-row['ar_in_target'])/((row['ar_amt_target']/row['capacity'])*100))+0.999), axis=1)
@@ -1577,6 +1686,24 @@ def rebalancing(request):
eligible_count = 0
enabled_count = 0
available_count = 0
+ try:
+ query = request.GET.urlencode()[1:]
+ if query == '1':
+ #Filter Sink (AR Enabled)
+ channels_df = channels_df[channels_df['auto_rebalance']==True][channels_df['is_active']==True]
+ elif query == '2':
+ #Filter Source (Eligible to rebalance out)
+ channels_df = channels_df[channels_df['auto_rebalance']==False][channels_df['is_active']==True].sort_values(by=['percent_outbound'], ascending=False)
+ else:
+ #Proceed
+ pass
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
+
context = {
'eligible_count': eligible_count,
'enabled_count': enabled_count,
@@ -1585,14 +1712,14 @@ def rebalancing(request):
'rebalancer': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:20],
'rebalancer_form': RebalancerForm,
'local_settings': LocalSettings.objects.filter(key__contains='AR-').order_by('key'),
- 'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
+ 'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
return render(request, 'rebalancing.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def keysends(request):
if request.method == 'GET':
context = {
@@ -1602,33 +1729,49 @@ def keysends(request):
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def autopilot(request):
if request.method == 'GET':
+ chan_id = request.GET.urlencode()[1:]
+ filter_21d = datetime.now() - timedelta(days=21)
+ autopilot = Autopilot.objects.filter(timestamp__gte=filter_21d).order_by('-id') if chan_id == "" else Autopilot.objects.filter(chan_id = chan_id).filter(timestamp__gte=filter_21d).order_by('-id')
context = {
- 'autopilot': Autopilot.objects.all().order_by('-id')
+ 'autopilot': autopilot
}
return render(request, 'autopilot.html', context)
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def autofees(request):
if request.method == 'GET':
- context = {
- 'autofees': Autofees.objects.all().order_by('-id')
- }
- return render(request, 'autofees.html', context)
+ chan_id = request.GET.urlencode()[1:]
+ filter_7d = datetime.now() - timedelta(days=7)
+ autofees_df = DataFrame.from_records(Autofees.objects.filter(timestamp__gte=filter_7d).order_by('-id').values() if chan_id == "" else Autofees.objects.filter(chan_id=chan_id).filter(timestamp__gte=filter_7d).order_by('-id').values())
+ if autofees_df.shape[0]> 0:
+ autofees_df['change'] = autofees_df.apply(lambda row: 0 if row.old_value == 0 else round((row.new_value-row.old_value)*100/row.old_value, 1), axis=1)
+ #print (f"{datetime.now().strftime('%c')} : {chan_id=} {autofees=}")
+ try:
+ context = {
+ 'autofees': [] if autofees_df.empty else autofees_df.to_dict(orient='records')
+ }
+ return render(request, 'autofees.html', context)
+ except Exception as e:
+ try:
+ error = str(e.code())
+ except:
+ error = str(e)
+ return render(request, 'error.html', {'error': error})
else:
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def open_channel_form(request):
if request.method == 'POST':
form = OpenChannelForm(request.POST)
if form.is_valid():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
peer_pubkey = form.cleaned_data['peer_pubkey']
connected = False
if Peers.objects.filter(pubkey=peer_pubkey, connected=True).exists():
@@ -1660,7 +1803,7 @@ def open_channel_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def close_channel_form(request):
if request.method == 'POST':
form = CloseChannelForm(request.POST)
@@ -1676,7 +1819,7 @@ def close_channel_form(request):
channel_point.funding_txid_bytes = bytes.fromhex(funding_txid)
channel_point.funding_txid_str = funding_txid
channel_point.output_index = output_index
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
if form.cleaned_data['force']:
for response in stub.CloseChannel(ln.CloseChannelRequest(channel_point=channel_point, force=True)):
messages.success(request, 'Channel force closed! Closing TXID: ' + str(response.close_pending.txid[::-1].hex()) + ':' + str(response.close_pending.output_index))
@@ -1697,13 +1840,13 @@ def close_channel_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def connect_peer_form(request):
if request.method == 'POST':
form = ConnectPeerForm(request.POST)
if form.is_valid():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
peer_id = form.cleaned_data['peer_id']
if peer_id.count('@') == 0 and len(peer_id) == 66:
peer_pubkey = peer_id
@@ -1726,12 +1869,17 @@ def connect_peer_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def new_address_form(request):
if request.method == 'POST':
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- response = stub.NewAddress(ln.NewAddressRequest(type=0))
+ stub = lnrpc.LightningStub(lnd_connect())
+ version = stub.GetInfo(ln.GetInfoRequest()).version
+ # Verify sufficient version to handle p2tr address creation
+ if float(version[:4]) >= 0.15:
+ response = stub.NewAddress(ln.NewAddressRequest(type=4))
+ else:
+ response = stub.NewAddress(ln.NewAddressRequest(type=0))
messages.success(request, 'Deposit Address: ' + str(response.address))
except Exception as e:
error = str(e)
@@ -1741,13 +1889,13 @@ def new_address_form(request):
messages.error(request, 'Address request failed! Error: ' + error_msg)
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def add_invoice_form(request):
if request.method == 'POST':
form = AddInvoiceForm(request.POST)
if form.is_valid():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
response = stub.AddInvoice(ln.Invoice(value=form.cleaned_data['value']))
messages.success(request, 'Invoice created! ' + str(response.payment_request))
except Exception as e:
@@ -1760,7 +1908,7 @@ def add_invoice_form(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def rebalance(request):
if request.method == 'POST':
form = RebalancerForm(request.POST)
@@ -1789,14 +1937,14 @@ def rebalance(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def update_chan_policy(request):
if request.method == 'POST':
form = ChanPolicyForm(request.POST)
if form.is_valid():
if form.cleaned_data['new_base_fee'] is not None or form.cleaned_data['new_fee_rate'] is not None or form.cleaned_data['new_cltv'] is not None:
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
if form.cleaned_data['target_all']:
if form.cleaned_data['new_base_fee'] is not None and form.cleaned_data['new_fee_rate'] is not None and form.cleaned_data['new_cltv'] is not None:
args = {'global': True, 'base_fee_msat': form.cleaned_data['new_base_fee'], 'fee_rate': (form.cleaned_data['new_fee_rate']/1000000), 'time_lock_delta': form.cleaned_data['new_cltv']}
@@ -1821,10 +1969,13 @@ def update_chan_policy(request):
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=new_base_fee, fee_rate=new_fee_rate, time_lock_delta=new_cltv))
db_channel = Channels.objects.get(chan_id=channel.chan_id)
db_channel.local_base_fee = new_base_fee
+ old_fee_rate = db_channel.local_fee_rate
db_channel.local_fee_rate = new_fee_rate*1000000
db_channel.local_cltv = new_cltv
if form.cleaned_data['new_fee_rate'] is not None:
db_channel.fees_updated = datetime.now()
+ Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Manual"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save()
+
db_channel.save()
else:
messages.error(request, 'No channels were specified in the update request!')
@@ -1842,7 +1993,7 @@ def update_chan_policy(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect('home')
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def auto_rebalance(request):
if request.method == 'POST':
form = AutoRebalanceForm(request.POST)
@@ -1987,7 +2138,7 @@ def auto_rebalance(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def update_channel(request):
if request.method == 'POST':
form = UpdateChannel(request.POST)
@@ -1997,7 +2148,7 @@ def update_channel(request):
update_target = int(form.cleaned_data['update_target'])
db_channel = Channels.objects.filter(chan_id=chan_id)[0]
if update_target == 0:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channel_point = ln.ChannelPoint()
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
channel_point.funding_txid_str = db_channel.funding_txid
@@ -2007,15 +2158,17 @@ def update_channel(request):
db_channel.save()
messages.success(request, 'Base fee for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
elif update_target == 1:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
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 = db_channel.output_index
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(target/1000000), time_lock_delta=db_channel.local_cltv))
+ old_fee_rate = db_channel.local_fee_rate
db_channel.local_fee_rate = target
db_channel.fees_updated = datetime.now()
db_channel.save()
+ Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Manual"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save()
messages.success(request, 'Fee rate for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
elif update_target == 2:
db_channel.ar_amt_target = target
@@ -2038,7 +2191,7 @@ def update_channel(request):
db_channel.save()
messages.success(request, 'Auto rebalancer max cost for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target) + '%')
elif update_target == 7:
- stub = lnrouter.RouterStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrouter.RouterStub(lnd_connect())
channel_point = ln.ChannelPoint()
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
channel_point.funding_txid_str = db_channel.funding_txid
@@ -2054,7 +2207,7 @@ def update_channel(request):
db_channel.save()
messages.success(request, 'Auto fees status for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.auto_fees))
elif update_target == 9:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channel_point = ln.ChannelPoint()
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
channel_point.funding_txid_str = db_channel.funding_txid
@@ -2062,18 +2215,34 @@ def update_channel(request):
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=target))
db_channel.local_cltv = target
db_channel.save()
- messages.success(request, 'CLTV for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
+ messages.success(request, 'CLTV for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(float(target)))
elif update_target == 10:
- db_channel.closing_costs = target
+ stub = lnrpc.LightningStub(lnd_connect())
+ 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 = db_channel.output_index
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, min_htlc_msat_specified=True, min_htlc_msat=int(target*1000)))
+ db_channel.local_min_htlc_msat = int(target*1000)
+ db_channel.save()
+ messages.success(request, 'Min HTLC for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(float(target)))
+ elif update_target == 11:
+ stub = lnrpc.LightningStub(lnd_connect())
+ 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 = db_channel.output_index
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, max_htlc_msat=int(target*1000)))
+ db_channel.local_max_htlc_msat = int(target*1000)
db_channel.save()
- messages.success(request, 'Closing costs for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.closing_costs))
+ messages.success(request, 'Max HTLC for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
else:
messages.error(request, 'Invalid target code. Please try again.')
else:
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def update_pending(request):
if request.method == 'POST':
form = UpdatePending(request.POST)
@@ -2129,7 +2298,7 @@ def update_pending(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-@login_required(login_url='/lndg-admin/login/?next=/')
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def update_setting(request):
if request.method == 'POST':
form = UpdateSetting(request.POST)
@@ -2288,7 +2457,7 @@ def update_setting(request):
messages.success(request, 'Updated payment cleanup retention days to: ' + str(retention_days))
elif key == 'ALL-oRate':
target = int(value)
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channels = Channels.objects.filter(is_open=True)
for db_channel in channels:
channel_point = ln.ChannelPoint()
@@ -2296,16 +2465,18 @@ def update_setting(request):
channel_point.funding_txid_str = db_channel.funding_txid
channel_point.output_index = db_channel.output_index
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(target/1000000), time_lock_delta=db_channel.local_cltv))
+ old_fee_rate = db_channel.local_fee_rate
db_channel.local_fee_rate = target
db_channel.fees_updated = datetime.now()
db_channel.save()
+ Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Manual"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save()
messages.success(request, 'Fee rate for all open channels updated to a value of: ' + str(target))
elif key == 'ALL-oBase':
target = int(value)
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channels = Channels.objects.filter(is_open=True)
for db_channel in channels:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channel_point = ln.ChannelPoint()
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
channel_point.funding_txid_str = db_channel.funding_txid
@@ -2316,10 +2487,10 @@ def update_setting(request):
messages.success(request, 'Base fee for all channels updated to a value of: ' + str(target))
elif key == 'ALL-CLTV':
target = int(value)
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channels = Channels.objects.filter(is_open=True)
for db_channel in channels:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
channel_point = ln.ChannelPoint()
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
channel_point.funding_txid_str = db_channel.funding_txid
@@ -2328,6 +2499,20 @@ def update_setting(request):
db_channel.local_cltv = target
db_channel.save()
messages.success(request, 'CLTV for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-minHTLC':
+ target = int(float(value)*1000)
+ stub = lnrpc.LightningStub(lnd_connect())
+ channels = Channels.objects.filter(is_open=True)
+ for db_channel in channels:
+ stub = lnrpc.LightningStub(lnd_connect())
+ 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 = db_channel.output_index
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=db_channel.local_base_fee, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=db_channel.local_cltv, min_htlc_msat_specified=True, min_htlc_msat=target))
+ db_channel.local_min_htlc_msat = target
+ db_channel.save()
+ messages.success(request, 'Min HTLC for all channels updated to a value of: ' + str(float(value)))
elif key == 'ALL-Amts':
target = int(value)
channels = Channels.objects.filter(is_open=True).update(ar_amt_target=target)
@@ -2412,53 +2597,147 @@ def update_setting(request):
db_enabled.value = enabled
db_enabled.save()
messages.success(request, 'Updated autofees daily failed HTLC trigger limit setting to: ' + str(enabled))
+ elif key == 'AF-UpdateHours':
+ enabled = int(value)
+ try:
+ db_enabled = LocalSettings.objects.get(key='AF-UpdateHours')
+ except:
+ LocalSettings(key='AF-UpdateHours', value='24').save()
+ db_enabled = LocalSettings.objects.get(key='AF-UpdateHours')
+ db_enabled.value = enabled
+ db_enabled.save()
+ messages.success(request, 'Updated autofees update hours setting to: ' + str(enabled))
else:
- messages.error(request, 'Invalid Request. Please try again.')
+ messages.error(request, 'Invalid Request. Please try again. [' + key +']')
+ else:
+ messages.error(request, 'Invalid Request Form. Please try again.')
+ return redirect(request.META.get('HTTP_REFERER'))
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def update_closing(request):
+ if request.method == 'POST':
+ form = UpdateClosing(request.POST)
+ if form.is_valid() and Closures.objects.filter(funding_txid=form.cleaned_data['funding_txid'], funding_index=form.cleaned_data['funding_index']).exists():
+ funding_txid = form.cleaned_data['funding_txid']
+ funding_index = form.cleaned_data['funding_index']
+ target = int(form.cleaned_data['target'])
+ db_closing = Closures.objects.filter(funding_txid=funding_txid, funding_index=funding_index)[0]
+ db_closing.closing_costs = target
+ db_closing.save()
+ messages.success(request, 'Updated closing costs for ' + str(funding_txid) + ':' + str(funding_index) + ' updated to a value of: ' + str(target))
+ else:
+ messages.error(request, 'Invalid Request. Please try again.')
+ return redirect(request.META.get('HTTP_REFERER'))
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def update_keysend(request):
+ if request.method == 'POST':
+ form = UpdateKeysend(request.POST)
+ if form.is_valid() and Invoices.objects.filter(r_hash=form.cleaned_data['r_hash']).exists():
+ r_hash = form.cleaned_data['r_hash']
+ db_invoice = Invoices.objects.filter(r_hash=r_hash)[0]
+ db_invoice.is_revenue = not db_invoice.is_revenue
+ db_invoice.save()
+ messages.success(request, ('Marked' if db_invoice.is_revenue else 'Unmarked') + ' invoice ' + str(r_hash) + ' as revenue.')
+ else:
+ messages.error(request, 'Invalid Request. Please try again.')
+ return redirect(request.META.get('HTTP_REFERER'))
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def add_avoid(request):
+ if request.method == 'POST':
+ form = AddAvoid(request.POST)
+ if form.is_valid():
+ pubkey = form.cleaned_data['pubkey']
+ notes = form.cleaned_data['notes']
+ AvoidNodes(pubkey=pubkey, notes=notes).save()
+ messages.success(request, 'Successfully added node ' + str(pubkey) + ' to the avoid list.')
+ else:
+ messages.error(request, 'Invalid Request. Please try again.')
+ return redirect(request.META.get('HTTP_REFERER'))
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def remove_avoid(request):
+ if request.method == 'POST':
+ form = RemoveAvoid(request.POST)
+ if form.is_valid() and AvoidNodes.objects.filter(pubkey=form.cleaned_data['pubkey']).exists():
+ pubkey = form.cleaned_data['pubkey']
+ AvoidNodes.objects.filter(pubkey=pubkey).delete()
+ messages.success(request, 'Successfully removed node ' + str(pubkey) + ' from the avoid list.')
else:
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def get_fees(request):
+ if request.method == 'GET':
+ missing_fees = Closures.objects.exclude(close_type__in=[4, 5]).exclude(open_initiator=2, resolution_count=0).filter(closing_costs=0)
+ if missing_fees:
+ for missing_fee in missing_fees:
+ try:
+ txid = missing_fee.closing_tx
+ closing_costs = get_tx_fees(txid) if missing_fee.open_initiator == 1 else 0
+ for resolution in Resolutions.objects.filter(chan_id=missing_fee.chan_id).exclude(resolution_type=2):
+ closing_costs += get_tx_fees(resolution.sweep_txid)
+ missing_fee.closing_costs = closing_costs
+ missing_fee.save()
+ except Exception as error:
+ messages.error(request, f"Error getting closure fees: {txid=} {error=}")
+ return redirect(request.META.get('HTTP_REFERER'))
+ return redirect(request.META.get('HTTP_REFERER'))
+
class PaymentsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Payments.objects.all()
serializer_class = PaymentSerializer
class PaymentHopsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = PaymentHops.objects.all()
serializer_class = PaymentHopsSerializer
class InvoicesViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Invoices.objects.all()
serializer_class = InvoiceSerializer
class ForwardsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Forwards.objects.all()
serializer_class = ForwardSerializer
class PeersViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Peers.objects.all()
serializer_class = PeerSerializer
class OnchainViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Onchain.objects.all()
serializer_class = OnchainSerializer
class ClosuresViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Closures.objects.all()
serializer_class = ClosuresSerializer
class ResolutionsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Resolutions.objects.all()
serializer_class = ResolutionsSerializer
class PendingHTLCViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = PendingHTLCs.objects.all()
serializer_class = PendingHTLCSerializer
class FailedHTLCViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = FailedHTLCs.objects.all()
serializer_class = FailedHTLCSerializer
class LocalSettingsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = LocalSettings.objects.all()
serializer_class = LocalSettingsSerializer
@@ -2472,6 +2751,7 @@ def update(self, request, pk=None):
return Response(serializer.errors)
class ChannelsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Channels.objects.all()
serializer_class = ChannelSerializer
@@ -2485,6 +2765,7 @@ def update(self, request, pk=None):
return Response(serializer.errors)
class RebalancerViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Rebalancer.objects.all()
serializer_class = RebalancerSerializer
@@ -2497,11 +2778,12 @@ def create(self, request):
return Response(serializer.errors)
@api_view(['POST'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def connect_peer(request):
serializer = ConnectPeerSerializer(data=request.data)
if serializer.is_valid():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
peer_id = serializer.validated_data['peer_id']
if peer_id.count('@') == 0 and len(peer_id) == 66:
peer_pubkey = peer_id
@@ -2524,11 +2806,12 @@ def connect_peer(request):
return Response({'error': 'Invalid request!'})
@api_view(['POST'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def open_channel(request):
serializer = OpenChannelSerializer(data=request.data)
if serializer.is_valid():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
peer_pubkey = serializer.validated_data['peer_pubkey']
connected = False
if Peers.objects.filter(pubkey=peer_pubkey, connected=True).exists():
@@ -2559,6 +2842,7 @@ def open_channel(request):
return Response({'error': 'Invalid request!'})
@api_view(['POST'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def close_channel(request):
serializer = CloseChannelSerializer(data=request.data)
if serializer.is_valid():
@@ -2573,7 +2857,7 @@ def close_channel(request):
channel_point.funding_txid_bytes = bytes.fromhex(funding_txid)
channel_point.funding_txid_str = funding_txid
channel_point.output_index = output_index
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
if serializer.validated_data['force']:
for response in stub.CloseChannel(ln.CloseChannelRequest(channel_point=channel_point, force=True)):
return Response({'message': 'Channel force closed! Closing TXID: ' + str(response.close_pending.txid[::-1].hex()) + ':' + str(response.close_pending.output_index)})
@@ -2592,11 +2876,12 @@ def close_channel(request):
return Response({'error': 'Invalid request!'})
@api_view(['POST'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def add_invoice(request):
serializer = AddInvoiceSerializer(data=request.data)
if serializer.is_valid() and serializer.validated_data['value'] >= 0:
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
response = stub.AddInvoice(ln.Invoice(value=serializer.validated_data['value']))
return Response({'message': 'Invoice created!', 'data':str(response.payment_request)})
except Exception as e:
@@ -2609,10 +2894,16 @@ def add_invoice(request):
return Response({'error': 'Invalid request!'})
@api_view(['GET'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def new_address(request):
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- response = stub.NewAddress(ln.NewAddressRequest(type=0))
+ stub = lnrpc.LightningStub(lnd_connect())
+ version = stub.GetInfo(ln.GetInfoRequest()).version
+ # Verify sufficient version to handle p2tr address creation
+ if float(version[:4]) >= 0.15:
+ response = stub.NewAddress(ln.NewAddressRequest(type=4))
+ else:
+ response = stub.NewAddress(ln.NewAddressRequest(type=0))
return Response({'message': 'Retrieved new deposit address!', 'data':str(response.address)})
except Exception as e:
error = str(e)
@@ -2622,13 +2913,14 @@ def new_address(request):
return Response({'error': 'Address creation failed! Error: ' + error_msg})
@api_view(['POST'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def update_alias(request):
serializer = UpdateAliasSerializer(data=request.data)
if serializer.is_valid():
peer_pubkey = serializer.validated_data['peer_pubkey']
if Channels.objects.filter(remote_pubkey=peer_pubkey).exists():
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
new_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=peer_pubkey)).node.alias
update_channels = Channels.objects.filter(remote_pubkey=peer_pubkey)
for channel in update_channels:
@@ -2648,9 +2940,10 @@ def update_alias(request):
return redirect('home')
@api_view(['GET'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def get_info(request):
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
response = stub.GetInfo(ln.GetInfoRequest())
target = {'identity_pubkey':response.identity_pubkey, 'alias':response.alias, 'num_active_channels':response.num_active_channels, 'num_peers':response.num_peers, 'block_height':response.block_height, 'block_hash':response.block_hash,'synced_to_chain':response.synced_to_chain,'testnet':response.testnet,'uris':[uri for uri in response.uris],'best_header_timestamp':response.best_header_timestamp,'version':response.version,'num_inactive_channels':response.num_inactive_channels,'chains':[{'chain':response.chains[i].chain,'network':response.chains[i].network} for i in range(0,len(response.chains))],'color':response.color,'synced_to_graph':response.synced_to_graph}
return Response({'message': 'success', 'data':target})
@@ -2662,11 +2955,21 @@ def get_info(request):
return Response({'error': 'Failed to call getinfo! Error: ' + error_msg})
@api_view(['GET'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def api_balances(request):
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- response = stub.WalletBalance(ln.WalletBalanceRequest())
- target = {'total_balance':response.confirmed_balance, 'confirmed_balance':response.confirmed_balance}
+ stub = lnrpc.LightningStub(lnd_connect())
+ balances = stub.WalletBalance(ln.WalletBalanceRequest())
+ pending_channels = stub.PendingChannels(ln.PendingChannelsRequest())
+ limbo_balance = pending_channels.total_limbo_balance
+ pending_open_balance = 0
+ if pending_channels.pending_open_channels:
+ target_resp = pending_channels.pending_open_channels
+ for i in range(0,len(target_resp)):
+ pending_open_balance += target_resp[i].channel.local_balance
+ channels = Channels.objects.filter(is_open=1)
+ offchain_balance = channels.aggregate(Sum('local_balance'))['local_balance__sum'] + channels.aggregate(Sum('pending_outbound'))['pending_outbound__sum'] + pending_open_balance + limbo_balance
+ target = {'total_balance':(balances.total_balance + offchain_balance),'offchain_balance':offchain_balance,'onchain_balance':balances.total_balance, 'confirmed_balance':balances.confirmed_balance, 'unconfirmed_balance':balances.unconfirmed_balance}
return Response({'message': 'success', 'data':target})
except Exception as e:
error = str(e)
@@ -2676,9 +2979,59 @@ def api_balances(request):
return Response({'error': 'Failed to get wallet balances! Error: ' + error_msg})
@api_view(['GET'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
+def api_income(request):
+ try:
+ stub = lnrpc.LightningStub(lnd_connect())
+ try:
+ days = int(request.GET.urlencode()[1:])
+ except:
+ days = None
+ day_filter = datetime.now() - timedelta(days=days) if days else None
+ node_info = stub.GetInfo(ln.GetInfoRequest())
+ payments = Payments.objects.filter(status=2).filter(creation_date__gte=day_filter) if day_filter else Payments.objects.filter(status=2)
+ onchain_txs = Onchain.objects.filter(time_stamp__gte=day_filter) if day_filter else Onchain.objects.all()
+ closures = Closures.objects.filter(close_height__gte=(node_info.block_height - (days*144))) if days else Closures.objects.all()
+ forwards = Forwards.objects.filter(forward_date__gte=day_filter) if day_filter else Forwards.objects.all()
+ forward_count = forwards.count()
+ forward_amount = 0 if forward_count == 0 else int(forwards.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
+ total_revenue = 0 if forward_count == 0 else int(forwards.aggregate(Sum('fee'))['fee__sum'])
+ invoices = Invoices.objects.filter(state=1, is_revenue=True).filter(settle_date__gte=day_filter) if day_filter else Invoices.objects.filter(state=1, is_revenue=True)
+ total_received = 0 if invoices.count() == 0 else int(invoices.aggregate(Sum('amt_paid'))['amt_paid__sum'])
+ total_revenue += total_received
+ total_revenue_ppm = 0 if forward_amount == 0 else int(total_revenue/(forward_amount/1000000))
+ total_sent = 0 if payments.count() == 0 else int(payments.aggregate(Sum('value'))['value__sum'])
+ total_fees = 0 if payments.count() == 0 else int(payments.aggregate(Sum('fee'))['fee__sum'])
+ total_fees_ppm = 0 if total_sent == 0 else int(total_fees/(total_sent/1000000))
+ onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
+ close_fees = closures.aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures.exists() else 0
+ onchain_costs += close_fees
+ profits = int(total_revenue-total_fees-onchain_costs)
+ target = {
+ 'forward_count': forward_count,
+ 'forward_amount': forward_amount,
+ 'total_revenue': total_revenue,
+ 'total_revenue_ppm': total_revenue_ppm,
+ 'total_fees': total_fees,
+ 'total_fees_ppm': total_fees_ppm,
+ 'onchain_costs': onchain_costs,
+ 'profits': profits,
+ 'profits_ppm': 0 if forward_amount == 0 else int(profits/(forward_amount/1000000)),
+ 'percent_cost': 0 if total_revenue == 0 else int(((total_fees+onchain_costs)/total_revenue)*100),
+ }
+ return Response({'message': 'success', 'data':target})
+ 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]
+ return Response({'error': 'Failed to get revenue stats! Error: ' + error_msg})
+
+@api_view(['GET'])
+@is_login_required(permission_classes([IsAuthenticated]), settings.LOGIN_REQUIRED)
def pending_channels(request):
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
response = stub.PendingChannels(ln.PendingChannelsRequest())
if response.pending_open_channels or response.pending_closing_channels or response.pending_force_closing_channels or response.waiting_close_channels or response.total_limbo_balance:
target = {}
diff --git a/htlc_stream.py b/htlc_stream.py
index 1a41044e..525d6b9b 100644
--- a/htlc_stream.py
+++ b/htlc_stream.py
@@ -2,7 +2,6 @@
from gui.lnd_deps import router_pb2 as lnr
from gui.lnd_deps import router_pb2_grpc as lnrouter
from gui.lnd_deps.lnd_connect import lnd_connect
-from lndg import settings
from os import environ
from time import sleep
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
@@ -11,7 +10,7 @@
def main():
try:
- connection = lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)
+ connection = lnd_connect()
routerstub = lnrouter.RouterStub(connection)
for response in routerstub.SubscribeHtlcEvents(lnr.SubscribeHtlcEventsRequest()):
if response.event_type == 3 and str(response.link_fail_event) != '':
@@ -26,7 +25,7 @@ def main():
amount = int(response.link_fail_event.info.outgoing_amt_msat/1000)
wire_failure = response.link_fail_event.wire_failure
failure_detail = response.link_fail_event.failure_detail
- missed_fee = 0 if out_chan == None else round(((amount/1000000) * out_chan.local_fee_rate) + (out_chan.local_base_fee/1000), 3)
+ missed_fee = (response.link_fail_event.info.incoming_amt_msat - response.link_fail_event.info.outgoing_amt_msat)/1000
FailedHTLCs(amount=amount, chan_id_in=in_chan_id, chan_id_out=out_chan_id, chan_in_alias=in_chan_alias, chan_out_alias=out_chan_alias, chan_out_liq=out_chan_liq, chan_out_pending=out_chan_pending, wire_failure=wire_failure, failure_detail=failure_detail, missed_fee=missed_fee).save()
except Exception as e:
print('Error while running failed HTLC stream: ' + str(e))
diff --git a/initialize.py b/initialize.py
index b2232b80..c8e433fa 100644
--- a/initialize.py
+++ b/initialize.py
@@ -5,7 +5,7 @@
from django.conf import settings
BASE_DIR = Path(__file__).resolve().parent
-def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted):
+def write_settings(node_ip, lnd_tls_path, lnd_macaroon_path, lnd_database_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted, nologinrequired):
#Generate a unique secret to be used for your django site
secret = secrets.token_urlsafe(64)
if whitenoise:
@@ -19,6 +19,14 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
""" % (csrftrusted)
else:
csrf = ''
+ if not nologinrequired:
+ api_login = """
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.IsAuthenticated',
+ ],"""
+ else:
+ print('WARNING: No password login option detected, LNDg will not require authentication...')
+ api_login = ''
settings_file = '''"""
Django settings for lndg project.
@@ -48,9 +56,12 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
ALLOWED_HOSTS = ['%s']
%s
-LND_DIR_PATH = '%s'
+LND_TLS_PATH = '%s'
+LND_MACAROON_PATH = '%s'
+LND_DATABASE_PATH = '%s'
LND_NETWORK = '%s'
LND_RPC_SERVER = '%s'
+LOGIN_REQUIRED = %s
# Application definition
@@ -129,10 +140,7 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
- 'PAGE_SIZE': 100,
- 'DEFAULT_PERMISSION_CLASSES': [
- 'rest_framework.permissions.IsAuthenticated',
- ],
+ 'PAGE_SIZE': 100,%s
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
@@ -157,12 +165,12 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'gui/static/')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-''' % (secret, debug, node_ip, csrf, lnd_dir_path, lnd_network, lnd_rpc_server, wnl)
+''' % (secret, debug, node_ip, csrf, lnd_tls_path, lnd_macaroon_path, lnd_database_path, lnd_network, lnd_rpc_server, not nologinrequired, wnl, api_login)
try:
f = open("lndg/settings.py", "x")
f.close()
except:
- print('A settings file may already exist, please double check.')
+ print('A settings file may already exist, skipping creation...')
return
try:
f = open("lndg/settings.py", "w")
@@ -232,7 +240,7 @@ def write_supervisord_settings(sduser):
f = open("/usr/local/etc/supervisord.conf", "x")
f.close()
except:
- print('A supervisord settings file may already exist, please double check.')
+ print('A supervisord settings file may already exist, skipping creation...')
return
try:
f = open("/usr/local/etc/supervisord.conf", "w")
@@ -245,7 +253,7 @@ def main():
help_msg = "LNDg Initializer"
parser = argparse.ArgumentParser(description = help_msg)
parser.add_argument('-ip', '--nodeip',help = 'IP that will be used to access the LNDg page', default='*')
- parser.add_argument('-dir', '--lnddir',help = 'LND Directory for tls cert and admin macaroon paths', default='~/.lnd')
+ parser.add_argument('-dir', '--lnddir',help = 'LND Directory for tls cert and admin macaroon paths', default=None)
parser.add_argument('-net', '--network', help = 'Network LND will run over', default='mainnet')
parser.add_argument('-server', '--rpcserver', help = 'Server address to use for rpc communications with LND', default='localhost:10009')
parser.add_argument('-sd', '--supervisord', help = 'Setup supervisord to run jobs/rebalancer background processes', action='store_true')
@@ -256,9 +264,16 @@ def main():
parser.add_argument('-u', '--adminuser', help = 'Setup a custom admin username', default='lndg-admin')
parser.add_argument('-pw', '--adminpw', help = 'Setup a custom admin password', default=None)
parser.add_argument('-csrf', '--csrftrusted', help = 'Set trusted CSRF origins', default=None)
+ parser.add_argument('-tls', '--tlscert', help = 'Set the path to the tls cert', default=None)
+ parser.add_argument('-mcrn', '--macaroon', help = 'Set the path to the macroon file', default=None)
+ parser.add_argument('-lnddb', '--lnddatabase', help = 'Set the path to the channel.db for monitoring', default=None)
+ parser.add_argument('-nologin', '--nologinrequired', help = 'By default, force all connections to be authenticated', action='store_true')
args = parser.parse_args()
+ if args.lnddir and (args.tlscert or args.macaroon or args.lnddatabase):
+ parser.error("You may not use tlscert or macaroon flags with the lnddir flag")
+ exit
node_ip = args.nodeip
- lnd_dir_path = args.lnddir
+ lnd_dir_path = args.lnddir if args.lnddir else '~/.lnd'
lnd_network = args.network
lnd_rpc_server = args.rpcserver
setup_supervisord = args.supervisord
@@ -269,10 +284,14 @@ def main():
adminuser = args.adminuser
adminpw = args.adminpw
csrftrusted = args.csrftrusted
+ nologinrequired = args.nologinrequired
+ lnd_tls_path = args.tlscert if args.tlscert else lnd_dir_path + '/tls.cert'
+ lnd_macaroon_path = args.macaroon if args.macaroon else lnd_dir_path + '/data/chain/bitcoin/' + lnd_network + '/admin.macaroon'
+ lnd_database_path = args.lnddatabase if args.lnddatabase else lnd_dir_path + '/data/graph/' + lnd_network + '/channel.db'
if docker:
setup_supervisord = True
whitenoise = True
- write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted)
+ write_settings(node_ip, lnd_tls_path, lnd_macaroon_path, lnd_database_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted, nologinrequired)
if setup_supervisord:
print('Supervisord setup requested...')
write_supervisord_settings(sduser)
diff --git a/jobs.py b/jobs.py
index 92fcaf89..77b7ec88 100644
--- a/jobs.py
+++ b/jobs.py
@@ -1,5 +1,5 @@
import django
-from django.db.models import Max, Min
+from django.db.models import Max
from datetime import datetime, timedelta
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
@@ -12,15 +12,15 @@
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
-from lndg.settings import LND_NETWORK
+from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels, Rebalancer
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
- if len(payment_data) > 0 and payment.payment_hash == payment_data[0].payment_hash:
+ #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
@@ -28,43 +28,14 @@ def update_payments(stub):
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()
- if payment.status == 2:
- for attempt in payment.htlcs:
- if attempt.status == 1:
- 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
- PaymentHops(payment_hash=new_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:
- if new_payment.chan_out is None:
- new_payment.chan_out = hop.chan_id
- new_payment.chan_out_alias = alias
- else:
- new_payment.chan_out = 'MPP'
- new_payment.chan_out_alias = 'MPP'
- if hop_count == total_hops and 5482373484 in hop.custom_records and new_payment.keysend_preimage is None:
- records = hop.custom_records
- message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
- new_payment.keysend_preimage = records[5482373484].hex()
- new_payment.message = message
- if hop_count == total_hops and hop.pub_key == self_pubkey and new_payment.rebal_chan is None:
- new_payment.rebal_chan = hop.chan_id
- new_payment.save()
- except:
+ except Exception as e:
#Error inserting, try to update instead
- update_payment(stub, payment, self_pubkey)
+ 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]
@@ -73,11 +44,13 @@ def update_payment(stub, payment, self_pubkey):
db_payment.fee = round(payment.fee_msat/1000, 3)
db_payment.status = payment.status
db_payment.index = payment.payment_index
- db_payment.save()
- if payment.status == 2:
+ if payment.status == 2 or payment.status == 1 or payment.status == 3:
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:
+ if attempt.status == 1 or attempt.status == 0 or attempt.status == 2:
hops = attempt.route.hops
hop_count = 0
cost_to = 0
@@ -89,9 +62,15 @@ def update_payment(stub, payment, self_pubkey):
except:
alias = ''
fee = hop.fee_msat/1000
- 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()
+ 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:
+ 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
@@ -105,11 +84,74 @@ def update_payment(stub, payment, self_pubkey):
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()
+ db_payment.save()
+ try:
+ adjust_ar_amt( payment, db_payment.rebal_chan )
+ except Exception as e:
+ print (f"{datetime.now().strftime('%c')} : Error adjusting AR Amount {payment=} {db_payment.rebal_chan=} : {str(e)=}")
+
+def adjust_ar_amt( payment, chan_id ):
+ if payment.status not in (2,3):
+ return
+ #skip rapid fire rebalances
+ last_rebalance_duration = Rebalancer.objects.filter(payment_hash=payment.payment_hash)[0].duration if Rebalancer.objects.filter(payment_hash=payment.payment_hash).exists() else 0
+ #print (f"{datetime.now().strftime('%c')} : DEBUG {last_rebalance_duration=} {payment.payment_hash=}")
+ if last_rebalance_duration <= 1 or payment.status not in (2,3):
+ print (f"{datetime.now().strftime('%c')} : Skipping Liquidity Estimation {last_rebalance_duration=} {payment.payment_hash=}")
+ return
+ #To be coverted to settings later
+ lower_limit = 69420
+ upper_limit = 2
+
+ if LocalSettings.objects.filter(key='AR-Target%').exists():
+ ar_target = float(LocalSettings.objects.filter(key='AR-Target%')[0].value)
+ else:
+ LocalSettings(key='AR-Target%', value='5').save()
+ ar_target = 5
+
+ #Adjust AR Target Amount, increase if success reduce if failed.
+ db_channel = Channels.objects.filter(chan_id = chan_id)[0] if Channels.objects.filter(chan_id = chan_id).exists() else None
+ if payment.status == 2 and chan_id is not None:
+ if db_channel is not None and payment.value_msat/1000 > 1000 :
+ new_ar_amount = int(min(max(db_channel.ar_amt_target * 1.11, payment.value_msat/1000), db_channel.capacity*ar_target*upper_limit/100))
+ if new_ar_amount > db_channel.ar_amt_target:
+ print (f"{datetime.now().strftime('%c')} : Increase AR Target Amount {chan_id=} {db_channel.alias=} {db_channel.ar_amt_target=} {new_ar_amount=}")
+ db_channel.ar_amt_target = new_ar_amount
+ db_channel.save()
+
+ if payment.status == 3:
+ estimated_liquidity = 0
+ attempt = None
+ for attempt in payment.htlcs:
+ total_hops=len(attempt.route.hops)
+ #Failure Codes https://github.com/lightningnetwork/lnd/blob/9f013f5058a7780075bca393acfa97aa0daec6a0/lnrpc/lightning.proto#L4200
+ if (attempt.failure.code in (1,2) and attempt.failure.failure_source_index == total_hops) or attempt.failure.code == 12:
+ #Failure 1,2 from last hop indicating liquidity available, failure 12 shows fees in sufficient but liquidity available
+ estimated_liquidity += attempt.route.total_amt
+ chan_id=attempt.route.hops[len(attempt.route.hops)-1].chan_id
+ print (f"{datetime.now().strftime('%c')} : Liquidity Estimation {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {chan_id=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}")
+
+ if estimated_liquidity == 0:
+ if attempt is not None:
+ #Could not estimate liquidity for valid attempts, reduce by half
+ estimated_liquidity = db_channel.ar_amt_target/2 if db_channel is not None else 0
+ print (f"{datetime.now().strftime('%c')} : Liquidity Estimation not possible, halving {attempt.attempt_id=} {attempt.status=} {attempt.failure.code=} {chan_id=} {attempt.route.total_amt=} {payment.value_msat/1000=} {estimated_liquidity=} {payment.payment_hash=}")
+ else:
+ #Mostly a case of NO ROUTE
+ print (f"{datetime.now().strftime('%c')} : Liquidity Estimation not performed {payment.payment_hash=} {payment.status=} {chan_id=} {estimated_liquidity=} {attempt=}")
+
+ if payment.value_msat/1000 >= lower_limit and estimated_liquidity <= payment.value_msat/1000 and estimated_liquidity > 0:
+ #Change AR amount. Ignore zero liquidity case which implies breakout from rapid fire AR
+ new_ar_amount = int(estimated_liquidity if estimated_liquidity > lower_limit else lower_limit)
+ if db_channel is not None and new_ar_amount < db_channel.ar_amt_target:
+ print (f"{datetime.now().strftime('%c')} : Decrease AR Target Amount {chan_id=} {db_channel.alias=} {db_channel.ar_amt_target=} {new_ar_amount=}")
+ db_channel.ar_amt_target = new_ar_amount
+ db_channel.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)
@@ -132,7 +174,7 @@ def update_invoice(stub, invoice, db_invoice):
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(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ 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
@@ -207,33 +249,47 @@ def update_channels(stub):
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
try:
chan_data = stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel.chan_id))
+ old_fee_rate = db_channel.local_fee_rate if db_channel.local_fee_rate is not None else 0
if chan_data.node1_pub == channel.remote_pubkey:
db_channel.local_base_fee = chan_data.node2_policy.fee_base_msat
db_channel.local_fee_rate = chan_data.node2_policy.fee_rate_milli_msat
db_channel.local_disabled = chan_data.node2_policy.disabled
db_channel.local_cltv = chan_data.node2_policy.time_lock_delta
+ db_channel.local_min_htlc_msat = chan_data.node2_policy.min_htlc
+ db_channel.local_max_htlc_msat = chan_data.node2_policy.max_htlc_msat
db_channel.remote_base_fee = chan_data.node1_policy.fee_base_msat
db_channel.remote_fee_rate = chan_data.node1_policy.fee_rate_milli_msat
db_channel.remote_disabled = chan_data.node1_policy.disabled
db_channel.remote_cltv = chan_data.node1_policy.time_lock_delta
+ db_channel.remote_min_htlc_msat = chan_data.node1_policy.min_htlc
+ db_channel.remote_max_htlc_msat = chan_data.node1_policy.max_htlc_msat
else:
db_channel.local_base_fee = chan_data.node1_policy.fee_base_msat
db_channel.local_fee_rate = chan_data.node1_policy.fee_rate_milli_msat
db_channel.local_disabled = chan_data.node1_policy.disabled
db_channel.local_cltv = chan_data.node1_policy.time_lock_delta
+ db_channel.local_min_htlc_msat = chan_data.node1_policy.min_htlc
+ db_channel.local_max_htlc_msat = chan_data.node1_policy.max_htlc_msat
db_channel.remote_base_fee = chan_data.node2_policy.fee_base_msat
db_channel.remote_fee_rate = chan_data.node2_policy.fee_rate_milli_msat
db_channel.remote_disabled = chan_data.node2_policy.disabled
db_channel.remote_cltv = chan_data.node2_policy.time_lock_delta
+ db_channel.remote_min_htlc_msat = chan_data.node2_policy.min_htlc
+ db_channel.remote_max_htlc_msat = chan_data.node2_policy.max_htlc_msat
except:
+ old_fee_rate = 0
db_channel.local_base_fee = 0
db_channel.local_fee_rate = 0
db_channel.local_disabled = False
db_channel.local_cltv = 40
+ db_channel.local_min_htlc_msat = 0
+ db_channel.local_max_htlc_msat = 0
db_channel.remote_base_fee = 0
db_channel.remote_fee_rate = 0
db_channel.remote_disabled = False
db_channel.remote_cltv = 40
+ db_channel.remote_min_htlc_msat = 0
+ db_channel.remote_max_htlc_msat = 0
db_channel.local_balance = channel.local_balance
db_channel.remote_balance = channel.remote_balance
db_channel.unsettled_balance = channel.unsettled_balance
@@ -285,7 +341,7 @@ def update_channels(stub):
db_channel.local_fee_rate = fee_rate
db_channel.local_cltv = cltv
db_channel.fees_updated = datetime.now()
- if pending_channel.auto_rebalance:
+ 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
@@ -295,10 +351,15 @@ def update_channels(stub):
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:
+ if pending_channel.auto_fees is not None:
db_channel.auto_fees = pending_channel.auto_fees
pending_channel.delete()
db_channel.save()
+ if db_channel.local_fee_rate != old_fee_rate:
+ 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
+ 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()
+
counter += 1
chan_list.append(channel.chan_id)
records = Channels.objects.filter(is_open=True).count()
@@ -361,12 +422,12 @@ def network_links():
return network_links
def get_tx_fees(txid):
- base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
+ 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('Error getting closure fees for ', txid, ':', str(e))
+ print(f"{datetime.now().strftime('%c')} : Error getting closure fees {txid=} {str(e)=}")
fee = 0
return fee
@@ -381,7 +442,7 @@ def update_closures(stub):
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 == 1 else 0
+ 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()
@@ -395,9 +456,8 @@ def update_closures(stub):
if resolution.resolution_type != 2:
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()
- if channel:
- channel.closing_costs = closing_costs
- channel.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()
@@ -410,20 +470,29 @@ def reconnect_peers(stub):
print (f"{datetime.now().strftime('%c')} : Reconnecting {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=}")
- stub.DisconnectPeer(ln.DisconnectPeerRequest(pub_key=inactive_peer))
- peer.connected = False
- peer.save()
+ try:
+ response = stub.DisconnectPeer(ln.DisconnectPeerRequest(pub_key=inactive_peer))
+ print (f"{datetime.now().strftime('%c')} : .... Status Disconnect {peer.alias=} {inactive_peer=} {response=}")
+ peer.connected = False
+ peer.save()
+ except Exception as e:
+ print (f"{datetime.now().strftime('%c')} : .... Error disconnecting {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:
- print (f"{datetime.now().strftime('%c')} : ... Unable to find node info on graph, using last known value {peer.alias=} {peer.pubkey=} {peer.address=}")
+ except Exception as e:
+ print (f"{datetime.now().strftime('%c')} : ... Unable to find node info on graph, using last known value {peer.alias=} {peer.pubkey=} {peer.address=} {str(e)=}")
host = peer.address
- address = ln.LightningAddress(pubkey=inactive_peer, host=host)
+ #address = ln.LightningAddress(pubkey=inactive_peer, host=host)
print (f"{datetime.now().strftime('%c')} : ... Attempting connection to {peer.alias=} {inactive_peer=} {host=}")
try:
- response = stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=False, timeout=5))
- print (f"{datetime.now().strftime('%c')} : .... Status {peer.alias=} {inactive_peer=} {response=}")
+ #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))
+ #response = stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=False, timeout=5))
+ #print (f"{datetime.now().strftime('%c')} : .... Status {peer.alias=} {inactive_peer=} {response=}")
except Exception as e:
error = str(e)
details_index = error.find('details =') + 11
@@ -462,7 +531,7 @@ def clean_payments(stub):
finally:
payment.cleaned = True
payment.save()
- print (f"{datetime.now().strftime('%c')} : Cleaned {payment.index=} {payment.status=} {payment.cleaned=} {payment.payment_hash=}")
+ #print (f"{datetime.now().strftime('%c')} : Cleaned {payment.index=} {payment.status=} {payment.cleaned=} {payment.payment_hash=}")
def auto_fees(stub):
if LocalSettings.objects.filter(key='AF-Enabled').exists():
@@ -501,7 +570,12 @@ def auto_fees(stub):
else:
LocalSettings(key='AF-FailedHTLCs', value='25').save()
failed_htlc_limit = 25
- channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > 86400, axis=1)
+ if LocalSettings.objects.filter(key='AF-UpdateHours').exists():
+ update_hours = int(LocalSettings.objects.filter(key='AF-UpdateHours')[0].value)
+ else:
+ LocalSettings(key='AF-UpdateHours', value='24').save()
+ update_hours = 24
+ channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > (update_hours*3600), axis=1)
channels_df = channels_df[channels_df['eligible']==True]
if channels_df.shape[0] > 0:
failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(timestamp__gte=filter_1day).order_by('-id').values())
@@ -565,11 +639,12 @@ def auto_fees(stub):
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='Fee Rate', old_value=target_channel['local_fee_rate'], new_value=target_channel['new_rate']).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()
def main():
+ #print (f"{datetime.now().strftime('%c')} : Entering Jobs")
try:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
#Update data
update_peers(stub)
update_channels(stub)
@@ -582,7 +657,7 @@ def main():
clean_payments(stub)
auto_fees(stub)
except Exception as e:
- print('Error processing background data: ' + str(e))
-
+ print (f"{datetime.now().strftime('%c')} : Error processing background data: {str(e)=}")
+ #print (f"{datetime.now().strftime('%c')} : Exit Jobs")
if __name__ == '__main__':
main()
diff --git a/keysend.py b/keysend.py
index defc22ef..febc9aeb 100644
--- a/keysend.py
+++ b/keysend.py
@@ -1,6 +1,5 @@
import secrets, time, struct
from hashlib import sha256
-from lndg import settings
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps import router_pb2 as lnr
@@ -12,7 +11,7 @@
def keysend(target_pubkey, msg, amount, fee_limit, timeout, sign):
#Construct and send
try:
- routerstub = lnrouter.RouterStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ routerstub = lnrouter.RouterStub(lnd_connect())
secret = secrets.token_bytes(32)
hashed_secret = sha256(secret).hexdigest()
custom_records = [(5482373484, secret),]
@@ -20,8 +19,8 @@ def keysend(target_pubkey, msg, amount, fee_limit, timeout, sign):
if len(msg) > 0:
custom_records.append((34349334, bytes.fromhex(msg.encode('utf-8').hex())))
if sign == True:
- stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
- signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
+ stub = lnrpc.LightningStub(lnd_connect())
+ signerstub = lnsigner.SignerStub(lnd_connect())
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
timestamp = struct.pack(">i", int(time.time()))
signature = signerstub.SignMessage(lns.SignMessageReq(msg=(bytes.fromhex(self_pubkey)+bytes.fromhex(target_pubkey)+timestamp+bytes.fromhex(msg.encode('utf-8').hex())), key_loc=lns.KeyLocator(key_family=6, key_index=0))).signature
diff --git a/lndg/urls.py b/lndg/urls.py
index e3b8a13a..77164087 100644
--- a/lndg/urls.py
+++ b/lndg/urls.py
@@ -13,9 +13,11 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
-from django.contrib import admin
from django.urls import path, include
+from django.views.generic.base import RedirectView
+from django.contrib.staticfiles.storage import staticfiles_storage
urlpatterns = [
+ path('favicon.ico', RedirectView.as_view(url=staticfiles_storage.url("favicon.ico"))),
path('', include('gui.urls')),
-]
+]
\ No newline at end of file
diff --git a/nginx.sh b/nginx.sh
index cf158be2..100410bf 100644
--- a/nginx.sh
+++ b/nginx.sh
@@ -87,7 +87,8 @@ EOF
function setup_nginx() {
cat << EOF > /etc/nginx/sites-enabled/lndg
-user $INSTALL_USER
+# the below setting can sometimes help resolve permission issues
+# user $INSTALL_USER
upstream django {
server unix://$HOME_DIR/lndg/lndg.sock; # for a file socket
diff --git a/rebalancer.py b/rebalancer.py
index 3648fc11..1da49980 100644
--- a/rebalancer.py
+++ b/rebalancer.py
@@ -6,7 +6,6 @@
from gui.lnd_deps import router_pb2 as lnr
from gui.lnd_deps import router_pb2_grpc as lnrouter
from gui.lnd_deps.lnd_connect import lnd_connect
-from lndg import settings
from os import environ
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
@@ -29,7 +28,7 @@ def run_rebalancer(rebalance):
rebalance.start = datetime.now()
try:
#Open connection with lnd via grpc
- connection = lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)
+ connection = lnd_connect()
stub = lnrpc.LightningStub(connection)
routerstub = lnrouter.RouterStub(connection)
chan_ids = json.loads(rebalance.outgoing_chan_ids)
@@ -78,14 +77,27 @@ def run_rebalancer(rebalance):
finally:
rebalance.stop = datetime.now()
rebalance.save()
- if rebalance.status == 2:
+ original_alias = rebalance.target_alias
+ inc=1.21
+ dec=2
+ if rebalance.status ==2:
update_channels(stub, rebalance.last_hop_pubkey, successful_out)
auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target'))
inbound_cans = auto_rebalance_channels.filter(remote_pubkey=rebalance.last_hop_pubkey).filter(auto_rebalance=True, inbound_can__gte=1)
- outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
+ outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).exclude(remote_pubkey=rebalance.last_hop_pubkey).values_list('chan_id', flat=True))
if len(inbound_cans) > 0 and len(outbound_cans) > 0:
- next_rebalance = Rebalancer(value=rebalance.value, fee_limit=rebalance.fee_limit, outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=rebalance.target_alias, duration=1)
+ next_rebalance = Rebalancer(value=int(rebalance.value*inc), fee_limit=round(rebalance.fee_limit*inc, 3), outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=original_alias, duration=1)
next_rebalance.save()
+ print (f"{datetime.now().strftime('%c')} : RapidFire up {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}")
+ else:
+ next_rebalance = None
+ elif rebalance.status > 2 and rebalance.duration <= 1 and rebalance.value > 69420:
+ #Previous Rapidfire with increased value failed, try with lower value up to 69420.
+ inbound_cans = auto_rebalance_channels.filter(remote_pubkey=rebalance.last_hop_pubkey).filter(auto_rebalance=True, inbound_can__gte=1)
+ if len(inbound_cans) > 0 and len(outbound_cans) > 0:
+ next_rebalance = Rebalancer(value=int(rebalance.value/dec), fee_limit=round(rebalance.fee_limit/dec, 3), outgoing_chan_ids=str(outbound_cans).replace('\'', ''), last_hop_pubkey=rebalance.last_hop_pubkey, target_alias=original_alias, duration=1)
+ next_rebalance.save()
+ print (f"{datetime.now().strftime('%c')} : RapidFire Down {next_rebalance.target_alias=} {next_rebalance.value=} {rebalance.value=}")
else:
next_rebalance = None
else:
@@ -121,7 +133,7 @@ def auto_schedule():
if not LocalSettings.objects.filter(key='AR-Inbound%').exists():
LocalSettings(key='AR-Inbound%', value='100').save()
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
- inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1)
+ inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1).order_by('-remote_balance')
if len(inbound_cans) > 0 and len(outbound_cans) > 0:
if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists():
max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value)
@@ -194,7 +206,11 @@ def auto_enable():
for peer_channel in lookup_channels.filter(chan_id__in=chan_list):
#print('Processing: ', peer_channel.alias, ' : ', peer_channel.chan_id, ' : ', oapD, " : ", iapD, ' : ', outbound_percent, ' : ', inbound_percent)
- if oapD > (iapD*1.10) and outbound_percent > 75:
+ if peer_channel.ar_out_target == 100 and peer_channel.auto_rebalance == True:
+ #Special Case for LOOP, Wos, etc. Always Auto Rebalance if enabled to keep outbound full.
+ print (f"{datetime.now().strftime('%c')} : Pass {peer_channel.alias=} {peer_channel.chan_id=} {peer_channel.ar_out_target=} {peer_channel.auto_rebalance=}")
+ pass
+ elif oapD > (iapD*1.10) and outbound_percent > 75:
#print('Case 1: Pass')
pass
elif oapD > (iapD*1.10) and inbound_percent > 75 and peer_channel.auto_rebalance == False:
diff --git a/systemd.md b/systemd.md
index fa89ebca..b8f2dc2b 100644
--- a/systemd.md
+++ b/systemd.md
@@ -93,7 +93,7 @@ User=
Group=
ExecStart=/usr/bin/bash /home//lndg/htlc_stream.sh
StandardError=append:/var/log/lnd_htlc_stream_error.log
-Restart=on-failure
+Restart=always
RestartSec=60s
[Install]
WantedBy=multi-user.target
diff --git a/systemd.sh b/systemd.sh
index a8fd6a9e..51c0da51 100644
--- a/systemd.sh
+++ b/systemd.sh
@@ -83,7 +83,7 @@ User=$INSTALL_USER
Group=$INSTALL_USER
ExecStart=/usr/bin/bash $HOME_DIR/lndg/htlc_stream.sh
StandardError=append:/var/log/lnd_htlc_stream_error.log
-Restart=on-failure
+Restart=always
RestartSec=60s
[Install]
WantedBy=multi-user.target
|