Add Watchtower Page

This commit is contained in:
Taylor Helsper 2022-03-13 22:52:26 -05:00
parent 30110f1994
commit 75ee479249
6 changed files with 362 additions and 90 deletions

View File

@ -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

View File

@ -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 += "<br/>"
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():

View File

@ -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

View File

@ -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():

View File

@ -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 %}
<tr>
<th>TLS Certification</th>
<th>TLS Certificate</th>
<td>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="/lnd/tls.cert">download</a>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="/lnd/regen_tls_cert">regenerate</a>
</td>
</tr>
<tr>
<th>Macaroon Download</th>
<th>Macaroons</th>
<td>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" id="admin_macaroon" href="/lnd/admin.macaroon">admin</a>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" id="readonly_macaroon" href="/lnd/readonly.macaroon">readonly</a>
@ -280,14 +265,9 @@
</tr>
{% endif %}
<tr>
<th>Watchtower Server</th>
<th>Watchtower</th>
<td>
<label class="switch">
<input type="checkbox" id="watchtower_enabled_checkbox" {% if watchtower_enabled %}checked{% endif %}>
<span class="slider round"></span>
</label>
<br/>
<button id="watchtower_enabled_save" style="display: none; margin-top: 5px;" class="ui-button ui-widget ui-corner-all settings_button_small">Save</button>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="/lnd/watchtower">open</a>
</td>
</tr>
<tr>
@ -304,10 +284,12 @@
{{alias}}
</td>
</tr>
<!--
<tr>
<th>Ports (gRPC/REST)</th>
<td>10009 / 10080</td>
</tr>
-->
{% endif %}
</table>
</div>
@ -348,16 +330,6 @@
</div>
{% endif %}
{% if wallet_logged_in and watchtower_enabled %}
<!-- URI Tile / Row -->
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Watchtower Server Pubkey/URI</div>
<div class="info_tile_contents" style="font-size: 12px;">{{watchtower_text}}</div>
</div>
</div>
{% endif %}
{% if wallet_exists and not wallet_logged_in %}
<!-- Status Tile / Row (should be only shown short term) -->
<div class="app_tile_row">

View File

