Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor setup state and node timeout #1058

Merged
merged 4 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/cryptoadvance/specter/bitcoind.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,18 @@ def start_bitcoind(
extra_args=extra_args,
)

self.wait_for_bitcoind(self.rpcconn, timeout=timeout)
try:
self.wait_for_bitcoind(self.rpcconn, timeout=timeout)
self.status = "Running"
except ExtProcTimeoutException as e:
self.status = "Starting up"
raise e
except Exception as e:
self.status = "Error"
raise e
if self.network == "regtest":
self.mine(block_count=100)

return self.rpcconn

def version(self):
Expand Down Expand Up @@ -284,6 +293,7 @@ def __init__(
)
self.bitcoind_path = bitcoind_path
self.rpcconn.ipaddress = "localhost"
self.status = "Down"
except Exception as e:
logger.exception(
f"Failed to instantiate BitcoindPlainController. Error: {e}"
Expand Down Expand Up @@ -348,14 +358,18 @@ def cleanup_bitcoind(self, cleanup_hard=None, datadir=None):

def stop_bitcoind(self):
self.cleanup_bitcoind()
self.status = "Down"

def check_existing(self):
"""other then in docker, we won't check on the "instance-level". This will return true if if a
bitcoind is running on the default port.
"""
if not self.check_bitcoind(self.rpcconn):
if self.status == "Running":
self.status = "Down"
return None
else:
self.status = "Running"
return True


Expand Down
72 changes: 21 additions & 51 deletions src/cryptoadvance/specter/server_endpoints/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ def server_error_timeout(e):
# make sure specter knows that rpc is not there
app.specter.check()
app.logger.error("ExternalProcessTimeoutException: %s" % e)
return render_template("500_timeout.jinja", error=e, loglines=e.loglines), 500
flash("Bitcoin Core node is starting up, please refresh in a few seconds")
return redirect(url_for("settings_endpoint.bitcoin_core"))


########## on every request ###############
Expand Down Expand Up @@ -154,13 +155,8 @@ def about():
@app.route("/node_setup_wizard/<step>", methods=["GET", "POST"])
@login_required
def node_setup_wizard(step):
app.specter.config["bitcoind_setup"]["error"] = ""
app.specter.config["bitcoind_setup"]["stage"] = ""
app.specter.config["bitcoind_setup"]["stage_progress"] = -1
app.specter.config["torbrowser_setup"]["error"] = ""
app.specter.config["torbrowser_setup"]["stage"] = ""
app.specter.config["torbrowser_setup"]["stage_progress"] = -1
app.specter._save()
app.specter.reset_setup("bitcoind")
app.specter.reset_setup("torbrowser")

return render_template(
"node_setup_wizard.jinja", step=step, specter=app.specter, rand=rand
Expand All @@ -170,45 +166,38 @@ def node_setup_wizard(step):
@app.route("/setup_bitcoind", methods=["POST"])
@login_required
def setup_bitcoind():
bitcoind_setup_status = app.specter.config.get(
"bitcoind_setup", {"stage_progress": -1}
)
app.specter.config["rpc"]["datadir"] = request.form.get(
"bitcoin_core_datadir", app.specter.config["rpc"]["datadir"]
)
app.specter._save()
if os.path.exists(app.specter.config["rpc"]["datadir"]):
if request.form["override_data_folder"] != "true":
return {"error": "data folder already exists"}
if (
not os.path.isfile(app.specter.bitcoind_path)
and bitcoind_setup_status["stage_progress"] == -1
and app.specter.setup_status["bitcoind"]["stage_progress"] == -1
):
app.specter.config["bitcoind_setup"]["stage_progress"] = 0
app.specter.config["bitcoind_setup"]["error"] = ""
app.specter._save()
app.specter.update_setup_status("bitcoind", "STARTING_SETUP")
t = threading.Thread(
target=setup_bitcoind_thread,
args=(app.specter, app.config["INTERNAL_BITCOIND_VERSION"]),
)
t.start()
elif os.path.isfile(app.specter.bitcoind_path):
return {"error": "bitcoind already installed"}
elif bitcoind_setup_status["stage_progress"] != -1:
elif app.specter.setup_status["bitcoind"]["stage_progress"] != -1:
return {"error": "bitcoind installation is still under progress"}
return {"success": "Starting Bitcoin Core setup!"}


@app.route("/setup_bitcoind_directory", methods=["POST"])
@login_required
def setup_bitcoind_directory():
bitcoind_setup_status = app.specter.config.get(
"bitcoind_setup", {"stage_progress": -1}
)
if (
os.path.isfile(app.specter.bitcoind_path)
and bitcoind_setup_status["stage_progress"] == -1
and app.specter.setup_status["bitcoind"]["stage_progress"] == -1
):
app.specter.config["bitcoind_setup"]["stage_progress"] = 0
app.specter.config["bitcoind_setup"]["error"] = ""
app.specter._save()
app.specter.update_setup_status("bitcoind", "STARTING_SETUP")
quicksync = request.form["quicksync"] == "true"
pruned = request.form["nodetype"] == "pruned"
t = threading.Thread(
Expand All @@ -218,64 +207,45 @@ def setup_bitcoind_directory():
t.start()
elif not os.path.isfile(app.specter.bitcoind_path):
return {"error": "bitcoind in not installed but required for this step"}
elif bitcoind_setup_status["stage_progress"] != -1:
elif app.specter.setup_status["bitcoind"]["stage_progress"] != -1:
return {"error": "bitcoind installation is still under progress"}
return {"success": "Starting Bitcoin Core setup!"}


@app.route("/setup_tor", methods=["POST"])
@login_required
def setup_tor():
tor_setup_status = app.specter.config.get(
"torbrowser_setup", {"stage_progress": -1}
)
if (
not os.path.isfile(app.specter.torbrowser_path)
and tor_setup_status["stage_progress"] == -1
and app.specter.setup_status["torbrowser"]["stage_progress"] == -1
):
app.specter.config["torbrowser_setup"]["stage_progress"] = 0
app.specter.config["torbrowser_setup"]["error"] = ""
app.specter.setup_status["torbrowser"]["stage_progress"] = 0
app.specter.setup_status["torbrowser"]["error"] = ""
app.specter._save()
t = threading.Thread(
target=setup_tor_thread,
args=(app.specter,),
)
t.start()
elif os.path.isfile(app.specter.torbrowser_path):
return {"error": "tor is already installed"}
elif tor_setup_status["stage_progress"] != -1:
return {"error": "tor installation is still under progress"}
return {"error": "Tor is already installed"}
elif app.specter.setup_status["torbrowser"]["stage_progress"] != -1:
return {"error": "Tor installation is still under progress"}
return {"success": "Starting Tor setup!"}


@app.route("/get_node_setup_status")
@login_required
@app.csrf.exempt
def get_node_setup_status():
return app.specter.config.get(
"bitcoind_setup",
{
"installed": os.path.isfile(app.specter.bitcoind_path),
"stage_progress": -1,
"stage": "none",
"error": "",
},
)
return app.specter.get_setup_status("bitcoind")


@app.route("/get_tor_setup_status")
@login_required
@app.csrf.exempt
def get_tor_setup_status():
return app.specter.config.get(
"torbrowser_setup",
{
"installed": os.path.isfile(app.specter.torbrowser_path),
"stage_progress": -1,
"stage": "none",
"error": "",
},
)
return app.specter.get_setup_status("torbrowser")


# TODO: Move all these below to REST API
Expand Down
4 changes: 1 addition & 3 deletions src/cryptoadvance/specter/server_endpoints/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,7 @@ def tor():
if not current_user.is_admin:
flash("Only an admin is allowed to access this page.", "error")
return redirect("")
app.specter.config["torbrowser_setup"]["stage"] = ""
app.specter.config["torbrowser_setup"]["stage_progress"] = -1
app.specter._save()
app.specter.reset_setup("torbrowser")
current_version = notify_upgrade(app, flash)
proxy_url = app.specter.proxy_url
only_tor = app.specter.only_tor
Expand Down
61 changes: 53 additions & 8 deletions src/cryptoadvance/specter/specter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from stem.control import Controller
from .specter_error import SpecterError, ExtProcTimeoutException
from sys import exit
from .util.setup_states import SETUP_STATES

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -141,7 +142,7 @@ def __init__(self, data_folder="./data", config={}):
"proxy_url": "socks5h://localhost:9050", # Tor proxy URL
"only_tor": False,
"tor_control_port": "",
"tor_status": False,
"tor_status": False, # Should start Tor hidden service on startup?
"hwi_bridge_url": "/hwi/api/",
# unique id that will be used in wallets path in Bitcoin Core
# empty by default for backward-compatibility
Expand All @@ -157,12 +158,6 @@ def __init__(self, data_folder="./data", config={}):
"fee_estimator_custom_url": "",
"bitcoind": False,
"bitcoind_internal_version": "",
"bitcoind_setup": {
"stage_progress": -1,
},
"torbrowser_setup": {
"stage_progress": -1,
},
}

self.torbrowser_path = os.path.join(
Expand All @@ -178,6 +173,20 @@ def __init__(self, data_folder="./data", config={}):

self._bitcoind = None
self._tor_daemon = None

self.node_status = None
self.setup_status = {
"bitcoind": {
"stage_progress": -1,
"stage": "",
"error": "",
},
"torbrowser": {
"stage_progress": -1,
"stage": "",
"error": "",
},
}
# health check: loads config, tests rpc
# also loads and checks wallets for all users
try:
Expand Down Expand Up @@ -218,7 +227,10 @@ def __init__(self, data_folder="./data", config={}):
)
logger.error(e.get_logger_friendly())
finally:
self.set_bitcoind_pid(self.bitcoind.bitcoind_proc.pid)
try:
self.set_bitcoind_pid(self.bitcoind.bitcoind_proc.pid)
except Exception as e:
logger.error(e)
self.update_tor_controller()
self.checker = Checker(lambda: self.check(check_all=True), desc="health")
self.checker.start()
Expand Down Expand Up @@ -544,6 +556,39 @@ def set_bitcoind_pid(self, pid):
self.config["bitcoind"] = pid
self._save()

def update_setup_status(self, software_name, stage):
self.setup_status[software_name]["error"] = ""
if stage in SETUP_STATES:
self.setup_status[software_name]["stage"] = SETUP_STATES[stage].get(
software_name, stage
)
else:
self.setup_status[software_name]["stage"] = stage
self.setup_status[software_name]["stage_progress"] = 0

def update_setup_download_progress(self, software_name, progress):
self.setup_status[software_name]["error"] = ""
self.setup_status[software_name]["stage_progress"] = progress

def update_setup_error(self, software_name, error):
self.setup_status[software_name]["error"] = error
self.setup_status[software_name]["stage_progress"] = -1

def reset_setup(self, software_name):
self.setup_status[software_name]["error"] = ""
self.setup_status[software_name]["stage"] = "stage"
self.setup_status[software_name]["stage_progress"] = -1

def get_setup_status(self, software_name):
if software_name == "bitcoind":
installed = os.path.isfile(self.bitcoind_path)
elif software_name == "torbrowser":
installed = os.path.isfile(self.torbrowser_path)
else:
installed = False

return {"installed": installed, **self.setup_status[software_name]}

def update_use_external_node(self, use_external_node):
""" set whatever specter should connect to internal or external node """
self.config["rpc"]["external_node"] = use_external_node
Expand Down
20 changes: 18 additions & 2 deletions src/cryptoadvance/specter/templates/node_setup_wizard.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@
<br>
<button type="button" class="btn hidden action" id="show-progress-details" style="max-height: 36px;" onclick="showPageOverlay('stage-progress-details');">View Progress</button>
<h1 id="stage-progress-details" class="hidden" style="margin: auto;"></h1>
<div id="override-data-folder-popup" class="hidden" style="margin: auto;">
<p>Your Bitcoin Core data folder seems to be already used.<br>
Would you like to override it or select a different folder path?
</p><br>
<div class="row">
<button type="button" class="btn" onclick="hidePageOverlay()">Edit path</button>&nbsp;&nbsp;
<button type="button" class="btn" onclick="hidePageOverlay();setupBitcoind(true)">Override existing</button>
</div>
</div>
<br><br>
{% endblock %}

Expand Down Expand Up @@ -250,12 +259,13 @@
}
}

