Skip to content

Commit 857f25a

Browse files
moneymanolisk9ert
andauthored
Chore: Node manager refactoring (#1974)
* refactoring of node manager + tests Co-authored-by: k9ert <[email protected]>
1 parent 48bccfa commit 857f25a

File tree

8 files changed

+174
-129
lines changed

8 files changed

+174
-129
lines changed

src/cryptoadvance/specter/managers/node_manager.py

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def __init__(
3030
data_folder="",
3131
):
3232
self.nodes = {}
33+
# Dict is sth. like: {'nigiri_regtest': <Node name=Nigiri regtest fullpath=...>, 'default': <Node name=Bitcoin Core fullpath=...>}
3334
self.data_folder = data_folder
3435
self._active_node = active_node
3536
self.proxy_url = proxy_url
@@ -51,22 +52,23 @@ def load_from_disk(self, data_folder=None):
5152
# creating folders if they don't exist
5253
if not os.path.isdir(data_folder):
5354
os.mkdir(data_folder)
54-
nodes_files = load_jsons(self.data_folder, key="name")
55+
nodes_files = load_jsons(self.data_folder, key="alias")
5556
for node_alias in nodes_files:
5657
try:
57-
self.nodes[
58-
nodes_files[node_alias]["name"]
59-
] = PersistentObject.from_json(
58+
self.nodes[node_alias] = PersistentObject.from_json(
6059
nodes_files[node_alias],
6160
self,
62-
default_alias=node_alias,
61+
default_alias=nodes_files[node_alias]["alias"],
6362
default_fullpath=calc_fullpath(self.data_folder, node_alias),
6463
)
6564
except SpecterInternalException as e:
6665
logger.error(f"Skipping node {node_alias} due to {e}")
6766

6867
if not self.nodes:
6968
if os.environ.get("ELM_RPC_USER"):
69+
logger.debug(
70+
"Creating an external Elements node with the initial configuration."
71+
)
7072
self.add_external_node(
7173
node_type="ELM",
7274
name="Blockstream Liquid",
@@ -79,7 +81,9 @@ def load_from_disk(self, data_folder=None):
7981
protocol="http",
8082
default_alias=self.DEFAULT_ALIAS,
8183
)
82-
logger.info("Creating initial node-configuration")
84+
logger.debug(
85+
"Creating an external BTC node with the initial configuration."
86+
)
8387
self.add_external_node(
8488
node_type="BTC",
8589
name="Bitcoin Core",
@@ -93,46 +97,56 @@ def load_from_disk(self, data_folder=None):
9397
default_alias=self.DEFAULT_ALIAS,
9498
)
9599

96-
# Just to be sure here ....
100+
# Make sure we always have the default node
101+
# (needed for the rpc-as-pin-authentication used on Raspiblitz)
97102
has_default_node = False
98-
for name, node in self.nodes.items():
103+
for node in self.nodes.values():
99104
if node.alias == self.DEFAULT_ALIAS:
100-
return
101-
# Make sure we always have a default node
102-
# (needed for the rpc-as-pin-authentication, created and used for raspiblitz)
103-
self.add_external_node(
104-
node_type="BTC",
105-
name="Bitcoin Core",
106-
autodetect=True,
107-
datadir=get_default_datadir(),
108-
user="",
109-
password="",
110-
port=8332,
111-
host="localhost",
112-
protocol="http",
113-
default_alias=self.DEFAULT_ALIAS,
114-
)
105+
has_default_node = True
106+
# Recreate the default node if it doesn't exist anymore
107+
if not has_default_node:
108+
logger.debug("Recreating the default node.")
109+
self.add_external_node(
110+
node_type="BTC",
111+
name="Bitcoin Core",
112+
autodetect=True,
113+
datadir=get_default_datadir(),
114+
user="",
115+
password="",
116+
port=8332,
117+
host="localhost",
118+
protocol="http",
119+
default_alias=self.DEFAULT_ALIAS,
120+
)
115121

116122
@property
117-
def active_node(self):
123+
def active_node(self) -> Node:
118124
return self.get_by_alias(self._active_node)
119125

120126
@property
121-
def nodes_names(self):
122-
return sorted(self.nodes.keys())
127+
def nodes_names(self) -> list:
128+
"""Returns a list of the names (not the aliases) of the nodes"""
129+
return [node.name for node in self.nodes.values()]
123130

124-
def switch_node(self, node_alias):
125-
# this will throw an error if the node doesn't exist
131+
def switch_node(self, node_alias: str):
132+
# This will throw an error if the node doesn't exist
133+
logger.debug(f"Switching from {self._active_node} to {node_alias}.")
126134
self._active_node = self.get_by_alias(node_alias).alias
127135

128-
def default_node(self):
136+
def default_node(self) -> Node:
129137
return self.get_by_alias(self.DEFAULT_ALIAS)
130138

131-
def get_by_alias(self, alias):
132-
for node_name in self.nodes:
133-
if self.nodes[node_name] and self.nodes[node_name].alias == alias:
134-
return self.nodes[node_name]
135-
raise SpecterError("Node %s does not exist!" % alias)
139+
def get_by_alias(self, alias: str) -> Node:
140+
for node in self.nodes.values():
141+
if node.alias == alias:
142+
return node
143+
raise SpecterError("Node alias %s does not exist!" % alias)
144+
145+
def get_by_name(self, name: str) -> Node:
146+
for node in self.nodes.values():
147+
if node.name == name:
148+
return node
149+
raise SpecterError("Node name %s does not exist!" % name)
136150

137151
def update_bitcoind_version(self, specter, version):
138152
stopped_nodes = []
@@ -168,7 +182,7 @@ def add_external_node(
168182
"""Adding a node. Params:
169183
:param node_type: only valid for autodetect. Either BTC or ELM
170184
This should only be used for an external node. Use add_internal_node for internal node
171-
and if you have defined your own node-type, use save_node directly. to save the node (and create it yourself)
185+
and if you have defined your own node type, use save_node directly to save the node (and create it yourself)
172186
"""
173187
if not default_alias:
174188
node_alias = alias(name)
@@ -195,20 +209,19 @@ def add_external_node(
195209
node_type,
196210
self,
197211
)
198-
logger.info(f"persisting {node} in add_external_node")
199-
self.nodes[name] = node
200-
return self.save_node(node)
212+
logger.debug(f"Persisting {node_alias} from an add_external_node call.")
213+
self.nodes[node_alias] = node
214+
self.save_node(node)
215+
return node
201216

202217
def save_node(self, node):
203218
fullpath = (
204219
node.fullpath
205220
if hasattr(node, "fullpath")
206-
else calc_fullpath(self.data_folder, node.name)
221+
else calc_fullpath(self.data_folder, node.alias)
207222
)
208223
write_node(node, fullpath)
209-
210224
logger.info("Added new node {}".format(node.alias))
211-
return node
212225

213226
def add_internal_node(
214227
self,
@@ -251,15 +264,22 @@ def add_internal_node(
251264
network,
252265
self.internal_bitcoind_version,
253266
)
254-
self.nodes[name] = node
255-
return self.save_node(node)
267+
self.nodes[node_alias] = node
268+
return node
256269

257270
def delete_node(self, node, specter):
258271
logger.info("Deleting {}".format(node.alias))
259-
# Delete files
260-
delete_file(node.fullpath)
261-
delete_file(node.fullpath + ".bkp")
262-
if self._active_node == node.alias:
263-
specter.update_active_node(next(iter(self.nodes.values())).alias)
264-
del self.nodes[node.name]
265-
logger.info("Node {} was deleted successfully".format(node.alias))
272+
try:
273+
# Delete from wallet manager
274+
del self.nodes[node.alias]
275+
# Delete files
276+
delete_file(node.fullpath)
277+
delete_file(node.fullpath + ".bkp")
278+
# Update the active node
279+
if self._active_node == node.alias:
280+
specter.update_active_node(
281+
next(iter(self.nodes.values())).alias
282+
) # This switches to the first node in the node list, which is usually the default node
283+
logger.info("Node {} was deleted successfully".format(node.alias))
284+
except KeyError:
285+
raise SpecterError(f"{node.name} not found, node could not be deleted.")

src/cryptoadvance/specter/server_endpoints/nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def node_settings(node_alias):
166166
flash(_("Test failed: {}").format(test["err"]), "error")
167167
elif action == "save":
168168
if not node_alias:
169-
if node.name in app.specter.node_manager.nodes:
169+
if node.name in app.specter.node_manager.nodes_names:
170170
flash(
171171
_(
172172
"Node with this name already exists, please choose a different name."
@@ -271,7 +271,7 @@ def internal_node_settings(node_alias):
271271
node.rename(node_name)
272272
elif action == "forget":
273273
if not node_alias:
274-
flash(_("Failed to deleted node. Node isn't saved"), "error")
274+
flash(_("Failed to delete node. Node isn't saved"), "error")
275275
elif len(app.specter.node_manager.nodes) > 1:
276276
node.stop()
277277
app.specter.node_manager.delete_node(node, app.specter)

src/cryptoadvance/specter/server_endpoints/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def setup_bitcoind_datadir():
161161
network = request.form.get("network", "main")
162162
node_name = "Specter Bitcoin" if network == "main" else f"Specter {network.title()}"
163163
i = 1
164-
while node_name in app.specter.node_manager.nodes:
164+
while node_name in app.specter.node_manager.nodes_names:
165165
i += 1
166166
node_name = (
167167
f"Specter Bitcoin {i}"

src/cryptoadvance/specter/templates/includes/sidebar/components/node_select_popup.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<div id="node_select_popup" class="hidden" style="text-align: left;">
2626
<h1>{{ _("Node configuration") }}</h1>
2727
{% for node_name in specter.node_manager.nodes_names %}
28-
{% set node = specter.node_manager.nodes[node_name] %}
28+
{% set node = specter.node_manager.get_by_name(node_name) %}
2929
<form action="{{url_for('nodes_endpoint.switch_node')}}" id="{{node.alias}}-select-node-form" method="POST">
3030
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
3131
<input type="hidden" name="node_alias" value="{{ node.alias }}"/>

src/cryptoadvance/specter/util/migrations/migration_0002.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ def description(self) -> str:
4141
In order to reverse this migration, you do need to reverese the addition of the
4242
python_class like this:
4343
44+
sudo apt-get install moreutils jq
4445
cd ~/.specter/nodes
46+
# adding the external_node key again:
47+
for file in `ls *.json`; do jq '. | select(.python_class=="cryptoadvance.specter.internal_node.InternalNode") + {"external_node":false} , . | select(.python_class=="cryptoadvance.specter.node.Node") + {"external_node":true}' $file | sponge $file ; done
48+
# remove python_class key
4549
for file in `ls *.json`; do jq 'del(.python_class)' $file | sponge $file ; done
4650
cd ..
4751
jq 'del(.migration_executions[] | select(.migration_id == 2))' migration_data.json | sponge migration_data.json
@@ -55,7 +59,11 @@ def execute(self):
5559
return
5660

5761
nodes = {}
58-
nodes_files = load_jsons(node_folder, key="name")
62+
logger.info(f"Loading all json-files from {node_folder}")
63+
nodes_files = load_jsons(node_folder, key="alias")
64+
logger.info(
65+
f"iterating these nodes: {[node_alias for node_alias in nodes_files ]}"
66+
)
5967
for node_alias in nodes_files:
6068

6169
fullpath = os.path.join(self.data_folder, "%s.json" % node_alias)

tests/conftest.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,39 @@ def node(empty_data_folder, bitcoin_regtest):
218218
nodes_folder = empty_data_folder + "/nodes"
219219
if not os.path.isdir(nodes_folder):
220220
os.makedirs(nodes_folder)
221-
node = Node.from_json(
222-
{
223-
"autodetect": False,
224-
"datadir": bitcoin_regtest.datadir,
225-
"user": bitcoin_regtest.rpcconn.rpcuser,
226-
"password": bitcoin_regtest.rpcconn.rpcpassword,
227-
"port": bitcoin_regtest.rpcconn.rpcport,
228-
"host": bitcoin_regtest.rpcconn.ipaddress,
229-
"protocol": "http",
230-
},
231-
manager=NodeManager(data_folder=nodes_folder),
232-
default_fullpath=os.path.join(nodes_folder, "standard_node.json"),
221+
nm = NodeManager(data_folder=nodes_folder)
222+
node = nm.add_external_node(
223+
"BTC",
224+
"Standard node",
225+
False,
226+
bitcoin_regtest.datadir,
227+
bitcoin_regtest.rpcconn.rpcuser,
228+
bitcoin_regtest.rpcconn.rpcpassword,
229+
bitcoin_regtest.rpcconn.rpcport,
230+
bitcoin_regtest.rpcconn._ipaddress,
231+
"http",
232+
"standard_node",
233+
)
234+
return node
235+
236+
237+
@pytest.fixture
238+
def node_with_different_port(empty_data_folder, bitcoin_regtest):
239+
nodes_folder = empty_data_folder + "/nodes"
240+
if not os.path.isdir(nodes_folder):
241+
os.makedirs(nodes_folder)
242+
nm = NodeManager(data_folder=nodes_folder)
243+
node = nm.add_external_node(
244+
"BTC",
245+
"Node with a different port",
246+
False,
247+
"",
248+
bitcoin_regtest.rpcconn.rpcuser,
249+
bitcoin_regtest.rpcconn.rpcpassword,
250+
18333,
251+
bitcoin_regtest.rpcconn._ipaddress,
252+
"http",
253+
"satoshis_node",
233254
)
234255
return node
235256

0 commit comments

Comments
 (0)