@ -0,0 +1,209 @@
<!DOCTYPE html lang="en">
<head>
<title>{{ title }}</title>
{% include 'includes/head.html' %}
{% if refresh_rate is defined and refresh_rate is not none %}
<meta http-equiv="refresh" content="{{ refresh_rate }}">
{% endif %}
<script>
function remove_tower(tower) {
if ( confirm("Are you sure you want to remove tower "+tower+"?") ) {
window.location = location.protocol+'//'+location.hostname+"/lnd/watchtower/remove_tower?tower="+tower
}
}
$(document).ready(function() {
$('#watchtower_server_enabled_checkbox').change(function () {
$("#watchtower_server_enabled_save").show();
});
$("#watchtower_server_enabled_save").on("click", function() {
enabled=$('#watchtower_server_enabled_checkbox').is(":checked")
if (enabled)
{
window.location.href='/lnd/watchtower/set_watchtower_enabled?enabled=1'
}
else
{
window.location.href='/lnd/watchtower/set_watchtower_enabled?enabled=0'
}
});
});
</script>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/"><span class="ui-icon ui-icon-home"></span>home&nbsp;</a>
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/lnd"><span class="ui-icon ui-icon-arrowthick-1-w"></span>back&nbsp;</a>
</div>
{% include 'includes/message_display.html' %}
<div class="main_header">Watchtower Server</div>
<br/>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Status</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Enabled</th>
<td>
<label class="switch">
<input type="checkbox" id="watchtower_server_enabled_checkbox" {% if watchtower_server_enabled %}checked{% endif %}>
<span class="slider round"></span>
</label>
<br/>
<button id="watchtower_server_enabled_save" style="display: none; margin-top: 5px;" class="ui-button ui-widget ui-corner-all settings_button_small">Save</button>
</td>
</tr>
</table>
</div>
</div>
</div>
{% if watchtower_server_enabled %}
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">URI / Pubkey</div>
<div class="info_tile_contents" style="font-size: 12px;">
{{ watchtower_server_uri }}
</div>
</div>
</div>
{% endif %}
<br/>
<div class="main_header">Watchtower Client</div>
<br/>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Status</div>
<div class="info_tile_contents">
Enabled
</div>
</div>
</div>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Active Towers</div>
<div class="info_tile_contents">
{% if watchtower_client_towers and watchtower_client_towers|length > 0%}
<table class="info_table" style="font-size: 12px;" cellpadding=5>
<tr>
<th><b>URI(s)</b></th>
<th><b>Sessions</b></th>
<th>&nbsp;</th>
</tr>
{% for tower in watchtower_client_towers %}
<!--
{'pubkey': 'A7MWka/imgKNHBo2swdaVXMRBcOopDqzvMIO+rNN7DZI',
'addresses': ['553e5qijayrejjtmzabwibaesge6lb34jegkn7fwvfcszl35a3uj24id.onion:9911'],
'active_session_candidate': False,
'num_sessions': 2,
'sessions':
[{'num_backups': 58,
'num_pending_backups': 0,
'max_backups': 1024,
'sweep_sat_per_byte': 10,
'sweep_sat_per_vbyte': 10},
{'num_backups': 0,
'num_pending_backups': 0,
'max_backups': 1024,
'sweep_sat_per_byte': 10,
'sweep_sat_per_vbyte': 10}],
'pubkey_b16': b'03B31691AFE29A028D1C1A36B3075A55731105C3A8A43AB3BCC20EFAB34DEC3648'}
-->
<tr>
<td>
<small>
{% set first = True %}
{% for addr in tower.addresses %}
{% if first %} {% set first = False %} {% else %} <br/> {% endif %}
{{tower.pubkey_b16}}@{{addr}}
{% endfor %}
</small>
</td>
<td>{{tower.num_sessions}}</td>
<td><button onclick="remove_tower('{{tower.pubkey_b16}}')" class="ui-button ui-widget ui-corner-all mynode_button_small red"><span class="ui-icon ui-icon-trash"></span></button></td>
</tr>
{% endfor %}
</table>
{% else %}
No active client towers
{% endif %}
<br/><br/>
<form action="/lnd/watchtower/add_tower" method="post">
<input type="text" id="new_tower" name="new_tower" class="text ui-widget-content ui-corner-all" size="100" style="font-size: 14px;" placeholder="pubkey@address:9911"/>
<input type="submit" class="ui-button ui-widget ui-corner-all mynode_button_small" value="Add">
</form>
</div>
</div>
</div>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Stats</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Backups</th>
<td>{{watchtower_client_stats.num_backups}}</td>
</tr>
<tr>
<th>Pending Backups</th>
<td>{{watchtower_client_stats.num_pending_backups}}</td>
</tr>
<tr>
<th>Failed Updates</th>
<td>{{watchtower_client_stats.num_failed_backups}}</td>
</tr>
<tr>
<th>Sessions Acquired</th>
<td>{{watchtower_client_stats.num_sessions_acquired}}</td>
</tr>
<tr>
<th>Sessions Exhausted</th>
<td>{{watchtower_client_stats.num_sessions_exhausted}}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Policy</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Max Updates</th>
<td>{{watchtower_client_policy.max_updates}}</td>
</tr>
<tr>
<th>Sweep sat/byte</th>
<td>{{watchtower_client_policy.sweep_sat_per_byte}}</td>
</tr>
<tr>
<th>Sweep sat/vbyte</th>
<td>{{watchtower_client_policy.sweep_sat_per_vbyte}}</td>
</tr>
</table>
</div>
</div>
</div>
{% include 'includes/footer.html' %}
</body>
</html>