async function setupBitcoind() {
async function setupBitcoind(override=false) {
torSetup = false;
let url = "{{ url_for('setup_bitcoind') }}"
var formData = new FormData();
formData.append('csrf_token', '{{ csrf_token() }}');
formData.append('bitcoin_core_datadir', document.getElementById('bitcoin_core_datadir').value);
formData.append('override_data_folder', override);
try {
const response = await fetch(
url,
Expand All @@ -278,6 +288,12 @@
showNotification('Bitcoin Core already installed, skipped step.');
showStep(++currentStep);
return;
} else if (jsonResponse.error == "data folder already exists") {
let advancedSettings = document.getElementById(`advanced_settings_3`);
if (advancedSettings.classList.contains("hidden")) {
toggleAdvanced(3);
}
showPageOverlay("override-data-folder-popup");
}
showError(jsonResponse.error, 4000);
} catch(e) {
Expand Down Expand Up @@ -351,7 +367,7 @@
return;
}
document.getElementById('stage-progress-details').innerHTML = `${stage}<br>`;
if (progress > 0) {
if (progress > 0 && progress < 100) {
document.getElementById('stage-progress-details').innerHTML += `<br><br><b style="font-size:0.8em;">Download Progress: ${progress}%</b>`;
}
setTimeout(fetchStageProgress, 1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<br><br>
{% endif %}
{% if bitcoind_exists %}
<h2>Built in Bitcoin Node Status: {% if is_running %}Running{% else %}Down{% endif %}</h2><br>
<h2>Built in Bitcoin Node Status: {% if specter.bitcoind.status %}{{specter.bitcoind.status}}{% else %}Down{% endif %}</h2><br>
<span class="note" style="font-size: 1.3em;">Bitcoin Core Version: {{specter.config.bitcoind_internal_version}}</span>
<br><br>
{% if not external_node %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
return;
}
document.getElementById('stage-progress-details').innerHTML = `${stage}<br>`;
if (progress > 0) {
if (progress > 0 && progress < 100) {
document.getElementById('stage-progress-details').innerHTML += `<br><br><b style="font-size:0.8em;">Download Progress: ${progress}%</b>`;
}
setTimeout(fetchStageProgress, 1000);
Expand Down