From 479a563e1848a5f5e6b285458c0f68ed56a2b5e0 Mon Sep 17 00:00:00 2001
From: cryptosharks131 <38626122+cryptosharks131@users.noreply.github.com>
Date: Mon, 28 Mar 2022 15:14:02 -0400
Subject: [PATCH] v1.0.5 (#56)
---
gui/forms.py | 1 +
gui/migrations/0023_repair_closures.py | 23 ++
gui/migrations/0024_autofees.py | 26 +++
.../0025_alter_closures_unique_together.py | 54 +++++
gui/models.py | 19 +-
gui/serializers.py | 14 +-
gui/templates/advanced.html | 56 ++++-
gui/templates/autofees.html | 36 +++
gui/templates/autopilot.html | 2 +-
gui/templates/balances.html | 6 +-
gui/templates/base.html | 2 +-
gui/templates/channel.html | 38 ++-
gui/templates/channels.html | 10 +-
gui/templates/closures.html | 2 +-
gui/templates/fee_rates.html | 21 +-
gui/templates/home.html | 23 +-
gui/templates/payments.html | 12 +-
gui/urls.py | 3 +
gui/views.py | 219 ++++++++++++++----
jobs.py | 86 ++++++-
rebalancer.py | 18 +-
21 files changed, 577 insertions(+), 94 deletions(-)
create mode 100644 gui/migrations/0023_repair_closures.py
create mode 100644 gui/migrations/0024_autofees.py
create mode 100644 gui/migrations/0025_alter_closures_unique_together.py
create mode 100644 gui/templates/autofees.html
diff --git a/gui/forms.py b/gui/forms.py
index e5d4d450..16c7594c 100644
--- a/gui/forms.py
+++ b/gui/forms.py
@@ -81,6 +81,7 @@ class AutoRebalanceForm(forms.Form):
(5, 'ar_enabled'),
(6, 'ar_max_cost'),
(7, 'channel_state'),
+ (8, 'auto_fees'),
]
class UpdateChannel(forms.Form):
diff --git a/gui/migrations/0023_repair_closures.py b/gui/migrations/0023_repair_closures.py
new file mode 100644
index 00000000..dfe387ef
--- /dev/null
+++ b/gui/migrations/0023_repair_closures.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.7 on 2022-03-11 23:31
+
+from django.db import migrations, models
+import django.utils.timezone
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0022_auto_20220227_2235'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='channels',
+ name='fees_updated',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name='channels',
+ name='auto_fees',
+ field=models.BooleanField(default=False),
+ ),
+ ]
\ No newline at end of file
diff --git a/gui/migrations/0024_autofees.py b/gui/migrations/0024_autofees.py
new file mode 100644
index 00000000..553be92e
--- /dev/null
+++ b/gui/migrations/0024_autofees.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.2.7 on 2022-03-16 17:12
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0023_repair_closures'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Autofees',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
+ ('chan_id', models.CharField(max_length=20)),
+ ('peer_alias', models.CharField(max_length=32)),
+ ('setting', models.CharField(max_length=20)),
+ ('old_value', models.IntegerField()),
+ ('new_value', models.IntegerField()),
+ ],
+ ),
+ ]
diff --git a/gui/migrations/0025_alter_closures_unique_together.py b/gui/migrations/0025_alter_closures_unique_together.py
new file mode 100644
index 00000000..8317d7ad
--- /dev/null
+++ b/gui/migrations/0025_alter_closures_unique_together.py
@@ -0,0 +1,54 @@
+# Generated by Django 3.2.7 on 2022-03-23 14:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0024_autofees'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Resolutions',
+ ),
+ migrations.DeleteModel(
+ name='Closures',
+ ),
+ migrations.CreateModel(
+ name='Resolutions',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('chan_id', models.CharField(max_length=20)),
+ ('resolution_type', models.IntegerField()),
+ ('outcome', models.IntegerField()),
+ ('outpoint_tx', models.CharField(max_length=64)),
+ ('outpoint_index', models.IntegerField()),
+ ('amount_sat', models.BigIntegerField()),
+ ('sweep_txid', models.CharField(max_length=64)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Closures',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('chan_id', models.CharField(max_length=20)),
+ ('funding_txid', models.CharField(max_length=64)),
+ ('funding_index', models.IntegerField()),
+ ('closing_tx', models.CharField(max_length=64)),
+ ('remote_pubkey', models.CharField(max_length=66)),
+ ('capacity', models.BigIntegerField()),
+ ('close_height', models.IntegerField()),
+ ('settled_balance', models.BigIntegerField()),
+ ('time_locked_balance', models.BigIntegerField()),
+ ('close_type', models.IntegerField()),
+ ('open_initiator', models.IntegerField()),
+ ('close_initiator', models.IntegerField()),
+ ('resolution_count', models.IntegerField()),
+ ],
+ options={
+ 'unique_together': {('funding_txid', 'funding_index')},
+ },
+ ),
+ ]
diff --git a/gui/models.py b/gui/models.py
index d6e5b703..2ef14957 100644
--- a/gui/models.py
+++ b/gui/models.py
@@ -96,6 +96,8 @@ class Channels(models.Model):
ar_in_target = models.IntegerField(default=100)
ar_out_target = models.IntegerField()
ar_max_cost = models.IntegerField()
+ fees_updated = models.DateTimeField(default=timezone.now)
+ auto_fees = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if not self.ar_out_target:
@@ -169,7 +171,9 @@ class Meta:
app_label = 'gui'
class Closures(models.Model):
- chan_id = models.CharField(max_length=20, primary_key=True)
+ chan_id = models.CharField(max_length=20)
+ funding_txid = models.CharField(max_length=64)
+ funding_index = models.IntegerField()
closing_tx = models.CharField(max_length=64)
remote_pubkey = models.CharField(max_length=66)
capacity = models.BigIntegerField()
@@ -182,9 +186,10 @@ class Closures(models.Model):
resolution_count = models.IntegerField()
class Meta:
app_label = 'gui'
+ unique_together = (('funding_txid', 'funding_index'),)
class Resolutions(models.Model):
- chan_id = models.ForeignKey('Closures', on_delete=models.CASCADE)
+ chan_id = models.CharField(max_length=20)
resolution_type = models.IntegerField()
outcome = models.IntegerField()
outpoint_tx = models.CharField(max_length=64)
@@ -222,6 +227,16 @@ class Meta:
app_label = 'gui'
class Autopilot(models.Model):
+ timestamp = models.DateTimeField(default=timezone.now)
+ chan_id = models.CharField(max_length=20)
+ peer_alias = models.CharField(max_length=32)
+ setting = models.CharField(max_length=20)
+ old_value = models.IntegerField()
+ new_value = models.IntegerField()
+ class Meta:
+ app_label = 'gui'
+
+class Autofees(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
chan_id = models.CharField(max_length=20)
peer_alias = models.CharField(max_length=32)
diff --git a/gui/serializers.py b/gui/serializers.py
index b5ea05e3..c224e63f 100644
--- a/gui/serializers.py
+++ b/gui/serializers.py
@@ -1,6 +1,6 @@
from rest_framework import serializers
from rest_framework.relations import PrimaryKeyRelatedField
-from .models import LocalSettings, Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, Peers, Onchain, PendingHTLCs, FailedHTLCs
+from .models import LocalSettings, Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, Peers, Onchain, PendingHTLCs, FailedHTLCs, Closures, Resolutions
##FUTURE UPDATE 'exclude' TO 'fields'
@@ -89,6 +89,18 @@ class Meta:
model = Onchain
exclude = []
+class ClosuresSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.ReadOnlyField()
+ class Meta:
+ model = Closures
+ exclude = []
+
+class ResolutionsSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.ReadOnlyField()
+ class Meta:
+ model = Resolutions
+ exclude = []
+
class PaymentHopsSerializer(serializers.HyperlinkedModelSerializer):
payment_hash = PrimaryKeyRelatedField(read_only=True)
class Meta:
diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html
index 7b235639..6ff001b9 100644
--- a/gui/templates/advanced.html
+++ b/gui/templates/advanced.html
@@ -7,6 +7,60 @@
Advanced Channel Settings
+
+ |
+
+
+ |
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+ |
+
Channel ID |
Peer Alias |
@@ -142,7 +196,7 @@ Update Local Settings
{% elif settings.key == 'LND-RetentionDays' %}
- {% elif settings.key|slice:":3" == 'AR-' %}
+ {% elif settings.key|slice:":3" == 'AR-' or settings.key|slice:":3" == 'AF-' %}
{% else %}
diff --git a/gui/templates/autofees.html b/gui/templates/autofees.html
new file mode 100644
index 00000000..43464197
--- /dev/null
+++ b/gui/templates/autofees.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% block title %} {{ block.super }} - Autofees{% endblock %}
+{% block content %}
+{% load humanize %}
+{% if autofees %}
+
+
Autofees Logs
+
+
+ Timestamp |
+ Channel ID |
+ Peer Alias |
+ Setting |
+ Old Value |
+ New Value |
+
+ {% 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.new_value }} |
+
+ {% endfor %}
+
+
+{% endif %}
+{% if not autofees %}
+
+
No autofees 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
diff --git a/gui/templates/autopilot.html b/gui/templates/autopilot.html
index a6b20cec..80924151 100644
--- a/gui/templates/autopilot.html
+++ b/gui/templates/autopilot.html
@@ -16,7 +16,7 @@ Autopilot Logs
{% for log in autopilot %}
- {{ log.timestamp|naturaltime }} |
+ {{ log.timestamp|naturaltime }} |
{{ log.chan_id }} |
{% if log.peer_alias == '' %}---{% else %}{{ log.peer_alias }}{% endif %} |
{{ log.setting }} |
diff --git a/gui/templates/balances.html b/gui/templates/balances.html
index 2c34fa63..8d9ad30d 100644
--- a/gui/templates/balances.html
+++ b/gui/templates/balances.html
@@ -37,9 +37,9 @@ Transactions
{% for transaction in transactions %}
{{ transaction.tx_hash }} |
- {{ transaction.amount }} |
- {% if transaction.block_height == 0 %}{{ transaction.block_height }}{% else %}{{ transaction.block_height }}{% endif %} |
- {{ transaction.fee }} |
+ {{ transaction.amount|intcomma }} |
+ {% if transaction.block_height == 0 %}---{% else %}{{ transaction.block_height|intcomma }}{% endif %} |
+ {{ transaction.fee|intcomma }} |
{{ transaction.label }} |
{% endfor %}
diff --git a/gui/templates/base.html b/gui/templates/base.html
index 05c3f196..d6421e2d 100644
--- a/gui/templates/base.html
+++ b/gui/templates/base.html
@@ -28,7 +28,7 @@
diff --git a/gui/templates/channel.html b/gui/templates/channel.html
index 679fa2f9..6485b3b9 100644
--- a/gui/templates/channel.html
+++ b/gui/templates/channel.html
@@ -25,15 +25,17 @@ Lifetime Rebalanced Out: {{ channel.amt_rebal_out|intcomma }} ({{ channel.r
Profitability
- 1-Day Revenue: {{ channel.revenue_1day|intcomma }} | Costs: {{ channel.costs_1day|intcomma }} | Profits: {{ channel.profits_1day|intcomma }} [{{ channel.profits_vol_1day }}] | Assisted Revenues: {{ channel.revenue_assist_1day|intcomma }} | APY: {{ channel.apy_1day }}%
- 7-Day Revenue: {{ channel.revenue_7day|intcomma }} | Costs: {{ channel.costs_7day|intcomma }} | Profits: {{ channel.profits_7day|intcomma }} [{{ channel.profits_vol_7day }}] | Assisted Revenues: {{ channel.revenue_assist_7day|intcomma }} | APY: {{ channel.apy_7day }}%
- 30-Day Revenue: {{ channel.revenue_30day|intcomma }} | Costs: {{ channel.costs_30day|intcomma }} | Profits: {{ channel.profits_30day|intcomma }} [{{ channel.profits_vol_30day }}] | Assisted Revenues: {{ channel.revenue_assist_30day|intcomma }} | APY: {{ channel.apy_30day }}%
- Lifetime Revenue: {{ channel.revenue|intcomma }} | Costs: {{ channel.costs|intcomma }} | Profits: {{ channel.profits|intcomma }} [{{ channel.profits_vol }}] | Assisted Revenues: {{ channel.revenue_assist|intcomma }} | APY: {{ channel.apy }}%
+ 1-Day Revenue: {{ channel.revenue_1day|intcomma }} | Costs: {{ channel.costs_1day|intcomma }} | Profits: {{ channel.profits_1day|intcomma }} [{{ channel.profits_vol_1day }}] | Assisted Revenues: {{ channel.revenue_assist_1day|intcomma }} | APY: {{ channel.apy_1day }}% | iAPY: {{ channel.assisted_apy_1day }}% | CV: {{ channel.cv_1day }}%
+ 7-Day Revenue: {{ channel.revenue_7day|intcomma }} | Costs: {{ channel.costs_7day|intcomma }} | Profits: {{ channel.profits_7day|intcomma }} [{{ channel.profits_vol_7day }}] | Assisted Revenues: {{ channel.revenue_assist_7day|intcomma }} | APY: {{ channel.apy_7day }}% | iAPY: {{ channel.assisted_apy_7day }}% | CV: {{ channel.cv_7day }}%
+ 30-Day Revenue: {{ channel.revenue_30day|intcomma }} | Costs: {{ channel.costs_30day|intcomma }} | Profits: {{ channel.profits_30day|intcomma }} [{{ channel.profits_vol_30day }}] | Assisted Revenues: {{ channel.revenue_assist_30day|intcomma }} | APY: {{ channel.apy_30day }}% | iAPY: {{ channel.assisted_apy_30day }}% | CV: {{ channel.cv_30day }}%
+ Lifetime Revenue: {{ channel.revenue|intcomma }} | Costs: {{ channel.costs|intcomma }} | Profits: {{ channel.profits|intcomma }} [{{ channel.profits_vol }}] | Assisted Revenues: {{ channel.revenue_assist|intcomma }} | APY: {{ channel.apy }}% | iAPY: {{ channel.assisted_apy }}% | CV: {{ channel.cv }}%
Channel Settings
+ AF |
+ Updated |
7Day Flow |
Out Rate |
Rebal Rate |
@@ -54,6 +56,16 @@ Channel Settings
Active |
+
+
+ |
+ {{ channel.fees_updated|naturaltime }} |
{% if channel.net_routed_7day > 0 %}OUT{% elif channel.net_routed_7day < 0 %}IN{% else %}---{% endif %}{% if channel.net_routed_7day != 0 %} | {{ channel.net_routed_7day }}{% endif %} |
{{ channel.out_rate }} |
{{ channel.rebal_ppm }} |
@@ -252,13 +264,14 @@
Timestamp |
Hash |
- Value |
- Fee Paid |
- Status |
+ Value |
+ Fee Paid |
+ PPM Paid |
+ Status |
Chan Out Alias |
Chan Out ID |
- Route |
- Keysend |
+ Route |
+ Keysend |
{% for payment in payments %}
@@ -266,6 +279,7 @@
{{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
+ {{ payment.ppm|intcomma }} |
{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
@@ -318,6 +332,7 @@
Chan In Alias |
Chan Out Alias |
Forward Amount |
+ Actual Outbound |
Potential Fee |
HTLC Failure |
Failure Detail |
@@ -325,11 +340,12 @@
{% for failed_htlc in failed_htlcs %}
{{ failed_htlc.timestamp|naturaltime }} |
- {{ failed_htlc.chan_id_in }} |
- {{ failed_htlc.chan_id_out }} |
+ {{ 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 %} |
{{ 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 %} |
diff --git a/gui/templates/channels.html b/gui/templates/channels.html
index 753575ea..e5c228cb 100644
--- a/gui/templates/channels.html
+++ b/gui/templates/channels.html
@@ -20,11 +20,11 @@ Channel Performance
Peer Alias |
Capacity |
Outbound Flow |
- APY |
+ APY | CV |
Out [Profit] | In |
Inbound Flow |
Outbound Flow |
- APY |
+ APY | CV |
Out [Profit] | In |
Inbound Flow |
Updates |
@@ -34,13 +34,13 @@ Channel Performance
{{ channel.chan_id }} |
{% if channel.alias == '' %}{{ channel.remote_pubkey|slice:":12" }}{% else %}{{ channel.alias }}{% endif %} |
- {{ channel.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.apy_7day }}% | {{ channel.cv_7day }}% |
{{ channel.revenue_7day|intcomma }} [{{ channel.profits_7day|intcomma }}] | {{ channel.revenue_assist_7day|intcomma }} |
{{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }}) | {{ channel.amt_rebal_out_7day|intcomma }} M ({{ channel.rebal_out_7day }}) |
{{ channel.amt_routed_out_30day|intcomma }} M ({{ channel.routed_out_30day }}) | {{ channel.amt_rebal_in_30day|intcomma }} M ({{ channel.rebal_in_30day }}) |
- {{ channel.apy_30day }}% |
+ {{ channel.apy_30day }}% | {{ channel.cv_30day }}% |
{{ channel.revenue_30day|intcomma }} [{{ channel.profits_30day|intcomma }}] | {{ channel.revenue_assist_30day|intcomma }} |
{{ channel.amt_routed_in_30day|intcomma }} M ({{ channel.routed_in_30day }}) | {{ channel.amt_rebal_out_30day|intcomma }} M ({{ channel.rebal_out_30day }}) |
{{ channel.updates }}% |
diff --git a/gui/templates/closures.html b/gui/templates/closures.html
index adec9a8c..402d83b8 100644
--- a/gui/templates/closures.html
+++ b/gui/templates/closures.html
@@ -23,7 +23,7 @@ Closures
{% if closure.chan_id == '0' %}---{% else %}{{ closure.chan_id }}{% endif %} |
{% if closure.alias == '' %}{{ closure.remote_pubkey|slice:":12" }}{% else %}{{ closure.alias }}{% endif %} |
- {{ closure.capacity|intcomma }} |
+ {{ closure.capacity|intcomma }} |
{% if closure.closing_tx == '0000000000000000000000000000000000000000000000000000000000000000' %}---{% else %}{{ closure.closing_tx }}{% endif %} |
{{ closure.settled_balance|intcomma }} |
{{ closure.time_locked_balance|intcomma }} |
diff --git a/gui/templates/fee_rates.html b/gui/templates/fee_rates.html
index fa2b9801..20665bef 100644
--- a/gui/templates/fee_rates.html
+++ b/gui/templates/fee_rates.html
@@ -25,6 +25,15 @@ Suggested Fee Rates
Max Cost |
iRate |
iBase |
+ Updated |
+
+
+ |
{% for channel in channels %}
@@ -49,9 +58,19 @@ Suggested Fee Rates
{{ channel.local_base_fee|intcomma }} |
- {{ channel.ar_max_cost }}% |
+ {{ channel.ar_max_cost }}% |
{{ channel.remote_fee_rate|intcomma }} |
{{ channel.remote_base_fee|intcomma }} |
+ {{ channel.fees_updated|naturaltime }} |
+
+
+ |
{% endfor %}
diff --git a/gui/templates/home.html b/gui/templates/home.html
index a3d6ecc9..865ea030 100644
--- a/gui/templates/home.html
+++ b/gui/templates/home.html
@@ -4,7 +4,8 @@
{% load humanize %}
-
Capacity: {{ capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | Peers: {{ node_info.num_peers }} | DB Size: {{ db_size }} GB
+
Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | Peers: {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %}
+ {% if total_private > 0 %}
Private Capacity: {{ private_capacity|intcomma }} | Locked Liquidity: {{ private_outbound|intcomma }} | Active Private Channels: {{ active_private }} / {{ total_private }}
{% endif %}
Public Address: {% for info in node_info.uris %}{{ info }} | {% endfor %}
Lnd sync: {{ node_info.synced_to_graph }} | chain sync: {{ node_info.synced_to_chain }} | {% for info in node_info.chains %}{{ info }}{% endfor %} | {{ node_info.block_height }} | {{ node_info.block_hash }}
@@ -22,9 +23,11 @@
Lifetime Routed: {{ total_forwards|intcomma }} | Value: {{ total_value_forwa
7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound|intcomma }} [{{ profit_per_outbound_real|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%
-
Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%
-
Balance In Limbo: {{ limbo_balance|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }} | Pending HTLCs: {{ pending_htlc_count }}
-
+
Total Inbound Liquidity: {{ sum_inbound|intcomma }} | Outbound Liquidity: {{ sum_outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%
+
Active Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }}
+
Inactive Inbound Liquidity: {{ inactive_inbound|intcomma }} | Outbound Liquidity: {{ inactive_outbound|intcomma }} | Unsettled Liquidity: {{ inactive_unsettled|intcomma }}
+
Balance In Limbo: {{ limbo_balance|intcomma }} | Total Unsettled Liquidity: {{ total_unsettled|intcomma }} | Pending HTLCs: {{ pending_htlc_count }}
+
{% if active_channels %}
@@ -388,13 +391,14 @@
Timestamp |
Hash |
- Value |
- Fee Paid |
- Status |
+ Value |
+ Fee Paid |
+ PPM Paid |
+ Status |
Chan Out Alias |
Chan Out ID |
- Route |
- Keysend |
+ Route |
+ Keysend |
{% for payment in payments %}
@@ -402,6 +406,7 @@
{{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
+ {{ payment.ppm|intcomma }} |
{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
diff --git a/gui/templates/payments.html b/gui/templates/payments.html
index 11fd5d60..ed6d4bc1 100644
--- a/gui/templates/payments.html
+++ b/gui/templates/payments.html
@@ -9,13 +9,14 @@ Last 150 Payments
Timestamp |
Hash |
- Value |
- Fee Paid |
- Status |
+ Value |
+ Fee Paid |
+ PPM Paid |
+ Status |
Chan Out Alias |
Chan Out ID |
- Route |
- Keysend |
+ Route |
+ Keysend |
{% for payment in payments %}
@@ -23,6 +24,7 @@ Last 150 Payments
{{ payment.payment_hash }} |
{{ payment.value|add:"0"|intcomma }} |
{{ payment.fee|intcomma }} |
+ {{ payment.ppm|intcomma }} |
{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %} |
{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %} |
{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %} |
diff --git a/gui/urls.py b/gui/urls.py
index 3d57b4e6..ec0bd058 100644
--- a/gui/urls.py
+++ b/gui/urls.py
@@ -9,6 +9,8 @@
router.register(r'invoices', views.InvoicesViewSet)
router.register(r'forwards', views.ForwardsViewSet)
router.register(r'onchain', views.OnchainViewSet)
+router.register(r'closures', views.ClosuresViewSet)
+router.register(r'resolutions', views.ResolutionsViewSet)
router.register(r'peers', views.PeersViewSet)
router.register(r'channels', views.ChannelsViewSet)
router.register(r'rebalancer', views.RebalancerViewSet)
@@ -46,6 +48,7 @@
path('keysends/', views.keysends, name='keysends'),
path('channels/', views.channels, name='channels'),
path('autopilot/', views.autopilot, name='autopilot'),
+ path('autofees/', views.autofees, name='autofees'),
path('advanced/', views.advanced, name='advanced'),
path('api/', include(router.urls), name='api-root'),
path('api-auth/', include('rest_framework.urls'), name='api-auth'),
diff --git a/gui/views.py b/gui/views.py
index 094cfc96..70fb8699 100644
--- a/gui/views.py
+++ b/gui/views.py
@@ -9,8 +9,8 @@
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
-from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot
-from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer
+from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees
+from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer
from .lnd_deps import lightning_pb2 as ln
from .lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps import router_pb2 as lnr
@@ -50,12 +50,12 @@ def home(request):
pending_force_closed = pending_channels.pending_force_closing_channels
waiting_for_close = pending_channels.waiting_close_channels
#Get recorded payment events
- payments = Payments.objects.exclude(status=3).order_by('-creation_date')
+ payments = Payments.objects.exclude(status=3)
total_payments = payments.filter(status=2).count()
total_sent = 0 if total_payments == 0 else payments.filter(status=2).aggregate(Sum('value'))['value__sum']
total_fees = 0 if total_payments == 0 else payments.aggregate(Sum('fee'))['fee__sum']
#Get recorded invoice details
- invoices = Invoices.objects.exclude(state=2).order_by('-creation_date')
+ invoices = Invoices.objects.exclude(state=2)
total_invoices = invoices.filter(state=1).count()
total_received = 0 if total_invoices == 0 else invoices.aggregate(Sum('amt_paid'))['amt_paid__sum']
#Get recorded forwarding events
@@ -70,10 +70,10 @@ def home(request):
forwards_df_out_count = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_out', as_index=True).count()
#Get current active channels
active_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')).order_by('outbound_percent')
- total_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum']
- total_inbound = 0 if total_capacity == 0 else active_channels.aggregate(Sum('remote_balance'))['remote_balance__sum']
- total_outbound = 0 if total_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum']
- total_unsettled = 0 if total_capacity == 0 else active_channels.aggregate(Sum('unsettled_balance'))['unsettled_balance__sum']
+ active_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum']
+ active_inbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('remote_balance'))['remote_balance__sum']
+ active_outbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum']
+ active_unsettled = 0 if active_capacity == 0 else active_channels.aggregate(Sum('unsettled_balance'))['unsettled_balance__sum']
filter_7day = datetime.now() - timedelta(days=7)
forwards_df_7d = DataFrame.from_records(forwards.filter(forward_date__gte=filter_7day).values())
forwards_df_in_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).sum()
@@ -88,6 +88,7 @@ def home(request):
total_7day_fees = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('fee'))['fee__sum']
pending_htlc_count = Channels.objects.filter(is_open=True).aggregate(Sum('htlc_count'))['htlc_count__sum'] if Channels.objects.filter(is_open=True).exists() else 0
pending_outbound = Channels.objects.filter(is_open=True).aggregate(Sum('pending_outbound'))['pending_outbound__sum'] if Channels.objects.filter(is_open=True).exists() else 0
+ pending_inbound = Channels.objects.filter(is_open=True).aggregate(Sum('pending_inbound'))['pending_inbound__sum'] if Channels.objects.filter(is_open=True).exists() else 0
detailed_active_channels = []
for channel in active_channels:
detailed_channel = {}
@@ -124,10 +125,16 @@ def home(request):
detailed_active_channels.append(detailed_channel)
#Get current inactive channels
inactive_channels = Channels.objects.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')
+ inactive_capacity = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('capacity'))['capacity__sum']
private_channels = Channels.objects.filter(is_open=True, private=True).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')
inactive_outbound = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('local_balance'))['local_balance__sum']
- private_outbound = 0 if private_channels.count() == 0 else private_channels.aggregate(Sum('local_balance'))['local_balance__sum']
- sum_outbound = total_outbound + pending_outbound + inactive_outbound + private_outbound
+ inactive_inbound = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('remote_balance'))['remote_balance__sum']
+ private_count = private_channels.count()
+ active_private = private_channels.filter(is_active=True).count()
+ private_capacity = private_channels.aggregate(Sum('capacity'))['capacity__sum']
+ private_outbound = 0 if private_count == 0 else private_channels.aggregate(Sum('local_balance'))['local_balance__sum']
+ sum_outbound = active_outbound + pending_outbound + inactive_outbound
+ sum_inbound = active_inbound + pending_inbound + inactive_inbound
onchain_txs = Onchain.objects.all()
onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
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']
@@ -135,7 +142,7 @@ def home(request):
total_costs_7day = total_7day_fees + onchain_costs_7day
#Get list of recent rebalance requests
rebalances = Rebalancer.objects.all().annotate(ppm=(Sum('fee_limit')*1000000)/Sum('value')).order_by('-id')
- total_channels = node_info.num_active_channels + node_info.num_inactive_channels
+ total_channels = node_info.num_active_channels + node_info.num_inactive_channels - private_count
local_settings = LocalSettings.objects.filter(key__contains='AR-')
try:
db_size = round(path.getsize(path.expanduser(LND_DIR_PATH + '/data/graph/' + LND_NETWORK + '/channel.db'))*0.000000001, 3)
@@ -146,11 +153,11 @@ def home(request):
'node_info': node_info,
'total_channels': total_channels,
'balances': balances,
- 'payments': payments[:6],
+ 'payments': payments.annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:6],
'total_sent': int(total_sent),
'fees_paid': int(total_fees),
'total_payments': total_payments,
- 'invoices': invoices[:6],
+ 'invoices': invoices.order_by('-creation_date')[:6],
'total_received': total_received,
'total_invoices': total_invoices,
'forwards': forwards_df.head(15).to_dict(orient='records'),
@@ -169,10 +176,20 @@ def home(request):
'onchain_costs_7day': onchain_costs_7day,
'total_7day_fees': int(total_7day_fees),
'active_channels': detailed_active_channels,
- 'capacity': total_capacity,
- 'inbound': total_inbound,
- 'outbound': total_outbound,
- 'unsettled': total_unsettled,
+ 'total_capacity': active_capacity + inactive_capacity,
+ 'active_private': active_private,
+ 'total_private': private_count,
+ 'private_outbound': private_outbound,
+ 'private_capacity': private_capacity,
+ 'inbound': active_inbound,
+ 'sum_inbound': sum_inbound,
+ 'inactive_inbound': inactive_inbound,
+ 'outbound': active_outbound,
+ 'sum_outbound': sum_outbound,
+ 'inactive_outbound': inactive_outbound,
+ 'unsettled': active_unsettled,
+ 'inactive_unsettled': pending_outbound + pending_inbound - active_unsettled,
+ 'total_unsettled': pending_outbound + pending_inbound,
'limbo_balance': limbo_balance,
'inactive_channels': inactive_channels,
'private_channels': private_channels,
@@ -189,7 +206,7 @@ def home(request):
'routed_ppm': 0 if total_value_forwards == 0 else int((total_earned/total_value_forwards)*1000000),
'7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000),
'7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000),
- 'liq_ratio': 0 if total_outbound == 0 else int((total_inbound/sum_outbound)*100),
+ 'liq_ratio': 0 if sum_outbound == 0 else int((sum_inbound/sum_outbound)*100),
'eligible_count': Channels.objects.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(),
'enabled_count': Channels.objects.filter(is_open=True, auto_rebalance=True).count(),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
@@ -207,8 +224,8 @@ def channels(request):
filter_7day = datetime.now() - timedelta(days=7)
filter_30day = datetime.now() - timedelta(days=30)
forwards = Forwards.objects.filter(forward_date__gte=filter_30day)
- payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_30day).filter(rebal_chan__isnull=False)
- invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_30day).filter(r_hash__in=payments.values_list('payment_hash'))
+ payments = Payments.objects.filter(status=2, creation_date__gte=filter_30day, rebal_chan__isnull=False)
+ invoices = Invoices.objects.filter(state=1, r_hash__in=payments.values_list('payment_hash'))
channels = Channels.objects.filter(is_open=True, private=False)
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
@@ -236,7 +253,7 @@ def channels(request):
invoices_df_7d_count = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True).count()
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_30d = DataFrame() if invoices_df_30d.empty else invoices_df_30d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
- channels_df['capacity'] = channels_df.apply(lambda row: round(row.capacity/1000000, 1), axis=1)
+ channels_df['mil_capacity'] = channels_df.apply(lambda row: round(row.capacity/1000000, 1), axis=1)
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['routed_in_7day'] = channels_df.apply(lambda row: forwards_df_in_7d_count.loc[row.chan_id].amt_out_msat if (forwards_df_in_7d_count.index == row.chan_id).any() else 0, axis=1)
@@ -274,13 +291,13 @@ def channels(request):
outbound_ratio = node_outbound/node_capacity
channels_df['apy_7day'] = channels_df.apply(lambda row: round((row['profits_7day']*5214.2857)/(row['capacity']*outbound_ratio), 2), axis=1)
channels_df['apy_30day'] = channels_df.apply(lambda row: round((row['profits_30day']*1216.6667)/(row['capacity']*outbound_ratio), 2), axis=1)
+ channels_df['cv_7day'] = channels_df.apply(lambda row: round((row['revenue_7day']*5214.2857)/(row['capacity']*outbound_ratio) + (row['revenue_assist_7day']*5214.2857)/(row['capacity']*(1-outbound_ratio)), 2), axis=1)
+ channels_df['cv_30day'] = channels_df.apply(lambda row: round((row['revenue_30day']*1216.6667)/(row['capacity']*outbound_ratio) + (row['revenue_assist_30day']*1216.6667)/(row['capacity']*(1-outbound_ratio)), 2), axis=1)
else:
- channels_df['attempts_30day'] = 0
- channels_df['success_30day'] = 0
- channels_df['success_rate_30day'] = 0
- channels_df['attempts_7day'] = 0
- channels_df['success_7day'] = 0
- channels_df['success_rate_7day'] = 0
+ channels_df['apy_7day'] = 0.0
+ channels_df['apy_30day'] = 0.0
+ channels_df['cv_7day'] = 0.0
+ channels_df['cv_30day'] = 0.0
else:
apy_7day = 0
apy_30day = 0
@@ -299,11 +316,12 @@ def channels(request):
@login_required(login_url='/lndg-admin/login/?next=/')
def fees(request):
if request.method == 'GET':
+ filter_1day = datetime.now() - timedelta(days=1)
filter_7day = datetime.now() - timedelta(days=7)
- channels = Channels.objects.filter(is_open=True)
+ channels = Channels.objects.filter(is_open=True, private=False)
channels_df = DataFrame.from_records(channels.values())
if channels_df.shape[0] > 0:
- failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(timestamp__gte=filter_7day).order_by('-id').values())
+ 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']]
forwards = Forwards.objects.filter(forward_date__gte=filter_7day, amt_out_msat__gte=1000000)
@@ -320,7 +338,7 @@ def fees(request):
channels_df['revenue_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].fee) if forwards_df_out_7d_sum.empty == False and (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['revenue_assist_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].fee) if forwards_df_in_7d_sum.empty == False and (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
channels_df['out_rate'] = channels_df.apply(lambda row: int((row['revenue_7day']/row['amt_routed_out_7day'])*1000000) if row['amt_routed_out_7day'] > 0 else 0, axis=1)
- channels_df['failed_out_7day'] = 0 if failed_htlc_df.empty else channels_df.apply(lambda row: len(failed_htlc_df[failed_htlc_df['chan_id_out']==row.chan_id]), axis=1)
+ channels_df['failed_out_1day'] = 0 if failed_htlc_df.empty else channels_df.apply(lambda row: len(failed_htlc_df[failed_htlc_df['chan_id_out']==row.chan_id]), axis=1)
payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_7day).filter(rebal_chan__isnull=False)
invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).filter(r_hash__in=payments.values_list('payment_hash'))
payments_df_7d = DataFrame.from_records(payments.filter(creation_date__gte=filter_7day).values())
@@ -331,7 +349,7 @@ def fees(request):
channels_df['costs_7day'] = channels_df.apply(lambda row: 0 if row['amt_rebal_in_7day'] == 0 else int(payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[row.chan_id] if invoice_hashes_7d.empty == False and (invoice_hashes_7d.index == row.chan_id).any() else []]['fee'].sum()), axis=1)
channels_df['rebal_ppm'] = channels_df.apply(lambda row: int((row['costs_7day']/row['amt_rebal_in_7day'])*1000000) if row['amt_rebal_in_7day'] > 0 else 0, axis=1)
channels_df['max_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*1.15) if row['in_percent'] > 25 else int(row['local_fee_rate']), axis=1)
- channels_df['max_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['max_suggestion'] > (row['local_fee_rate']+25) else row['max_suggestion'], axis=1)
+ channels_df['max_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['max_suggestion'] > (row['local_fee_rate']+25) or row['max_suggestion'] == 0 else row['max_suggestion'], axis=1)
channels_df['min_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*0.75) if row['out_percent'] > 25 else int(row['local_fee_rate']), axis=1)
channels_df['min_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']-50 if row['min_suggestion'] < (row['local_fee_rate']-50) else row['min_suggestion'], axis=1)
channels_df['assisted_ratio'] = channels_df.apply(lambda row: round((row['revenue_assist_7day'] if row['revenue_7day'] == 0 else row['revenue_assist_7day']/row['revenue_7day']), 2), axis=1)
@@ -342,10 +360,14 @@ def fees(request):
channels_df['fee_rate_only'] = channels_df.apply(lambda row: int(row['local_fee_rate']+row['net_routed_7day']*row['local_fee_rate']*0.05), axis=1)
channels_df['new_rate'] = channels_df.apply(lambda row: row['adjusted_out_rate'] if row['net_routed_7day'] != 0 else (row['adjusted_rebal_rate'] if row['rebal_ppm'] > 0 and row['out_rate'] > 0 else (row['out_rate_only'] if row['out_rate'] > 0 else (row['min_suggestion'] if row['net_routed_7day'] == 0 and row['in_percent'] < 25 else row['fee_rate_only']))), axis=1)
channels_df['new_rate'] = channels_df.apply(lambda row: 0 if row['new_rate'] < 0 else row['new_rate'], axis=1)
- channels_df['new_rate'] = channels_df.apply(lambda row: row['max_suggestion'] if row['max_suggestion'] > 0 and row['new_rate'] > row['max_suggestion'] 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['new_rate'] = channels_df.apply(lambda row: row['local_fee_rate']-10 if row['adjustment']==0 and row['out_percent']>=25 and row['net_routed_7day']==0 else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['adjustment']==0 and row['out_percent']<25 and row['failed_out_1day']>25 else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['max_suggestion'] if row['new_rate'] > row['max_suggestion'] else row['new_rate'], axis=1)
channels_df['new_rate'] = channels_df.apply(lambda row: row['min_suggestion'] if row['new_rate'] < row['min_suggestion'] else row['new_rate'], axis=1)
channels_df['new_rate'] = channels_df.apply(lambda row: int(round(row['new_rate']/5, 0)*5), 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)
context = {
'channels': [] if channels_df.empty else channels_df.sort_values(by=['out_percent']).to_dict(orient='records'),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
@@ -422,13 +444,16 @@ def balances(request):
def closures(request):
if request.method == 'GET':
closures_df = DataFrame.from_records(Closures.objects.all().values())
- channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias'))
- if channels_df.empty:
- merged = closures_df
- merged['alias'] = ''
+ if closures_df.empty:
+ merged = DataFrame()
else:
- merged = merge(closures_df, channels_df, on='chan_id', how='left')
- merged['alias'] = merged['alias'].fillna('')
+ 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('')
context = {
'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
@@ -462,7 +487,7 @@ def channel(request):
filter_7day = datetime.now() - timedelta(days=7)
filter_30day = datetime.now() - timedelta(days=30)
forwards_df = DataFrame.from_records(Forwards.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).values())
- payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(chan_out=chan_id).filter(rebal_chan__isnull=False).values())
+ payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(chan_out=chan_id).filter(rebal_chan__isnull=False).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).values())
invoices_df = DataFrame.from_records(Invoices.objects.filter(state=1).filter(chan_in=chan_id).filter(r_hash__in=Payments.objects.filter(status=2).filter(rebal_chan=chan_id)).values())
channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True).values())
node_outbound = channels_df['local_balance'].sum()
@@ -710,7 +735,6 @@ def channel(request):
channels_df['profits_vol_30day'] = 0 if channels_df['amt_routed_out_30day'][0] == 0 else int(channels_df['profits_30day'] / (channels_df['amt_routed_out_30day']/1000000))
channels_df['profits_vol_7day'] = 0 if channels_df['amt_routed_out_7day'][0] == 0 else int(channels_df['profits_7day'] / (channels_df['amt_routed_out_7day']/1000000))
channels_df['profits_vol_1day'] = 0 if channels_df['amt_routed_out_1day'][0] == 0 else int(channels_df['profits_1day'] / (channels_df['amt_routed_out_1day']/1000000))
- channels_df['apy'] = 0.0
channels_df = channels_df.copy()
channels_df['net_routed_7day'] = round((channels_df['amt_routed_out_7day']-channels_df['amt_routed_in_7day'])/channels_df['capacity'], 1)
channels_df['out_rate'] = int((channels_df['revenue_7day']/channels_df['amt_routed_out_7day'])*1000000) if channels_df['amt_routed_out_7day'][0] > 0 else 0
@@ -736,15 +760,35 @@ def channel(request):
channels_df['fee_check'] = 1 if channels_df['ar_max_cost'][0] == 0 else int(round(((channels_df['fee_ratio']/channels_df['ar_max_cost'])*1000)/10, 0))
channels_df = channels_df.copy()
channels_df['steps'] = 0 if channels_df['inbound_can'][0] < 1 else int(((channels_df['in_percent']-channels_df['ar_in_target'])/((channels_df['ar_amt_target']/channels_df['capacity'])*100))+0.999)
+ channels_df['apy'] = 0.0
+ channels_df['apy_30day'] = 0.0
+ channels_df['apy_7day'] = 0.0
+ channels_df['apy_1day'] = 0.0
+ channels_df['assisted_apy'] = 0.0
+ channels_df['assisted_apy_30day'] = 0.0
+ channels_df['assisted_apy_7day'] = 0.0
+ channels_df['assisted_apy_1day'] = 0.0
+ channels_df['cv'] = 0.0
+ channels_df['cv_30day'] = 0.0
+ channels_df['cv_7day'] = 0.0
+ channels_df['cv_1day'] = 0.0
if node_capacity > 0:
outbound_ratio = node_outbound/node_capacity
if start_date is not None:
time_delta = datetime.now() - start_date.to_pydatetime()
days_routing = time_delta.days + (time_delta.seconds/86400)
channels_df['apy'] = round(((channels_df['profits']/days_routing)*36500)/(channels_df['capacity']*outbound_ratio), 2)
+ channels_df['assisted_apy'] = round(((channels_df['revenue_assist']/days_routing)*36500)/(channels_df['capacity']*(1-outbound_ratio)), 2)
+ channels_df['cv'] = round(((channels_df['revenue']/days_routing)*36500)/(channels_df['capacity']*outbound_ratio) + channels_df['assisted_apy'], 2)
channels_df['apy_30day'] = round((channels_df['profits_30day']*1216.6667)/(channels_df['capacity']*outbound_ratio), 2)
channels_df['apy_7day'] = round((channels_df['profits_7day']*5214.2857)/(channels_df['capacity']*outbound_ratio), 2)
channels_df['apy_1day'] = round((channels_df['profits_1day']*36500)/(channels_df['capacity']*outbound_ratio), 2)
+ channels_df['assisted_apy_30day'] = round((channels_df['revenue_assist_30day']*1216.6667)/(channels_df['capacity']*(1-outbound_ratio)), 2)
+ channels_df['assisted_apy_7day'] = round((channels_df['revenue_assist_7day']*5214.2857)/(channels_df['capacity']*(1-outbound_ratio)), 2)
+ channels_df['assisted_apy_1day'] = round((channels_df['revenue_assist_1day']*36500)/(channels_df['capacity']*(1-outbound_ratio)), 2)
+ 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)
else:
channels_df = DataFrame()
forwards_df = DataFrame()
@@ -874,7 +918,7 @@ def failed_htlcs(request):
def payments(request):
if request.method == 'GET':
context = {
- 'payments': Payments.objects.filter(status=2).order_by('-creation_date')[:150],
+ 'payments': Payments.objects.filter(status=2).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:150],
}
return render(request, 'payments.html', context)
else:
@@ -956,6 +1000,16 @@ def autopilot(request):
else:
return redirect('home')
+@login_required(login_url='/lndg-admin/login/?next=/')
+def autofees(request):
+ if request.method == 'GET':
+ context = {
+ 'autofees': Autofees.objects.all().order_by('-id')
+ }
+ return render(request, 'autofees.html', context)
+ else:
+ return redirect('home')
+
@login_required(login_url='/lndg-admin/login/?next=/')
def open_channel_form(request):
if request.method == 'POST':
@@ -1133,6 +1187,8 @@ def update_chan_policy(request):
if form.cleaned_data['target_all']:
args = {'global': True, 'base_fee_msat': form.cleaned_data['new_base_fee'], 'fee_rate': (form.cleaned_data['new_fee_rate']/1000000), 'time_lock_delta': 40}
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(**args))
+ channels = Channels.objects.filter(is_open=True)
+ channels.update(fees_updated=datetime.now())
elif len(form.cleaned_data['target_chans']) > 0:
for channel in form.cleaned_data['target_chans']:
channel_point = ln.ChannelPoint()
@@ -1140,6 +1196,9 @@ def update_chan_policy(request):
channel_point.funding_txid_str = channel.funding_txid
channel_point.output_index = channel.output_index
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=form.cleaned_data['new_base_fee'], fee_rate=(form.cleaned_data['new_fee_rate']/1000000), time_lock_delta=40))
+ db_channel = Channels.objects.get(chan_id=channel.chan_id)
+ db_channel.fees_updated = datetime.now()
+ db_channel.save()
else:
messages.error(request, 'No channels were specified in the update request!')
messages.success(request, 'Channel policies updated! This will be broadcast during the next graph update!')
@@ -1272,6 +1331,7 @@ def update_channel(request):
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=40))
db_channel.local_fee_rate = target
+ db_channel.fees_updated = datetime.now()
db_channel.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:
@@ -1306,6 +1366,10 @@ def update_channel(request):
messages.success(request, 'Toggled channel state for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') to a value of: ' + ('Enabled' if target == 1 else 'Disabled'))
if target == 0:
messages.warning(request, 'Use with caution, while a channel is disabled (local fees highlighted in red) it will not route out.')
+ elif update_target == 8:
+ db_channel.auto_fees = True if db_channel.auto_fees == False else False
+ db_channel.save()
+ messages.success(request, 'Auto fees status for chanel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.auto_fees))
else:
messages.error(request, 'Invalid target code. Please try again.')
else:
@@ -1328,7 +1392,6 @@ def update_setting(request):
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
db_percent_target.value = target_percent
db_percent_target.save()
- Channels.objects.all().update(ar_amt_target=Round(F('capacity')*target_percent, output_field=IntegerField()))
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
elif key == 'AR-Time':
target_time = int(value)
@@ -1359,7 +1422,6 @@ def update_setting(request):
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
db_outbound_target.value = outbound_percent
db_outbound_target.save()
- Channels.objects.all().update(ar_out_target=int(outbound_percent*100))
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
elif key == 'AR-MaxFeeRate':
fee_rate = int(value)
@@ -1380,7 +1442,6 @@ def update_setting(request):
db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%')
db_max_cost.value = max_cost
db_max_cost.save()
- Channels.objects.all().update(ar_max_cost=int(max_cost*100))
messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost))
elif key == 'AR-Autopilot':
autopilot = int(value)
@@ -1432,6 +1493,68 @@ def update_setting(request):
db_retention_days.value = retention_days
db_retention_days.save()
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))
+ channels = Channels.objects.filter(is_open=True)
+ for db_channel in channels:
+ 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=40))
+ db_channel.local_fee_rate = target
+ db_channel.fees_updated = datetime.now()
+ db_channel.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))
+ 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))
+ 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=target, fee_rate=(db_channel.local_fee_rate/1000000), time_lock_delta=40))
+ db_channel.local_base_fee = target
+ db_channel.save()
+ messages.success(request, 'Base fee for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-Amts':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True).update(ar_amt_target=target)
+ messages.success(request, 'AR target amounts for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-MaxCost':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True).update(ar_max_cost=target)
+ messages.success(request, 'AR max cost %s for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-oTarget':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True).update(ar_out_target=target)
+ messages.success(request, 'AR outbound liquidity target %s for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-iTarget':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True).update(ar_in_target=target)
+ messages.success(request, 'AR inbound liquidity target %s for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-AR':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True).update(auto_rebalance=target)
+ messages.success(request, 'Auto-Rebalance targeting for all channels updated to a value of: ' + str(target))
+ elif key == 'ALL-AF':
+ target = int(value)
+ channels = Channels.objects.filter(is_open=True, private=False).update(auto_fees=target)
+ messages.success(request, 'Auto Fees setting for all channels updated to a value of: ' + str(target))
+ elif key == 'AF-Enabled':
+ enabled = int(value)
+ try:
+ db_enabled = LocalSettings.objects.get(key='AF-Enabled')
+ except:
+ LocalSettings(key='AF-Enabled', value='0').save()
+ db_enabled = LocalSettings.objects.get(key='AF-Enabled')
+ db_enabled.value = enabled
+ db_enabled.save()
+ messages.success(request, 'Updated autofees enabled setting to: ' + str(enabled))
else:
messages.error(request, 'Invalid Request. Please try again.')
else:
@@ -1462,6 +1585,14 @@ class OnchainViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Onchain.objects.all()
serializer_class = OnchainSerializer
+class ClosuresViewSet(viewsets.ReadOnlyModelViewSet):
+ queryset = Closures.objects.all()
+ serializer_class = ClosuresSerializer
+
+class ResolutionsViewSet(viewsets.ReadOnlyModelViewSet):
+ queryset = Resolutions.objects.all()
+ serializer_class = ResolutionsSerializer
+
class PendingHTLCViewSet(viewsets.ReadOnlyModelViewSet):
queryset = PendingHTLCs.objects.all()
serializer_class = PendingHTLCSerializer
diff --git a/jobs.py b/jobs.py
index 7f7a9fd3..47971ef6 100644
--- a/jobs.py
+++ b/jobs.py
@@ -8,9 +8,10 @@
from gui.lnd_deps.lnd_connect import lnd_connect
from lndg import settings
from os import environ
+from pandas import DataFrame
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
-from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings
+from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees
def update_payments(stub):
#Remove anything in-flight so we can get most up to date status
@@ -283,12 +284,13 @@ def update_closures(stub):
counter += 1
if counter > skip:
resolution_count = len(closure.resolutions)
- db_closure = Closures(chan_id=closure.chan_id, 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)
+ txid, index = closure.channel_point.split(':')
+ 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)
db_closure.save()
if resolution_count > 0:
Resolutions.objects.filter(chan_id=closure.chan_id).delete()
for resolution in closure.resolutions:
- Resolutions(chan_id=db_closure, 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()
+ 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()
def reconnect_peers(stub):
inactive_peers = Channels.objects.filter(is_open=True, is_active=False, private=False).values_list('remote_pubkey', flat=True).distinct()
@@ -346,19 +348,95 @@ def clean_payments(stub):
payment.cleaned = True
payment.save()
+def auto_fees(stub):
+ if LocalSettings.objects.filter(key='AF-Enabled').exists():
+ enabled = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value)
+ else:
+ LocalSettings(key='AF-Enabled', value='0').save()
+ enabled = 0
+ if enabled == 1:
+ filter_1day = datetime.now() - timedelta(days=1)
+ filter_7day = datetime.now() - timedelta(days=7)
+ channels = Channels.objects.filter(is_open=True, is_active=True, private=False, auto_fees=True)
+ channels_df = DataFrame.from_records(channels.values())
+ channels_df['eligible'] = channels_df.apply(lambda row: (datetime.now()-row['fees_updated']).total_seconds() > 86400, 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())
+ 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']]
+ forwards = Forwards.objects.filter(forward_date__gte=filter_7day, amt_out_msat__gte=1000000)
+ forwards_df_7d = DataFrame.from_records(forwards.values())
+ forwards_df_in_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).sum()
+ forwards_df_out_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).sum()
+ 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['in_percent'] = channels_df.apply(lambda row: int(round((row['remote_balance']/row['capacity'])*100, 0)), axis=1)
+ channels_df['out_percent'] = channels_df.apply(lambda row: int(round((row['local_balance']/row['capacity'])*100, 0)), axis=1)
+ channels_df['amt_routed_in_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].amt_out_msat/1000) if (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
+ channels_df['amt_routed_out_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].amt_out_msat/1000) if (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1)
+ channels_df['net_routed_7day'] = channels_df.apply(lambda row: round((row['amt_routed_out_7day']-row['amt_routed_in_7day'])/row['capacity'], 1), axis=1)
+ channels_df['revenue_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].fee) if forwards_df_out_7d_sum.empty == False and (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1)
+ channels_df['revenue_assist_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].fee) if forwards_df_in_7d_sum.empty == False and (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1)
+ channels_df['out_rate'] = channels_df.apply(lambda row: int((row['revenue_7day']/row['amt_routed_out_7day'])*1000000) if row['amt_routed_out_7day'] > 0 else 0, axis=1)
+ channels_df['failed_out_1day'] = 0 if failed_htlc_df.empty else channels_df.apply(lambda row: len(failed_htlc_df[failed_htlc_df['chan_id_out']==row.chan_id]), axis=1)
+ payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_7day).filter(rebal_chan__isnull=False)
+ invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).filter(r_hash__in=payments.values_list('payment_hash'))
+ payments_df_7d = DataFrame.from_records(payments.filter(creation_date__gte=filter_7day).values())
+ invoices_df_7d = DataFrame.from_records(invoices.filter(settle_date__gte=filter_7day).values())
+ invoices_df_7d_sum = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True).sum()
+ invoice_hashes_7d = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True)['r_hash'].apply(list)
+ channels_df['amt_rebal_in_7day'] = channels_df.apply(lambda row: int(invoices_df_7d_sum.loc[row.chan_id].amt_paid) if invoices_df_7d_sum.empty == False and (invoices_df_7d_sum.index == row.chan_id).any() else 0, axis=1)
+ channels_df['costs_7day'] = channels_df.apply(lambda row: 0 if row['amt_rebal_in_7day'] == 0 else int(payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[row.chan_id] if invoice_hashes_7d.empty == False and (invoice_hashes_7d.index == row.chan_id).any() else []]['fee'].sum()), axis=1)
+ channels_df['rebal_ppm'] = channels_df.apply(lambda row: int((row['costs_7day']/row['amt_rebal_in_7day'])*1000000) if row['amt_rebal_in_7day'] > 0 else 0, axis=1)
+ channels_df['max_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*1.15) if row['in_percent'] > 25 else int(row['local_fee_rate']), axis=1)
+ channels_df['max_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['max_suggestion'] > (row['local_fee_rate']+25) or row['max_suggestion'] == 0 else row['max_suggestion'], axis=1)
+ channels_df['min_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*0.75) if row['out_percent'] > 25 else int(row['local_fee_rate']), axis=1)
+ channels_df['min_suggestion'] = channels_df.apply(lambda row: row['local_fee_rate']-50 if row['min_suggestion'] < (row['local_fee_rate']-50) else row['min_suggestion'], axis=1)
+ channels_df['assisted_ratio'] = channels_df.apply(lambda row: round((row['revenue_assist_7day'] if row['revenue_7day'] == 0 else row['revenue_assist_7day']/row['revenue_7day']), 2), axis=1)
+ channels_df['profit_margin'] = channels_df.apply(lambda row: row['out_rate']*((100-row['ar_max_cost'])/100), axis=1)
+ channels_df['adjusted_out_rate'] = channels_df.apply(lambda row: int(row['out_rate']+row['net_routed_7day']*row['assisted_ratio']), axis=1)
+ channels_df['adjusted_rebal_rate'] = channels_df.apply(lambda row: int(row['rebal_ppm']+row['profit_margin']), axis=1)
+ channels_df['out_rate_only'] = channels_df.apply(lambda row: int(row['out_rate']+row['net_routed_7day']*row['out_rate']*0.02), axis=1)
+ channels_df['fee_rate_only'] = channels_df.apply(lambda row: int(row['local_fee_rate']+row['net_routed_7day']*row['local_fee_rate']*0.05), axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['adjusted_out_rate'] if row['net_routed_7day'] != 0 else (row['adjusted_rebal_rate'] if row['rebal_ppm'] > 0 and row['out_rate'] > 0 else (row['out_rate_only'] if row['out_rate'] > 0 else (row['min_suggestion'] if row['net_routed_7day'] == 0 and row['in_percent'] < 25 else row['fee_rate_only']))), axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: 0 if row['new_rate'] < 0 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['new_rate'] = channels_df.apply(lambda row: row['local_fee_rate']-10 if row['adjustment']==0 and row['out_percent']>=25 and row['net_routed_7day']==0 else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['local_fee_rate']+25 if row['adjustment']==0 and row['out_percent']<25 and row['failed_out_1day']>25 else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['max_suggestion'] if row['new_rate'] > row['max_suggestion'] else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: row['min_suggestion'] if row['new_rate'] < row['min_suggestion'] else row['new_rate'], axis=1)
+ channels_df['new_rate'] = channels_df.apply(lambda row: int(round(row['new_rate']/5, 0)*5), axis=1)
+ channels_df['adjustment'] = channels_df.apply(lambda row: int(row['new_rate']-row['local_fee_rate']), axis=1)
+ update_df = channels_df[channels_df['adjustment']!=0]
+ if not update_df.empty:
+ for target_channel in update_df.to_dict(orient='records'):
+ print('Updating fees for channel ' + str(target_channel['chan_id']) + ' to a value of: ' + str(target_channel['new_rate']))
+ channel = Channels.objects.filter(chan_id=target_channel['chan_id'])[0]
+ channel_point = ln.ChannelPoint()
+ channel_point.funding_txid_bytes = bytes.fromhex(channel.funding_txid)
+ channel_point.funding_txid_str = channel.funding_txid
+ channel_point.output_index = channel.output_index
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=channel.local_base_fee, fee_rate=(target_channel['new_rate']/1000000), time_lock_delta=40))
+ 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()
+
def main():
try:
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
#Update data
update_channels(stub)
update_peers(stub)
- update_payments(stub)
update_invoices(stub)
+ update_payments(stub)
update_forwards(stub)
update_onchain(stub)
update_closures(stub)
reconnect_peers(stub)
clean_payments(stub)
+ auto_fees(stub)
except Exception as e:
print('Error processing background data: ' + str(e))
diff --git a/rebalancer.py b/rebalancer.py
index 4bc72539..21a97cfd 100644
--- a/rebalancer.py
+++ b/rebalancer.py
@@ -37,6 +37,7 @@ def run_rebalancer(rebalance):
elif payment_response.status == 2:
#SUCCESSFUL
rebalance.status = 2
+ successful_out = payment_response.htlcs[0].route.hops[0].pub_key
elif payment_response.status == 3:
#FAILURE
if payment_response.failure_reason == 1:
@@ -67,12 +68,12 @@ def run_rebalancer(rebalance):
rebalance.stop = datetime.now()
rebalance.save()
if rebalance.status == 2:
- update_channel(stub, rebalance.last_hop_pubkey)
+ 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)
if len(inbound_cans) > 0:
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
- 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=rebalance.duration)
+ 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.save()
else:
next_rebalance = None
@@ -80,8 +81,15 @@ def run_rebalancer(rebalance):
next_rebalance = None
return next_rebalance
-def update_channel(stub, peer_pubkey):
- channel = stub.ListChannels(ln.ListChannelsRequest(peer=bytes.fromhex(peer_pubkey))).channels[0]
+def update_channels(stub, incoming_channel, outgoing_channel):
+ # Incoming channel update
+ channel = stub.ListChannels(ln.ListChannelsRequest(peer=bytes.fromhex(incoming_channel))).channels[0]
+ db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
+ db_channel.local_balance = channel.local_balance
+ db_channel.remote_balance = channel.remote_balance
+ db_channel.save()
+ # Outgoing channel update
+ channel = stub.ListChannels(ln.ListChannelsRequest(peer=bytes.fromhex(outgoing_channel))).channels[0]
db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
db_channel.local_balance = channel.local_balance
db_channel.remote_balance = channel.remote_balance
@@ -127,7 +135,7 @@ def auto_schedule():
# TLDR: willing to pay 1 sat for every value_per_fee sats moved
if Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).exists():
last_rebalance = Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).order_by('-id')[0]
- if not (last_rebalance.value != target_value or last_rebalance.status in [2, 6] or (last_rebalance.status in [3, 4, 5, 7, 400, 408] and (int((datetime.now() - last_rebalance.stop).total_seconds() / 60) > 30)) or (last_rebalance.status == 1 and (int((datetime.now() - last_rebalance.start).total_seconds() / 60) > 30))):
+ if not (last_rebalance.value != target_value or last_rebalance.status == 2 or (last_rebalance.status in [3, 4, 5, 6, 7, 400, 408] and (int((datetime.now() - last_rebalance.stop).total_seconds() / 60) > 30)) or (last_rebalance.status == 1 and (int((datetime.now() - last_rebalance.start).total_seconds() / 60) > 30))):
continue
print('Creating Auto Rebalance Request')
print('Request for:', target.chan_id)