Skip to content

Commit 4b03d49

Browse files
authored
Merge pull request #285 from pinheadmz/addnode-arg
2 parents 892553a + b6b9de0 commit 4b03d49

File tree

15 files changed

+90
-34
lines changed

15 files changed

+90
-34
lines changed

src/backends/compose/compose_backend.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ def default_config_args(self, tank):
356356
defaults += f" -rpcport={tank.rpc_port}"
357357
defaults += f" -zmqpubrawblock=tcp://0.0.0.0:{tank.zmqblockport}"
358358
defaults += f" -zmqpubrawtx=tcp://0.0.0.0:{tank.zmqtxport}"
359+
# connect to initial peers as defined in graph file
360+
for dst_index in tank.init_peers:
361+
defaults += f" -addnode={self.get_container_name(dst_index, ServiceType.BITCOIN)}"
359362
return defaults
360363

361364
def copy_configs(self, tank):

src/backends/kubernetes/kubernetes_backend.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
from cli.image import build_image
1111
from kubernetes import client, config
1212
from kubernetes.client.models.v1_pod import V1Pod
13+
from kubernetes.client.models.v1_service import V1Service
1314
from kubernetes.client.rest import ApiException
1415
from kubernetes.dynamic import DynamicClient
16+
from kubernetes.dynamic.exceptions import ResourceNotFoundError
1517
from kubernetes.stream import stream
1618
from warnet.status import RunningStatus
1719
from warnet.tank import Tank
@@ -122,6 +124,15 @@ def get_pod(self, pod_name: str) -> V1Pod | None:
122124
if e.status == 404:
123125
return None
124126

127+
def get_service(self, service_name: str) -> V1Service | None:
128+
try:
129+
return cast(
130+
V1Service, self.client.read_namespaced_service(name=service_name, namespace=self.namespace)
131+
)
132+
except ApiException as e:
133+
if e.status == 404:
134+
return None
135+
125136
# We could enhance this by checking the pod status as well
126137
# The following pod phases are available: Pending, Running, Succeeded, Failed, Unknown
127138
# For example not able to pull image will be a phase of Pending, but the container status will be ErrImagePull
@@ -239,6 +250,7 @@ def get_messages(
239250
bitcoin_network: str = "regtest",
240251
):
241252
b_pod = self.get_pod(self.get_pod_name(b_index, ServiceType.BITCOIN))
253+
b_service = self.get_service(self.get_service_name(b_index))
242254
subdir = "/" if bitcoin_network == "main" else f"{bitcoin_network}/"
243255
base_dir = f"/root/.bitcoin/{subdir}message_capture"
244256
cmd = f"ls {base_dir}"
@@ -253,7 +265,7 @@ def get_messages(
253265
messages = []
254266

255267
for dir_name in dirs:
256-
if b_pod.status.pod_ip in dir_name:
268+
if b_pod.status.pod_ip in dir_name or b_service.spec.cluster_ip in dir_name:
257269
for file, outbound in [["msgs_recv.dat", False], ["msgs_sent.dat", True]]:
258270
# Fetch the file contents from the container
259271
file_path = f"{base_dir}/{dir_name}/{file}"
@@ -309,6 +321,9 @@ def default_bitcoind_config_args(self, tank):
309321
defaults += f" -rpcport={tank.rpc_port}"
310322
defaults += f" -zmqpubrawblock=tcp://0.0.0.0:{tank.zmqblockport}"
311323
defaults += f" -zmqpubrawtx=tcp://0.0.0.0:{tank.zmqtxport}"
324+
# connect to initial peers as defined in graph file
325+
for dst_index in tank.init_peers:
326+
defaults += f" -addnode={self.get_service_name(dst_index)}"
312327
return defaults
313328

314329
def create_bitcoind_container(self, tank: Tank) -> client.V1Container:
@@ -431,9 +446,8 @@ def remove_prometheus_service_monitors(self, tanks):
431446
name=f"warnet-tank-{tank.index:06d}",
432447
namespace=MAIN_NAMESPACE,
433448
)
434-
except ApiException as e:
435-
if e.status != 404:
436-
raise e
449+
except ResourceNotFoundError:
450+
continue
437451

