Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions src/warnet/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ def grep_logs(pattern: str, show_k8s_timestamps: bool, no_sort: bool):
return matching_logs


def is_external_peer(name: str) -> bool:
"""Check if tank_b is an external/non-tank peer (e.g. onion address or raw IP)"""
return ".onion" in name or not name.startswith("tank-")


@bitcoin.command()
@click.argument("tank_a", type=str, required=True)
@click.argument("tank_b", type=str, required=True)
Expand All @@ -159,6 +164,7 @@ def messages(tank_a: str, tank_b: str, chain: str):
"""
Fetch messages sent between <tank_a pod name> and <tank_b pod name> in [chain]

tank_b can be a tank pod name, an onion address, or any external peer identifier.
Optionally, include a namespace like so: tank-name.namespace
"""

Expand All @@ -171,28 +177,40 @@ def parse_name_and_namespace(tank: str) -> tuple[str, Optional[str]]:
return tank_split[0], namespace

tank_a_split = tank_a.split(".")
tank_b_split = tank_b.split(".")
if len(tank_a_split) > 2 or len(tank_b_split) > 2:
if len(tank_a_split) > 2:
click.secho("Accepted formats: tank-name OR tank-name.namespace")
click.secho(f"Foramts found: {tank_a} {tank_b}")
sys.exit(1)

# Only validate tank_b format if it's not an external peer
if not is_external_peer(tank_b):
tank_b_split = tank_b.split(".")
if len(tank_b_split) > 2:
click.secho("Accepted formats: tank-name OR tank-name.namespace")
click.secho(f"Format found: {tank_b}")
sys.exit(1)

tank_a, namespace_a = parse_name_and_namespace(tank_a)
tank_b, namespace_b = parse_name_and_namespace(tank_b)

# If tank_b is an external peer (onion address etc.), skip namespace parsing
if is_external_peer(tank_b):
namespace_b = None
else:
tank_b, namespace_b = parse_name_and_namespace(tank_b)

try:
namespace_a = get_default_namespace_or(namespace_a)
namespace_b = get_default_namespace_or(namespace_b)
if namespace_b is not None:
namespace_b = get_default_namespace_or(namespace_b)

# Get the messages
messages = get_messages(
tank_a, tank_b, chain, namespace_a=namespace_a, namespace_b=namespace_b
)

if not messages:
print(
f"No messages found between {tank_a} ({namespace_a}) and {tank_b} ({namespace_b})"
)
peer_display = tank_b if namespace_b is None else f"{tank_b} ({namespace_b})"
print(f"No messages found between {tank_a} ({namespace_a}) and {peer_display}")
return

# Process and print messages
Expand Down Expand Up @@ -224,15 +242,17 @@ def get_messages(tank_a: str, tank_b: str, chain: str, namespace_a: str, namespa
subdir = "" if chain == "main" else f"{chain}/"
base_dir = f"/root/.bitcoin/{subdir}message_capture"

# Get the IP of node_b
cmd = f"kubectl get pod {tank_b} -o jsonpath='{{.status.podIP}}' --namespace {namespace_b}"
tank_b_ip = run_command(cmd).strip()
if namespace_b is None:
# External peer (onion address, raw IP, etc.) — use address directly for dir matching
tank_b_ip = tank_b
tank_b_service_ip = tank_b
else:
# Known tank — look up IPs via kubectl
cmd = f"kubectl get pod {tank_b} -o jsonpath='{{.status.podIP}}' --namespace {namespace_b}"
tank_b_ip = run_command(cmd).strip()

# Get the service IP of node_b
cmd = (
f"kubectl get service {tank_b} -o jsonpath='{{.spec.clusterIP}}' --namespace {namespace_b}"
)
tank_b_service_ip = run_command(cmd).strip()
cmd = f"kubectl get service {tank_b} -o jsonpath='{{.spec.clusterIP}}' --namespace {namespace_b}"
tank_b_service_ip = run_command(cmd).strip()

# List directories in the message capture folder
cmd = f"kubectl exec {tank_a} --namespace {namespace_a} -- ls {base_dir}"
Expand Down
19 changes: 19 additions & 0 deletions test/onion_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ def onion_connect():

self.wait_for_predicate(onion_connect, timeout=20 * 60)

self.check_messages(onions)

def check_messages(self, onions: dict):
self.log.info("Checking captured messages from onion peer...")

# tank-0001's onion address as seen by tank-0000
onion_b = onions["tank-0001"]

# Bitcoin message capture dirs are named like: <ip>_<port> or <onion>_<port>
# We pass the onion address with port appended as tank_b
onion_b_with_port = f"{onion_b}_18444"

result = self.warnet(f"bitcoin messages tank-0000 {onion_b_with_port}")
self.log.info(f"Messages result: {result}")
assert result and len(result.strip()) > 0, (
f"Expected messages between tank-0000 and {onion_b_with_port} but got none"
)
self.log.info("Successfully retrieved messages from onion peer!")


if __name__ == "__main__":
test = OnionTest()
Expand Down
Loading