mynode/rootfs/standard/var/www/mynode/lightning_info.py

423 lines
14 KiB
Python
Raw Normal View History

2019-06-15 23:02:44 +00:00
import copy
import requests
import subprocess
import os
import time
import re
2020-11-08 21:13:03 +00:00
from flask import current_app as app
2019-06-15 23:02:44 +00:00
from threading import Timer
from utilities import *
2019-06-15 23:02:44 +00:00
from bitcoin_info import *
2020-10-08 02:59:44 +00:00
from systemctl_info import *
2019-06-15 23:02:44 +00:00
# Variables
lightning_info = None
lnd_ready = False
2019-08-01 04:27:16 +00:00
lnd_version = None
2021-01-16 18:15:52 +00:00
loop_version = None
pool_version = None
lightning_peers = None
2021-03-09 03:55:39 +00:00
lightning_peer_aliases = {}
lightning_channels = None
lightning_channel_balance = None
lightning_wallet_balance = None
lightning_desync_count = 0
2019-06-15 23:02:44 +00:00
LND_FOLDER = "/mnt/hdd/mynode/lnd/"
TLS_CERT_FILE = "/mnt/hdd/mynode/lnd/tls.cert"
LND_REST_PORT = "10080"
# Functions
def update_lightning_info():
global lightning_info
global lightning_peers
global lightning_channels
global lightning_channel_balance
global lightning_wallet_balance
global lightning_desync_count
2019-06-15 23:02:44 +00:00
global lnd_ready
# Get latest LN info
lightning_info = lnd_get("/getinfo")
# Set is LND ready
if lightning_info != None and "synced_to_chain" in lightning_info and lightning_info['synced_to_chain']:
lnd_ready = True
# Check for LND de-sync (this can happen unfortunately)
# See https://github.com/lightningnetwork/lnd/issues/1909
# See https://github.com/bitcoin/bitcoin/pull/14687
# Hopefully patch comes soon to enable TCP keepalive to prevent this from happening
if lnd_ready and lightning_info != None and "synced_to_chain" in lightning_info and not lightning_info['synced_to_chain']:
lightning_desync_count += 1
os.system("printf \"%s | LND De-sync!!! Count: {} \\n\" \"$(date)\" >> /tmp/lnd_failures".format(lightning_desync_count))
if lightning_desync_count >= 8:
os.system("printf \"%s | De-sync count too high! Retarting LND... \\n\" \"$(date)\" >> /tmp/lnd_failures")
restart_lnd()
lightning_desync_count = 0
return True
if lnd_ready:
if lightning_desync_count > 0:
os.system("printf \"%s | De-sync greater than 0 (was {}), but now synced! Setting to 0. \\n\" \"$(date)\" >> /tmp/lnd_failures".format(lightning_desync_count))
lightning_desync_count = 0
lightning_peers = lnd_get("/peers")
lightning_channels = lnd_get("/channels")
lightning_channel_balance = lnd_get("/balance/channels")
lightning_wallet_balance = lnd_get("/balance/blockchain")
2019-06-15 23:02:44 +00:00
return True
2021-03-17 04:31:46 +00:00
def get_lnd_deposit_address():
if os.path.isfile("/tmp/lnd_deposit_address"):
return get_file_contents("/tmp/lnd_deposit_address")
2021-03-17 04:31:46 +00:00
return get_new_lnd_deposit_address()
2021-03-17 04:31:46 +00:00
def get_new_lnd_deposit_address():
address = "NEW_ADDR"
try:
addressdata = lnd_get("/newaddress")
address = addressdata["address"]
set_file_contents("/tmp/lnd_deposit_address", address)
except:
address = "ERROR"
return address
2019-06-15 23:02:44 +00:00
def get_lightning_info():
global lightning_info
return copy.deepcopy(lightning_info)
def get_lightning_peers():
global lightning_peers
2021-03-17 04:31:46 +00:00
peerdata = copy.deepcopy(lightning_peers)
peers = []
if peerdata != None and "peers" in peerdata:
for p in peerdata["peers"]:
peer = p
if "bytes_recv" in p:
peer["bytes_recv"] = "{:.2f}".format(float(p["bytes_recv"]) / 1000 / 1000)
else:
peer["bytes_recv"] = "N/A"
if "bytes_sent" in p:
peer["bytes_sent"] = "{:.2f}".format(float(p["bytes_sent"]) / 1000 / 1000)
else:
peer["bytes_sent"] = "N/A"
if "sat_sent" in p:
peer["sat_sent"] = format_sat_amount(peer["sat_sent"])
if "sat_recv" in p:
peer["sat_recv"] = format_sat_amount(peer["sat_recv"])
if "ping_time" not in p:
peer["ping_time"] = "N/A"
if "pub_key" in p:
peer["alias"] = get_lightning_peer_alias( p["pub_key"] )
else:
peer["alias"] = "Unknown"
peers.append(peer)
return peers
2021-03-09 03:55:39 +00:00
def get_lightning_node_info(pubkey):
nodeinfo = lnd_get("/graph/node/{}".format(pubkey), timeout=2)
return nodeinfo
def get_lightning_peer_alias(pubkey):
global lightning_peer_aliases
if pubkey in lightning_peer_aliases:
return lightning_peer_aliases[pubkey]
nodeinfo = get_lightning_node_info(pubkey)
if nodeinfo != None and "node" in nodeinfo:
if "alias" in nodeinfo["node"]:
lightning_peer_aliases[pubkey] = nodeinfo["node"]["alias"]
return nodeinfo["node"]["alias"]
return "UNKNOWN"
2020-10-01 00:55:24 +00:00
def get_lightning_peer_count():
info = get_lightning_info()
num_peers = 0
2020-10-08 02:59:44 +00:00
if info != None and "num_peers" in info:
2020-10-01 00:55:24 +00:00
num_peers = info['num_peers']
return num_peers
def get_lightning_channels():
global lightning_channels
2021-03-17 04:31:46 +00:00
channeldata = copy.deepcopy(lightning_channels)
channels = []
if channeldata != None and "channels" in channeldata:
for c in channeldata["channels"]:
channel = c
if "capacity" in channel:
channel["capacity"] = format_sat_amount(channel["capacity"])
else:
channel["capacity"] = "N/A"
if "local_balance" in channel and "remote_balance" in channel:
l = float(channel["local_balance"])
r = float(channel["remote_balance"])
channel["chan_percent"] = (l / (l+r)) * 100
else:
channel["chan_percent"] = "0"
if "local_balance" in channel:
channel["local_balance"] = format_sat_amount(channel["local_balance"])
else:
channel["local_balance"] = "0"
if "remote_balance" in channel:
channel["remote_balance"] = format_sat_amount(channel["remote_balance"])
else:
channel["remote_balance"] = "0"
if "remote_pubkey" in channel:
channel["remote_alias"] = get_lightning_peer_alias( channel["remote_pubkey"] )
else:
channel["remote_alias"] = "Unknown"
channels.append(channel)
return channels
2020-10-01 00:55:24 +00:00
def get_lightning_channel_count():
2021-03-17 04:31:46 +00:00
channels = get_lightning_channels()
return len(channels)
2020-10-01 00:55:24 +00:00
def get_lightning_channel_balance():
global lightning_channel_balance
return copy.deepcopy(lightning_channel_balance)
def get_lightning_wallet_balance():
global lightning_wallet_balance
return copy.deepcopy(lightning_wallet_balance)
2020-10-01 00:55:24 +00:00
def get_lightning_balance_info():
channel_balance_data = get_lightning_channel_balance()
wallet_balance_data = get_lightning_wallet_balance()
balance_data = {}
balance_data["channel_balance"] = "N/A"
balance_data["channel_pending"] = "N/A"
balance_data["wallet_balance"] = "N/A"
balance_data["wallet_pending"] = "N/A"
channel_balance_data = get_lightning_channel_balance()
if channel_balance_data != None and "balance" in channel_balance_data:
balance_data["channel_balance"] = format_sat_amount( channel_balance_data["balance"] )
2020-10-01 00:55:24 +00:00
if channel_balance_data != None and "pending_open_balance" in channel_balance_data:
balance_data["channel_pending"] = format_sat_amount( channel_balance_data["pending_open_balance"] )
2020-10-01 00:55:24 +00:00
wallet_balance_data = get_lightning_wallet_balance()
if wallet_balance_data != None and "confirmed_balance" in wallet_balance_data:
balance_data["wallet_balance"] = format_sat_amount( wallet_balance_data["confirmed_balance"] )
2020-10-01 00:55:24 +00:00
if wallet_balance_data != None and "unconfirmed_balance" in wallet_balance_data:
balance_data["wallet_pending"] = format_sat_amount( wallet_balance_data["unconfirmed_balance"] )
2020-10-01 00:55:24 +00:00
return balance_data
2019-06-15 23:02:44 +00:00
def is_lnd_ready():
global lnd_ready
return lnd_ready
2021-03-09 03:55:39 +00:00
def lnd_get(path, timeout=10):
2019-06-15 23:02:44 +00:00
try:
macaroon = get_macaroon()
headers = {"Grpc-Metadata-macaroon":macaroon}
2021-03-09 03:55:39 +00:00
r = requests.get("https://localhost:"+LND_REST_PORT+"/v1"+path, verify=TLS_CERT_FILE,headers=headers, timeout=timeout)
2019-06-15 23:02:44 +00:00
except Exception as e:
2020-11-08 21:13:03 +00:00
app.logger.info("ERROR in lnd_get: "+str(e))
return {"error": str(e)}
2019-06-15 23:02:44 +00:00
return r.json()
def gen_new_wallet_seed():
seed = subprocess.check_output("python3 /usr/bin/gen_seed.py", shell=True)
return seed
def restart_lnd_actual():
2019-07-03 21:24:01 +00:00
global lnd_ready
lnd_ready = False
2019-06-15 23:02:44 +00:00
os.system("systemctl restart lnd")
os.system("systemctl restart lnd_unlock")
2019-11-04 04:50:47 +00:00
os.system("systemctl restart lnd_admin")
2019-06-15 23:02:44 +00:00
def restart_lnd():
t = Timer(1.0, restart_lnd_actual)
t.start()
2021-03-09 03:55:39 +00:00
def is_testnet_enabled():
return os.path.isfile("/mnt/hdd/mynode/settings/.testnet_enabled")
def get_lightning_wallet_file():
if is_testnet_enabled():
return "/mnt/hdd/mynode/lnd/data/chain/bitcoin/testnet/wallet.db"
return "/mnt/hdd/mynode/lnd/data/chain/bitcoin/mainnet/wallet.db"
def get_lightning_macaroon_file():
if is_testnet_enabled():
return "/mnt/hdd/mynode/lnd/data/chain/bitcoin/testnet/admin.macaroon"
return "/mnt/hdd/mynode/lnd/data/chain/bitcoin/mainnet/admin.macaroon"
2019-06-15 23:02:44 +00:00
def get_macaroon():
2021-03-09 03:55:39 +00:00
m = subprocess.check_output("xxd -ps -u -c 1000 " + get_lightning_macaroon_file(), shell=True)
2019-06-15 23:02:44 +00:00
return m.strip()
def lnd_wallet_exists():
2021-03-09 03:55:39 +00:00
return os.path.isfile( get_lightning_wallet_file() )
2019-06-15 23:02:44 +00:00
def create_wallet(seed):
try:
subprocess.check_call("create_lnd_wallet.tcl \""+seed+"\"", shell=True)
# Sync FS and sleep so the success redirect understands the wallet was created
os.system("sync")
time.sleep(2)
return True
except:
return False
def is_lnd_logged_in():
try:
macaroon = get_macaroon()
headers = {"Grpc-Metadata-macaroon":macaroon}
r = requests.get("https://localhost:"+LND_REST_PORT+"/v1/getinfo", verify=TLS_CERT_FILE,headers=headers)
if r.status_code == 200 and r.json():
return True
return False
except:
return False
2021-03-09 03:55:39 +00:00
def get_lnd_channel_backup_file():
if is_testnet_enabled():
return "/home/bitcoin/lnd_backup/channel_testnet.backup"
return "/home/bitcoin/lnd_backup/channel.backup"
2019-06-15 23:02:44 +00:00
def lnd_channel_backup_exists():
2021-03-09 03:55:39 +00:00
return os.path.isfile( get_lnd_channel_backup_file() )
2019-06-15 23:02:44 +00:00
def get_lnd_status():
if not lnd_wallet_exists():
return "Please create wallet..."
2020-10-03 04:43:30 +00:00
if not is_bitcoind_synced():
return "Waiting..."
2019-06-15 23:02:44 +00:00
if is_lnd_ready():
return "Running"
2019-09-25 01:21:33 +00:00
try:
2020-05-01 17:48:31 +00:00
#log = subprocess.check_output("tail -n 100 /var/log/lnd.log", shell=True)
log = get_journalctl_log("lnd")
2019-09-25 01:21:33 +00:00
lines = log.splitlines()
2020-05-01 17:48:31 +00:00
#lines.reverse()
2019-09-25 01:21:33 +00:00
for line in lines:
if "Caught up to height" in line:
m = re.search("height ([0-9]+)", line)
height = m.group(1)
percent = 100.0 * (float(height) / bitcoin_block_height)
return "Syncing... {:.2f}%".format(percent)
elif "Waiting for chain backend to finish sync" in line:
return "Syncing..."
elif "Started rescan from block" in line:
return "Scanning..."
2020-05-09 03:35:44 +00:00
elif "Version: " in line:
return "Launching..."
elif "Opening the main database" in line:
return "Opening DB..."
elif "Database now open" in line:
return "DB open..."
2021-02-27 17:23:45 +00:00
elif "unable to create server" in line:
return "Network Error"
2020-05-09 03:35:44 +00:00
elif "Waiting for wallet encryption password" in line:
return "Logging in..."
elif "LightningWallet opened" in line:
return "Wallet open..."
2019-09-25 01:21:33 +00:00
return "Waiting..."
except:
return "Status Error"
2020-10-03 04:43:30 +00:00
def get_lnd_status_color():
if not is_bitcoind_synced():
return "yellow"
if not lnd_wallet_exists():
# This hides the restart /login attempt LND does from the GUI
return "green"
lnd_status_code = get_service_status_code("lnd")
if lnd_status_code != 0:
lnd_status_color = "red"
lnd_status = get_lnd_status()
if lnd_status == "Logging in...":
lnd_status_color = "yellow"
return "green"
2019-06-15 23:02:44 +00:00
def get_lnd_channels():
try:
macaroon = get_macaroon()
headers = {"Grpc-Metadata-macaroon":macaroon}
r = requests.get("https://localhost:"+LND_REST_PORT+"/v1/channels", verify=TLS_CERT_FILE,headers=headers)
if r.status_code == 200 and r.json():
data = r.json()
return data["channels"]
return False
except Exception as e:
print("EXCEPTION: {}".format(str(e)))
return False
2019-08-01 04:27:16 +00:00
def get_lnd_version():
global lnd_version
if lnd_version == None:
lnd_version = subprocess.check_output("lnd --version | egrep -o '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n 1", shell=True)
2020-10-01 00:55:24 +00:00
return "v{}".format(lnd_version)
2019-11-04 04:50:47 +00:00
2021-01-16 18:15:52 +00:00
def get_loop_version():
global loop_version
if loop_version == None:
loop_version = subprocess.check_output("loopd --version | egrep -o '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n 1", shell=True)
return "v{}".format(loop_version)
def get_pool_version():
global pool_version
if pool_version == None:
pool_version = subprocess.check_output("poold --version | egrep -o '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n 1", shell=True)
return "v{}".format(pool_version)
2020-08-21 00:56:31 +00:00
2019-11-04 04:50:47 +00:00
def get_default_lnd_config():
try:
with open("/usr/share/mynode/lnd.conf") as f:
return f.read()
except:
return "ERROR"
def get_lnd_config():
try:
with open("/mnt/hdd/mynode/lnd/lnd.conf") as f:
return f.read()
except:
return "ERROR"
2019-11-05 01:34:19 +00:00
def get_lnd_custom_config():
2019-11-04 04:50:47 +00:00
try:
2019-11-05 01:34:19 +00:00
with open("/mnt/hdd/mynode/settings/lnd_custom.conf") as f:
2019-11-04 04:50:47 +00:00
return f.read()
except:
return "ERROR"
2019-11-05 01:34:19 +00:00
def set_lnd_custom_config(config):
2019-11-04 04:50:47 +00:00
try:
2019-11-05 01:34:19 +00:00
with open("/mnt/hdd/mynode/settings/lnd_custom.conf", "w") as f:
2019-11-04 04:50:47 +00:00
f.write(config)
os.system("sync")
return True
except:
return False
2019-11-05 01:34:19 +00:00
def using_lnd_custom_config():
return os.path.isfile("/mnt/hdd/mynode/settings/lnd_custom.conf")
2019-11-05 01:34:19 +00:00
def delete_lnd_custom_config():
2020-05-20 03:01:14 +00:00
os.system("rm -f /mnt/hdd/mynode/settings/lnd_custom.conf")
def get_lnd_alias_file_data():
try:
with open("/mnt/hdd/mynode/settings/.lndalias", "r") as f:
return f.read().strip()
except:
return "ERROR"
return "ERROR"