diff --git a/doc/bounties.md b/doc/bounties.md
index 84c27feb..928489f1 100644
--- a/doc/bounties.md
+++ b/doc/bounties.md
@@ -36,30 +36,6 @@ Additional myNode guides would be helpful for users attempting to use various to
GitHub: https://github.com/mynodebtc/mynodebtc.github.io
-## Add Page to Manage Lightning Watchtower
-
-Known active efforts: none
-
-Payout: 250k sats
-
-Users that want to use watchtower will likely prefer more detailed information available via the myNode UI. Watchtower integration is currently limited to enabling and disabling watchtower functionality along with viewing the watchtower URI.
-
-The watchtower config for tor is already setup and should be ready to use.
-
-Acceptance Criteria:
-- New link on Lightning page going to watchtower-specific page
-- New watchtower page
- - View watchtower clients, status, and details (lncli wtclient [towers, tower, stats, policy, ...])
- - Add new tower (lncli wtclient add ...)
- - Remove tower (lncli wtclient remove ...)
-- Should work if testnet is enabled
-
-Helpful links:
-
-https://github.com/lightningnetwork/lnd/blob/master/docs/watchtower.md
-
-https://github.com/openoms/lightning-node-management/blob/master/advanced-tools/watchtower.md
-
# Claimed Bounties
diff --git a/rootfs/standard/var/pynode/lightning_info.py b/rootfs/standard/var/pynode/lightning_info.py
index ea7a586d..63fcc624 100644
--- a/rootfs/standard/var/pynode/lightning_info.py
+++ b/rootfs/standard/var/pynode/lightning_info.py
@@ -7,6 +7,7 @@ import re
import datetime
import urllib
import random
+import base64
from device_info import *
from threading import Timer
from utilities import *
@@ -28,7 +29,10 @@ lightning_wallet_balance = None
lightning_transactions = None
lightning_payments = None
lightning_invoices = None
-lightning_watchtower_server_info = None
+lightning_watchtower_server_info = {}
+lightning_watchtower_client_towers = {}
+lightning_watchtower_client_stats = {}
+lightning_watchtower_client_policy = {}
lightning_desync_count = 0
lightning_update_count = 0
@@ -38,6 +42,19 @@ TLS_CERT_FILE = "/mnt/hdd/mynode/lnd/tls.cert"
LND_REST_PORT = "10080"
# Functions
+def run_lncli_command(cmd):
+ try:
+ base = "lncli "
+ base += "--lnddir=/mnt/hdd/mynode/lnd "
+ if is_testnet_enabled():
+ base += "--network=testnet "
+ cmd = cmd.replace("lncli ", base)
+ output = subprocess.check_output(cmd, shell=True)
+ return output
+ except Exception as e:
+ log_message("ERROR in run_lncli_command: {}".format(str(e)))
+ return None
+
def update_lightning_info():
global lightning_info
global lightning_peers
@@ -48,6 +65,9 @@ def update_lightning_info():
global lightning_payments
global lightning_invoices
global lightning_watchtower_server_info
+ global lightning_watchtower_client_towers
+ global lightning_watchtower_client_stats
+ global lightning_watchtower_client_policy
global lightning_desync_count
global lightning_update_count
global lnd_ready
@@ -88,6 +108,18 @@ def update_lightning_info():
lightning_wallet_balance = lnd_get("/balance/blockchain")
if is_watchtower_enabled():
lightning_watchtower_server_info = lnd_get_v2("/watchtower/server")
+ towers = lnd_get_v2("/watchtower/client?include_sessions=1")
+ tower_details = []
+ if towers != None and "towers" in towers:
+ for tower in towers["towers"]:
+ if "pubkey" in tower and tower["active_session_candidate"]:
+ pubkey_decoded = base64.b64decode(tower['pubkey'])
+ pubkey_b16 = to_string(base64.b16encode( pubkey_decoded )).lower()
+ tower["pubkey_b16"] = pubkey_b16
+ tower_details.append(tower)
+ lightning_watchtower_client_towers = tower_details
+ lightning_watchtower_client_stats = lnd_get_v2("/watchtower/client/stats")
+ lightning_watchtower_client_policy = lnd_get_v2("/watchtower/client/policy")
# Poll slower (make sure we gather data early)
if lightning_update_count < 30 or lightning_update_count % 2 == 0:
@@ -367,7 +399,46 @@ def get_lightning_payments_and_invoices():
def get_lightning_watchtower_server_info():
global lightning_watchtower_server_info
- return copy.deepcopy(lightning_watchtower_server_info)
+ server_info = copy.deepcopy(lightning_watchtower_server_info)
+ server_info["watchtower_server_uri"] = "..."
+
+ if server_info != None:
+ try:
+ if "uris" in server_info and len(server_info['uris']) > 0:
+ first_uri = True
+ text = ""
+ for uri in server_info['uris']:
+ if first_uri:
+ first_uri = False
+ else:
+ text += " "
+ text += uri
+ server_info["watchtower_server_uri"] = text
+ elif "pubkey" in server_info or "listeners" in server_info:
+ server_info["watchtower_server_uri"] = ""
+ if "pubkey" in server_info:
+ server_info["watchtower_server_uri"] += server_info["pubkey"]
+ #if "listeners":
+ # server_info["watchtower_server_uri"] += "listeners: " + watchtower_server_info["listeners"][0]
+ except:
+ return server_info
+
+ return server_info
+
+def get_lightning_watchtower_client_towers():
+ global lightning_watchtower_client_towers
+ towers = copy.deepcopy(lightning_watchtower_client_towers)
+ return towers
+
+def get_lightning_watchtower_client_stats():
+ global lightning_watchtower_client_stats
+ stats = copy.deepcopy(lightning_watchtower_client_stats)
+ return stats
+
+def get_lightning_watchtower_client_policy():
+ global lightning_watchtower_client_policy
+ policy = copy.deepcopy(lightning_watchtower_client_policy)
+ return policy
def is_lnd_ready():
global lnd_ready
@@ -606,15 +677,13 @@ def get_lnd_alias_file_data():
return "ERROR"
def is_watchtower_enabled():
- if os.path.isfile("/mnt/hdd/mynode/settings/.watchtower_enabled"):
- return True
- return False
+ return settings_file_exists("watchtower_enabled")
def enable_watchtower():
- touch("/mnt/hdd/mynode/settings/.watchtower_enabled")
+ create_settings_file("watchtower_enabled")
def disable_watchtower():
- delete_file("/mnt/hdd/mynode/settings/.watchtower_enabled")
+ delete_settings_file("watchtower_enabled")
# Only call from www process which has data
def update_lightning_json_cache():
diff --git a/rootfs/standard/var/www/mynode/lnd.py b/rootfs/standard/var/www/mynode/lnd.py
index 9b07ab83..2fab8151 100644
--- a/rootfs/standard/var/www/mynode/lnd.py
+++ b/rootfs/standard/var/www/mynode/lnd.py
@@ -164,23 +164,6 @@ def page_lnd():
payments = get_lightning_payments()
invoices = get_lightning_invoices()
- watchtower_server_info = get_lightning_watchtower_server_info()
- watchtower_text= "..."
- if watchtower_server_info != None:
- try:
- if "uris" in watchtower_server_info and len(watchtower_server_info['uris']) > 0:
- watchtower_text = watchtower_server_info['uris'][0]
- elif "pubkey" in watchtower_server_info or "listeners" in watchtower_server_info:
- watchtower_text = ""
- if "pubkey" in watchtower_server_info:
- watchtower_text += watchtower_server_info["pubkey"]
- #if "listeners":
- # watchtower_text += "listeners: " + watchtower_server_info["listeners"][0]
- else:
- watchtower_text = "missing info"
- except:
- watchtower_text = "error"
-
except Exception as e:
templateData = {
"title": "myNode Lightning Status",
@@ -225,7 +208,6 @@ def page_lnd():
"channel_pending": format_sat_amount(balance_info["channel_pending"]),
"wallet_balance": format_sat_amount(balance_info["wallet_balance"]),
"wallet_pending": format_sat_amount(balance_info["wallet_pending"]),
- "watchtower_text": Markup(watchtower_text),
"peers": peers,
"channels": channels,
"transactions": transactions,
@@ -537,7 +519,7 @@ def lnd_config_page():
}
return render_template('lnd_config.html', **templateData)
-@mynode_lnd.route("/lnd/set_watchtower_enabled")
+@mynode_lnd.route("/lnd/watchtower/set_watchtower_enabled")
def lnd_set_watchtower_enabled_page():
check_logged_in()
@@ -549,7 +531,68 @@ def lnd_set_watchtower_enabled_page():
restart_lnd()
flash("Watchtower settings updated!", category="message")
- return redirect(url_for(".page_lnd"))
+ return redirect(url_for(".page_lnd_watchtower"))
+
+@mynode_lnd.route("/lnd/watchtower")
+def page_lnd_watchtower():
+ check_logged_in()
+
+ watchtower_server_info = get_lightning_watchtower_server_info()
+ watchtower_client_towers = get_lightning_watchtower_client_towers()
+ watchtower_client_stats = get_lightning_watchtower_client_stats()
+ watchtower_client_policy = get_lightning_watchtower_client_policy()
+
+ templateData = {
+ "title": "myNode Lightning Watchtower",
+ "watchtower_server_enabled": is_watchtower_enabled(),
+ "watchtower_server_uri": Markup(watchtower_server_info["watchtower_server_uri"]),
+ "watchtower_client_towers": watchtower_client_towers,
+ "watchtower_client_stats": watchtower_client_stats,
+ "watchtower_client_policy": watchtower_client_policy,
+ "header": "Lightning Watchtower",
+ "ui_settings": read_ui_settings()
+ }
+ return render_template('lnd_watchtower.html', **templateData)
+
+@mynode_lnd.route("/lnd/watchtower/add_tower", methods=["POST"])
+def page_lnd_watchtower_add_tower():
+ check_logged_in()
+
+ if request.form.get("new_tower"):
+ cmd = "lncli wtclient add {}".format(request.form.get("new_tower"))
+ r = run_lncli_command(cmd)
+ if r == None:
+ flash("Error adding tower!", category="error")
+ return redirect(url_for(".page_lnd_watchtower"))
+ else:
+ flash("Error adding tower - missing tower", category="error")
+ return redirect(url_for(".page_lnd_watchtower"))
+
+ # Update Lightning Info
+ update_lightning_info()
+
+ flash("Tower added!", category="message")
+ return redirect(url_for(".page_lnd_watchtower"))
+
+@mynode_lnd.route("/lnd/watchtower/remove_tower", methods=["GET"])
+def page_lnd_watchtower_remove_tower():
+ check_logged_in()
+
+ if request.args.get("tower"):
+ cmd = "lncli wtclient remove {}".format(request.args.get("tower"))
+ r = run_lncli_command(cmd)
+ if r == None:
+ flash("Error removing tower!", category="error")
+ return redirect(url_for(".page_lnd_watchtower"))
+ else:
+ flash("Error removing tower - missing tower", category="error")
+ return redirect(url_for(".page_lnd_watchtower"))
+
+ # Update Lightning Info
+ update_lightning_info()
+
+ flash("Tower removed!", category="message")
+ return redirect(url_for(".page_lnd_watchtower"))
##############################################
## LND API Calls
diff --git a/rootfs/standard/var/www/mynode/price_info.py b/rootfs/standard/var/www/mynode/price_info.py
index da99a6bf..d518e2a3 100644
--- a/rootfs/standard/var/www/mynode/price_info.py
+++ b/rootfs/standard/var/www/mynode/price_info.py
@@ -16,11 +16,14 @@ def get_latest_price():
def get_price_diff_24hrs():
global price_data
- latest = get_latest_price()
- if len(price_data) > 0:
- old = price_data[0]["price"]
- if latest != "N/A" and old != "N/A":
- return latest - old
+ try:
+ latest = get_latest_price()
+ if len(price_data) > 0:
+ old = price_data[0]["price"]
+ if latest != "N/A" and old != "N/A":
+ return latest - old
+ except Exception as e:
+ log_message("ERROR get_price_diff_24hrs: {}".format(str(e)))
return 0.0
def get_price_up_down_flat_24hrs():
diff --git a/rootfs/standard/var/www/mynode/templates/lnd.html b/rootfs/standard/var/www/mynode/templates/lnd.html
index e5a92346..0c031d3b 100644
--- a/rootfs/standard/var/www/mynode/templates/lnd.html
+++ b/rootfs/standard/var/www/mynode/templates/lnd.html
@@ -107,21 +107,6 @@
$("#copy_lit_password").show();
}
- $('#watchtower_enabled_checkbox').change(function () {
- $("#watchtower_enabled_save").show();
- });
- $("#watchtower_enabled_save").on("click", function() {
- enabled=$('#watchtower_enabled_checkbox').is(":checked")
- if (enabled)
- {
- window.location.href='/lnd/set_watchtower_enabled?enabled=1'
- }
- else
- {
- window.location.href='/lnd/set_watchtower_enabled?enabled=0'
- }
- });
-
function show_deposit_address(addr) {
$("#lnd_deposit_address_text").html( addr );
$("#lnd_deposit_address_qrcode").attr("src", "/api/get_qr_code_image?url="+addr);
@@ -257,14 +242,14 @@
{% endif %}
{% if wallet_exists %}
- TLS Certification
+ TLS Certificate
download
regenerate
- Macaroon Download
+ Macaroons
admin
readonly
@@ -280,14 +265,9 @@
{% endif %}
- Watchtower Server
+ Watchtower
-
-
-
-
-
- Save
+ open
@@ -304,10 +284,12 @@
{{alias}}
+
{% endif %}
@@ -348,16 +330,6 @@
{% endif %}
-{% if wallet_logged_in and watchtower_enabled %}
-
-
-
-
-
{{watchtower_text}}
-
-
-{% endif %}
-
{% if wallet_exists and not wallet_logged_in %}
diff --git a/rootfs/standard/var/www/mynode/templates/lnd_watchtower.html b/rootfs/standard/var/www/mynode/templates/lnd_watchtower.html
new file mode 100644
index 00000000..ae1a22f4
--- /dev/null
+++ b/rootfs/standard/var/www/mynode/templates/lnd_watchtower.html
@@ -0,0 +1,209 @@
+
+
+
{{ title }}
+ {% include 'includes/head.html' %}
+
+ {% if refresh_rate is defined and refresh_rate is not none %}
+
+ {% endif %}
+
+
+
+
+
+ {% include 'includes/logo_header.html' %}
+
+
+ {% include 'includes/message_display.html' %}
+
+
Watchtower Server
+
+
+
+
+ {% if watchtower_server_enabled %}
+
+
+
+
+ {{ watchtower_server_uri }}
+
+
+
+ {% endif %}
+
+
+
+
Watchtower Client
+
+
+
+
+
+
+
+
+ {% if watchtower_client_towers and watchtower_client_towers|length > 0%}
+
+
+ URI(s)
+ Sessions
+
+
+ {% for tower in watchtower_client_towers %}
+
+
+
+
+ {% set first = True %}
+ {% for addr in tower.addresses %}
+ {% if first %} {% set first = False %} {% else %} {% endif %}
+ {{tower.pubkey_b16}}@{{addr}}
+ {% endfor %}
+
+
+ {{tower.num_sessions}}
+
+
+ {% endfor %}
+
+ {% else %}
+ No active client towers
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Backups
+ {{watchtower_client_stats.num_backups}}
+
+
+ Pending Backups
+ {{watchtower_client_stats.num_pending_backups}}
+
+
+ Failed Updates
+ {{watchtower_client_stats.num_failed_backups}}
+
+
+ Sessions Acquired
+ {{watchtower_client_stats.num_sessions_acquired}}
+
+
+ Sessions Exhausted
+ {{watchtower_client_stats.num_sessions_exhausted}}
+
+
+
+
+
+
+
+
+
+
+
+
+ Max Updates
+ {{watchtower_client_policy.max_updates}}
+
+
+ Sweep sat/byte
+ {{watchtower_client_policy.sweep_sat_per_byte}}
+
+
+ Sweep sat/vbyte
+ {{watchtower_client_policy.sweep_sat_per_vbyte}}
+
+
+
+
+
+
+ {% include 'includes/footer.html' %}
+
+