feat: Add initial internal node option
All checks were successful
Build Docker / Build Image (push) Successful in 10m29s
All checks were successful
Build Docker / Build Image (push) Successful in 10m29s
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ cache/
|
||||
build/
|
||||
dist/
|
||||
hsd/
|
||||
hsd-data/
|
||||
hsd.lock
|
||||
|
||||
Binary file not shown.
@@ -124,6 +124,8 @@ SHOW_EXPIRED: Show expired domains (true/false)
|
||||
EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary)
|
||||
EXPLORER_TX: URL for exploring transactions (default https://shakeshift.com/transaction/)
|
||||
HSD_NETWORK: Network to connect to (main, regtest, simnet)
|
||||
DISABLE_WALLETDNS: Disable Wallet DNS records when sending HNS to domains (true/false)
|
||||
INTERNAL_HSD: Use internal HSD node (true/false)
|
||||
```
|
||||
|
||||
|
||||
|
||||
213
account.py
213
account.py
@@ -7,6 +7,11 @@ import re
|
||||
import domainLookup
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import atexit
|
||||
import signal
|
||||
import sys
|
||||
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
@@ -29,17 +34,33 @@ elif HSD_NETWORK == "regtest":
|
||||
HSD_WALLET_PORT = 14039
|
||||
HSD_NODE_PORT = 14037
|
||||
|
||||
INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"]
|
||||
if INTERNAL_NODE:
|
||||
HSD_INTERNAL_NODE = os.getenv("INTERNAL_HSD","false").lower() in ["1","true","yes"]
|
||||
if HSD_INTERNAL_NODE:
|
||||
if HSD_API == "":
|
||||
# Use a random API KEY
|
||||
HSD_API = "firewallet-" + str(int(time.time()))
|
||||
|
||||
HSD_IP = "localhost"
|
||||
|
||||
SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
|
||||
if SHOW_EXPIRED is None:
|
||||
SHOW_EXPIRED = False
|
||||
|
||||
HSD_PROCESS = None
|
||||
|
||||
# Get hsdconfig.json
|
||||
HSD_CONFIG = {}
|
||||
if not os.path.exists('hsdconfig.json'):
|
||||
# Pull from the latest git
|
||||
response = requests.get("https://git.woodburn.au/nathanwoodburn/firewalletbrowser/raw/branch/main/hsdconfig.json")
|
||||
if response.status_code == 200:
|
||||
with open('hsdconfig.json', 'w') as f:
|
||||
f.write(response.text)
|
||||
HSD_CONFIG = response.json()
|
||||
else:
|
||||
with open('hsdconfig.json') as f:
|
||||
HSD_CONFIG = json.load(f)
|
||||
|
||||
|
||||
hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
|
||||
hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
|
||||
|
||||
@@ -1439,7 +1460,6 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"):
|
||||
|
||||
def convertHNS(value: int):
|
||||
return value/1000000
|
||||
return value/1000000
|
||||
|
||||
|
||||
def get_node_api_url(path=''):
|
||||
@@ -1461,3 +1481,188 @@ def get_wallet_api_url(path=''):
|
||||
path = f'/{path}'
|
||||
return f"{base_url}{path}"
|
||||
return base_url
|
||||
|
||||
|
||||
|
||||
# region HSD Internal Node
|
||||
|
||||
|
||||
|
||||
def checkPreRequisites() -> dict[str, bool]:
|
||||
prerequisites = {
|
||||
"node": False,
|
||||
"npm": False,
|
||||
"git": False,
|
||||
"hsd": False
|
||||
}
|
||||
|
||||
# Check if node is installed and get version
|
||||
nodeSubprocess = subprocess.run(["node", "-v"], capture_output=True, text=True)
|
||||
if nodeSubprocess.returncode == 0:
|
||||
major_version = int(nodeSubprocess.stdout.strip().lstrip('v').split('.')[0])
|
||||
if major_version >= HSD_CONFIG.get("minNodeVersion", 20):
|
||||
prerequisites["node"] = True
|
||||
|
||||
# Check if npm is installed
|
||||
npmSubprocess = subprocess.run(["npm", "-v"], capture_output=True, text=True)
|
||||
if npmSubprocess.returncode == 0:
|
||||
major_version = int(npmSubprocess.stdout.strip().split('.')[0])
|
||||
if major_version >= HSD_CONFIG.get("minNPMVersion", 8):
|
||||
prerequisites["npm"] = True
|
||||
|
||||
# Check if git is installed
|
||||
gitSubprocess = subprocess.run(["git", "-v"], capture_output=True, text=True)
|
||||
if gitSubprocess.returncode == 0:
|
||||
prerequisites["git"] = True
|
||||
|
||||
# Check if hsd is installed
|
||||
if os.path.exists("./hsd/bin/hsd"):
|
||||
prerequisites["hsd"] = True
|
||||
|
||||
|
||||
|
||||
|
||||
return prerequisites
|
||||
|
||||
|
||||
|
||||
def hsdInit():
|
||||
if not HSD_INTERNAL_NODE:
|
||||
return
|
||||
prerequisites = checkPreRequisites()
|
||||
|
||||
PREREQ_MESSAGES = {
|
||||
"node": "Install Node.js from https://nodejs.org/en/download (Version >= {minNodeVersion})",
|
||||
"npm": "Install npm (version >= {minNPMVersion}) - usually comes with Node.js",
|
||||
"git": "Install Git from https://git-scm.com/downloads"}
|
||||
|
||||
|
||||
# Check if all prerequisites are met (except hsd)
|
||||
if not all(prerequisites[key] for key in prerequisites if key != "hsd"):
|
||||
print("HSD Internal Node prerequisites not met:")
|
||||
for key, value in prerequisites.items():
|
||||
if not value:
|
||||
print(f" - {key} is missing or does not meet the version requirement.")
|
||||
exit(1)
|
||||
return
|
||||
|
||||
# Check if hsd is installed
|
||||
if not prerequisites["hsd"]:
|
||||
print("HSD not found, installing...")
|
||||
# If hsd folder exists, remove it
|
||||
if os.path.exists("hsd"):
|
||||
os.rmdir("hsd")
|
||||
|
||||
# Clone hsd repo
|
||||
gitClone = subprocess.run(["git", "clone", "--depth", "1", "--branch", HSD_CONFIG.get("version", "latest"), "https://github.com/handshake-org/hsd.git", "hsd"], capture_output=True, text=True)
|
||||
if gitClone.returncode != 0:
|
||||
print("Failed to clone hsd repository:")
|
||||
print(gitClone.stderr)
|
||||
exit(1)
|
||||
print("Cloned hsd repository.")
|
||||
# Install hsd dependencies
|
||||
print("Installing hsd dependencies...")
|
||||
npmInstall = subprocess.run(["npm", "install"], cwd="hsd", capture_output=True, text=True)
|
||||
if npmInstall.returncode != 0:
|
||||
print("Failed to install hsd dependencies:")
|
||||
print(npmInstall.stderr)
|
||||
exit(1)
|
||||
print("Installed hsd dependencies.")
|
||||
|
||||
def hsdStart():
|
||||
global HSD_PROCESS
|
||||
if not HSD_INTERNAL_NODE:
|
||||
return
|
||||
|
||||
# Check if hsd was started in the last 30 seconds
|
||||
if os.path.exists("hsd.lock"):
|
||||
lock_time = os.path.getmtime("hsd.lock")
|
||||
if time.time() - lock_time < 30:
|
||||
print("HSD was started recently, skipping start.")
|
||||
return
|
||||
else:
|
||||
os.remove("hsd.lock")
|
||||
|
||||
print("Starting HSD...")
|
||||
# Create a lock file
|
||||
with open("hsd.lock", "w") as f:
|
||||
f.write(str(time.time()))
|
||||
|
||||
# Config lookups with defaults
|
||||
chain_migrate = HSD_CONFIG.get("chainMigrate", False)
|
||||
wallet_migrate = HSD_CONFIG.get("walletMigrate", False)
|
||||
spv = HSD_CONFIG.get("spv", False)
|
||||
|
||||
# Base command
|
||||
cmd = [
|
||||
"node",
|
||||
"./hsd/bin/hsd",
|
||||
f"--network={HSD_NETWORK}",
|
||||
f"--prefix={os.path.join(os.getcwd(), 'hsd-data')}",
|
||||
f"--api-key={HSD_API}",
|
||||
"--agent=FireWallet",
|
||||
"--http-host=127.0.0.1",
|
||||
"--log-console=false"
|
||||
]
|
||||
|
||||
# Conditionally add migration flags
|
||||
if chain_migrate:
|
||||
cmd.append(f"--chain-migrate={chain_migrate}")
|
||||
if wallet_migrate:
|
||||
cmd.append(f"--wallet-migrate={wallet_migrate}")
|
||||
if spv:
|
||||
cmd.append("--spv")
|
||||
|
||||
|
||||
# Launch process
|
||||
HSD_PROCESS = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=os.getcwd(),
|
||||
text=True
|
||||
)
|
||||
|
||||
print(f"HSD started with PID {HSD_PROCESS.pid}")
|
||||
|
||||
atexit.register(hsdStop)
|
||||
|
||||
# Handle Ctrl+C
|
||||
try:
|
||||
signal.signal(signal.SIGINT, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||
signal.signal(signal.SIGTERM, lambda s, f: (hsdStop(), sys.exit(0)))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def hsdStop():
|
||||
global HSD_PROCESS
|
||||
|
||||
if HSD_PROCESS is None:
|
||||
return
|
||||
|
||||
print("Stopping HSD...")
|
||||
|
||||
# Send SIGINT (like Ctrl+C)
|
||||
HSD_PROCESS.send_signal(signal.SIGINT)
|
||||
|
||||
try:
|
||||
HSD_PROCESS.wait(timeout=10) # wait for graceful exit
|
||||
print("HSD shut down cleanly.")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("HSD did not exit yet, is it alright???")
|
||||
|
||||
# Clean up lock file
|
||||
if os.path.exists("hsd.lock"):
|
||||
os.remove("hsd.lock")
|
||||
|
||||
HSD_PROCESS = None
|
||||
|
||||
def hsdRestart():
|
||||
hsdStop()
|
||||
time.sleep(2)
|
||||
hsdStart()
|
||||
|
||||
|
||||
checkPreRequisites()
|
||||
hsdInit()
|
||||
hsdStart()
|
||||
# endregion
|
||||
8
hsdconfig.json
Normal file
8
hsdconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version": "v8.0.0",
|
||||
"chainMigrate":4,
|
||||
"walletMigrate":7,
|
||||
"minNodeVersion":20,
|
||||
"minNpmVersion":8,
|
||||
"spv": true
|
||||
}
|
||||
24
main.py
24
main.py
@@ -1150,14 +1150,13 @@ def settings():
|
||||
|
||||
if not os.path.exists(".git"):
|
||||
return render_template("settings.html", account=account,
|
||||
|
||||
hsd_version=account_module.hsdVersion(False),
|
||||
error=error,success=success,version="Error")
|
||||
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
||||
info = gitinfo.get_git_info()
|
||||
if not info:
|
||||
return render_template("settings.html", account=account,
|
||||
hsd_version=account_module.hsdVersion(False),
|
||||
error=error,success=success,version="Error")
|
||||
error=error,success=success,version="Error",internal=account_module.HSD_INTERNAL_NODE)
|
||||
|
||||
branch = info['refs']
|
||||
if branch != "main":
|
||||
@@ -1172,7 +1171,7 @@ def settings():
|
||||
version += ' (New version available)'
|
||||
return render_template("settings.html", account=account,
|
||||
hsd_version=account_module.hsdVersion(False),
|
||||
error=error,success=success,version=version)
|
||||
error=error,success=success,version=version,internal=account_module.HSD_INTERNAL_NODE)
|
||||
|
||||
@app.route('/settings/<action>')
|
||||
def settings_action(action):
|
||||
@@ -1189,19 +1188,21 @@ def settings_action(action):
|
||||
if 'error' in resp:
|
||||
return redirect("/settings?error=" + str(resp['error']))
|
||||
return redirect("/settings?success=Rescan started")
|
||||
elif action == "resend":
|
||||
|
||||
if action == "resend":
|
||||
resp = account_module.resendTXs()
|
||||
if 'error' in resp:
|
||||
return redirect("/settings?error=" + str(resp['error']))
|
||||
return redirect("/settings?success=Resent transactions")
|
||||
|
||||
|
||||
elif action == "zap":
|
||||
if action == "zap":
|
||||
resp = account_module.zapTXs(request.cookies.get("account"))
|
||||
if type(resp) == dict and 'error' in resp:
|
||||
return redirect("/settings?error=" + str(resp['error']))
|
||||
return redirect("/settings?success=Zapped transactions")
|
||||
elif action == "xpub":
|
||||
|
||||
if action == "xpub":
|
||||
xpub = account_module.getxPub(request.cookies.get("account"))
|
||||
content = "<br><br>"
|
||||
content += f"<textarea style='display: none;' id='data' rows='4' cols='50'>{xpub}</textarea>"
|
||||
@@ -1212,6 +1213,12 @@ def settings_action(action):
|
||||
title="xPub Key",
|
||||
content=f"<code>{xpub}</code>{content}")
|
||||
|
||||
if action == "restart":
|
||||
resp = account_module.hsdRestart()
|
||||
return render_template("message.html", account=account,
|
||||
title="Restarting",
|
||||
content="The node is restarting. This may take a minute or two. You can close this window.")
|
||||
|
||||
return redirect("/settings?error=Invalid action")
|
||||
|
||||
@app.route('/settings/upload', methods=['POST'])
|
||||
@@ -1259,6 +1266,9 @@ def login():
|
||||
wallets = account_module.listWallets()
|
||||
wallets = render.wallets(wallets)
|
||||
|
||||
# If there are no wallets redirect to either register or import
|
||||
if len(wallets) == 0:
|
||||
return redirect("/welcome")
|
||||
|
||||
if 'message' in request.args:
|
||||
return render_template("login.html",
|
||||
|
||||
@@ -69,24 +69,30 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Node Settings</h4><small>HSD Version: v{{hsd_version}}</small>
|
||||
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
||||
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
||||
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
||||
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<h6 class="text-muted mb-2 card-subtitle">Settings that affect all wallets</h6><ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/rescan">Rescan</a>
|
||||
<h3>Rescan</h3><span>Rescan the blockchain for transactions</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/resend">Resend</a>
|
||||
<h3>Resend unconfirmed transactions</h3><span>Resend any transactions that haven't been mined yet.</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/zap">Zap</a>
|
||||
<h3>Delete unconfirmed transactions</h3><span>This will only remove pending tx from the wallet older than 20 minutes (~ 2 blocks)</span>
|
||||
</div>
|
||||
</li>
|
||||
{% if internal %}
|
||||
<li class="list-group-item">
|
||||
<div><a class="btn btn-primary stick-right" role="button" href="/settings/restart">Restart Node</a>
|
||||
<h3>Restart Internal Node</h3><span>This will attempt to restart the HSD node</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
47
templates/welcome.html
Normal file
47
templates/welcome.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-bs-theme="dark" lang="en-au" style="height: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Welcome to FireWallet</title>
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="900x768" href="/assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i&display=swap">
|
||||
<link rel="stylesheet" href="/assets/css/styles.min.css">
|
||||
</head>
|
||||
|
||||
<body class="d-flex align-items-center bg-gradient-primary" style="height: 100%;">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-9 col-lg-12 col-xl-10">
|
||||
<h1 class="text-center" style="color: var(--bs-danger);background: var(--bs-primary);">{{error}}</h1>
|
||||
<div class="card shadow-lg my-5 o-hidden border-0" style="padding-top: 50px;padding-bottom: 50px;">
|
||||
<div class="card-body p-0">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 d-none d-lg-flex">
|
||||
<div class="flex-grow-1 bg-login-image" style="background: url("/assets/img/favicon.png") center / contain no-repeat;"></div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="text-center p-5">
|
||||
<div class="text-center">
|
||||
<h4 class="mb-4">Welcome to FireWallet!</h4>
|
||||
</div>
|
||||
<div class="btn-group-vertical btn-group-lg gap-1" role="group"><a class="btn btn-primary" role="button" href="/register">Create a new wallet</a><a class="btn btn-primary" role="button" href="/import-wallet">Import an existing wallet</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/script.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user