438452
def create_lnd_container(self, tank, bitcoind_service_name, volume_mounts) -> client.V1Container:
439453
# These args are appended to the Dockerfile `ENTRYPOINT ["lnd"]`
@@ -557,7 +571,7 @@ def create_bitcoind_service(self, tank) -> client.V1Service:
557571
selector={"app": self.get_pod_name(tank.index, ServiceType.BITCOIN)},
558572
publish_not_ready_addresses=True,
559573
ports=[
560-
# TODO: do we need to add 18444 here too?
574+
client.V1ServicePort(port=18444, target_port=18444, name="p2p"),
561575
client.V1ServicePort(port=tank.rpc_port, target_port=tank.rpc_port, name="rpc"),
562576
client.V1ServicePort(
563577
port=tank.zmqblockport, target_port=tank.zmqblockport, name="zmqblock"

src/cli/network.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ def status(network: str):
130130
print(f"Tank: {tank['tank_index']} \tBitcoin: {tank['bitcoin_status']}{lightning_status}{circuitbreaker_status}")
131131

132132

133+
@network.command()
134+
@click.option("--network", default="warnet", show_default=True)
135+
def connected(network: str):
136+
"""
137+
Indicate whether the all of the edges in the gaph file are connected in <network>
138+
"""
139+
print(rpc_call("network_connected", {"network": network}))
140+
141+
133142
@network.command()
134143
@click.option("--network", default="warnet", show_default=True)
135144
def export(network):

src/scenarios/miner_std.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ def add_options(self, parser):
3131
)
3232

3333
def run_test(self):
34+
while not self.warnet.network_connected():
35+
sleep(1)
36+
3437
current_miner = 0
3538

3639
while True:

src/warnet/server.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def setup_rpc(self):
175175
self.jsonrpc.register(self.network_down)
176176
self.jsonrpc.register(self.network_info)
177177
self.jsonrpc.register(self.network_status)
178+
self.jsonrpc.register(self.network_connected)
178179
self.jsonrpc.register(self.network_export)
179180
# Graph
180181
self.jsonrpc.register(self.graph_generate)
@@ -439,7 +440,6 @@ def thread_start(wn):
439440
wn = Warnet.from_network(network, self.backend)
440441
wn.apply_network_conditions()
441442
wn.wait_for_health()
442-
wn.connect_edges()
443443
self.logger.info(
444444
f"Resumed warnet named '{network}' from config dir {wn.config_dir}"
445445
)
@@ -474,7 +474,6 @@ def thread_start(wn, lock: threading.Lock):
474474
wn.warnet_up()
475475
wn.wait_for_health()
476476
wn.apply_network_conditions()
477-
wn.connect_edges()
478477
except Exception as e:
479478
trace = traceback.format_exc()
480479
self.logger.error(f"Unhandled exception starting warnet: {e}\n{trace}")
@@ -574,6 +573,17 @@ def network_status(self, network: str = "warnet") -> list[dict]:
574573
self.logger.error(msg)
575574
raise ServerError(message=msg) from e
576575

576+
def network_connected(self, network: str = "warnet") -> bool:
577+
"""
578+
Indicate whether all of the graph edges are connected in <network>
579+
"""
580+
try:
581+
wn = Warnet.from_network(network, self.backend)
582+
return wn.network_connected()
583+
except Exception as e:
584+
self.logger.error(f"{e}")
585+
return False
586+
577587
def generate_deployment(self, graph_file: str, network: str = "warnet") -> str:
578588
"""
579589
Generate the deployment file for a graph file

src/warnet/tank.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ def __init__(self, index: int, config_dir: Path, warnet):
4747
self._suffix = None
4848
self._ipv4 = None
4949
self._exporter_name = None
50+
# index of integers imported from graph file
51+
# indicating which tanks to initially connect to
52+
self.init_peers = []
5053

5154
def __str__(self) -> str:
5255
return f"Tank(index: {self.index}, version: {self.version}, conf: {self.bitcoin_config}, conf file: {self.conf_file}, netem: {self.netem}, IPv4: {self._ipv4})"

src/warnet/utils.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,17 +410,17 @@ def create_cycle_graph(
410410

411411
# Graph is a simply cycle graph with all nodes connected in a loop, including both ends.
412412
# Ensure each node has at least 8 outbound connections by making 7 more outbound connections
413-
for node in graph.nodes():
414-
logger.debug(f"Creating additional connections for node {node}")
413+
for src_node in graph.nodes():
414+
logger.debug(f"Creating additional connections for node {src_node}")
415415
for _ in range(8):
416416
# Choose a random node to connect to
417-
# Make sure it's not the same node and they aren't already connected
418-
potential_nodes = [ node for node in range(n) if n != node and not graph.has_edge(node, n) ]
417+
# Make sure it's not the same node and they aren't already connected in either direction
418+
potential_nodes = [ dst_node for dst_node in range(n) if dst_node != src_node and not graph.has_edge(dst_node, src_node) and not graph.has_edge(src_node, dst_node) ]
419419
if potential_nodes:
420420
chosen_node = random.choice(potential_nodes)
421-
graph.add_edge(node, chosen_node)
422-
logger.debug(f"Added edge: {node}:{chosen_node}")
423-
logger.debug(f"Node {node} edges: {graph.edges(node)}")
421+
graph.add_edge(src_node, chosen_node)
422+
logger.debug(f"Added edge: {src_node}:{chosen_node}")
423+
logger.debug(f"Node {src_node} edges: {graph.edges(src_node)}")
424424

425425
# calculate degree
426426
degree_dict = dict(graph.degree(graph.nodes()))

src/warnet/warnet.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,27 +161,21 @@ def tanks_from_graph(self):
161161
raise Exception(
162162
f"Node ID in graph must be incrementing integers (got '{node_id}', expected '{len(self.tanks)}')"
163163
)
164-
self.tanks.append(Tank.from_graph_node(node_id, self))
164+
tank = Tank.from_graph_node(node_id, self)
165+
# import edges as list of destinations to connect to
166+
for edge in self.graph.edges(data=True):
167+
(src, dst, data) = edge
168+
if "channel" in data:
169+
continue
170+
if src == node_id:
171+
tank.init_peers.append(int(dst))
172+
self.tanks.append(tank)
165173
logger.info(f"Imported {len(self.tanks)} tanks from graph")
166174

167175
def apply_network_conditions(self):
168176
for tank in self.tanks:
169177
tank.apply_network_conditions()
170178

171-
def connect_edges(self):
172-
if self.graph is None:
173-
return
174-
175-
for edge in self.graph.edges(data=True):
176-
(src, dst, data) = edge
177-
if "channel" in data:
178-
continue
179-
src_tank = self.tanks[src]
180-
dst_ip = self.tanks[dst].ipv4
181-
cmd = f"bitcoin-cli -regtest -rpcuser={src_tank.rpc_user} -rpcpassword={src_tank.rpc_password} addnode {dst_ip}:18444 onetry"
182-
logger.info(f"Using `{cmd}` to connect tanks {src} to {dst}")
183-
src_tank.exec(cmd=cmd)
184-
185179
def warnet_build(self):
186180
self.container_interface.build()
187181

@@ -227,3 +221,16 @@ def export(self, subdir):
227221

228222
def wait_for_health(self):
229223
self.container_interface.wait_for_healthy_tanks(self)
224+
225+
def network_connected(self):
226+
for tank in self.tanks:
227+
peerinfo = json.loads(self.container_interface.get_bitcoin_cli(tank, "getpeerinfo"))
228+
manuals = 0
229+
for peer in peerinfo:
230+
if peer["connection_type"] == "manual":
231+
manuals += 1
232+
# Even if more edges are specifed, bitcoind only allows
233+
# 8 manual outbound connections
234+
if min(8, len(tank.init_peers)) > manuals:
235+
return False
236+
return True

test/build_branch_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
base.start_server()
1313
print(base.warcli(f"network start {graph_file_path}"))
1414
base.wait_for_all_tanks_status(target="running", timeout=10*60)
15+
base.wait_for_all_edges()
1516

1617
print("\nWait for p2p connections")
1718

test/graph_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
# Test that the graph actually works
3131
print(base.warcli(f"network start {Path(tf)}"))
3232
base.wait_for_all_tanks_status(target="running")
33+
base.wait_for_all_edges()
3334
base.warcli("rpc 0 getblockcount")
3435

3536
base.stop_server()

0 commit comments

Comments
 (0)