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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ xrpld-netgen create:network [OPTIONS]
- `--nodedb_type` - Database type: "Memory" or "NuDB" (default: "NuDB")
- `--local` - Create local network without Docker (runs natively)
- `--binary_name` - Custom xrpld binary name (default: "xrpld")
- `--name` - Optional cluster base name: creates `workspace/{name}-cluster` instead of defaulting to the build version (or Git branch id). Start with `xrpld-netgen up --name {name}-cluster`.
- `--build_server` - Build server URL (auto-detected by protocol)

**Examples:**
Expand Down Expand Up @@ -239,12 +240,17 @@ xrpld-netgen up:standalone [OPTIONS]
- `--server` - Build server URL (optional)
- `--public_key` - Validator list public key
- `--import_key` - Import validator list key
- `--name` - Optional directory slug: files go under `workspace/{protocol}-{name}` instead of `workspace/{protocol}-{version}`. Tear down with `down:standalone --name {protocol}-{name}`.

**Examples:**
```bash
# Create standalone with current version
xrpld-netgen up:standalone --protocol xahau

# Custom folder name (same version, different workspace directory)
xrpld-netgen up:standalone --protocol xahau --name my-ledger
xrpld-netgen down:standalone --name xahau-my-ledger

# Create with specific version and IPFS
xrpld-netgen up:standalone --protocol xahau --version 2025.7.9-release+1951 --ipfs

Expand Down
45 changes: 42 additions & 3 deletions tests/unit/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@
# coding: utf-8

import pytest
from xrpld_netgen.main import standalone_workspace_dirname
from xrpld_netgen.utils.misc import (
docker_compose_top_level_name,
generate_ports,
get_node_port,
sha512_half,
get_node_db_path,
get_relational_db,
sanitize_cluster_name,
)


class TestGeneratePorts:
"""Test port generation for different node types"""

def test_generate_ports_validator_first(self):
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(1, "validator")
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(
1, "validator"
)
assert rpc_public == 5107
assert rpc_admin == 5105
assert ws_public == 6108
assert ws_admin == 6106
assert peer == 51335

def test_generate_ports_validator_second(self):
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(2, "validator")
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(
2, "validator"
)
assert rpc_public == 5207
assert rpc_admin == 5205
assert ws_public == 6208
Expand All @@ -47,7 +54,9 @@ def test_generate_ports_peer_second(self):
assert peer == 51255

def test_generate_ports_standalone(self):
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(0, "standalone")
rpc_public, rpc_admin, ws_public, ws_admin, peer = generate_ports(
0, "standalone"
)
assert rpc_public == 5007
assert rpc_admin == 5005
assert ws_public == 6008
Expand Down Expand Up @@ -129,6 +138,36 @@ def test_get_node_db_path_rwdb_network(self):
assert path == "/var/lib/xrpld/db"


class TestStandaloneWorkspaceDirname:
def test_default_uses_version(self):
assert (
standalone_workspace_dirname("xahau", "2025.1.0", None) == "xahau-2025.1.0"
)

def test_custom_slug(self):
assert (
standalone_workspace_dirname("xahau", "2025.1.0", "dev-a") == "xahau-dev-a"
)


class TestDockerComposeTopLevelName:
def test_lowercase_and_dots(self):
assert docker_compose_top_level_name("My.Net") == "my-net"


class TestSanitizeClusterName:
def test_trims_and_keeps_safe_chars(self):
assert sanitize_cluster_name(" my-net_1 ") == "my-net_1"

def test_rejects_path_separators(self):
with pytest.raises(ValueError, match="path"):
sanitize_cluster_name("a/b")

def test_rejects_parent_dir(self):
with pytest.raises(ValueError, match="path"):
sanitize_cluster_name("x..y")


class TestGetRelationalDb:
"""Test getting relational database backend configuration"""

Expand Down
42 changes: 40 additions & 2 deletions xrpld_netgen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
create_standalone_binary,
create_standalone_image,
start_local,
standalone_workspace_dirname,
)
from xrpld_netgen.network import (
create_network,
Expand Down Expand Up @@ -67,6 +68,7 @@
_XRPL_RELEASE_FALLBACK: str = "3.1.1"
_XAHAU_RELEASE_FALLBACK: str = "2025.7.9-release+1951"


def main():
parser = argparse.ArgumentParser(
description="A python cli to build xrpld networks and standalone ledgers."
Expand Down Expand Up @@ -213,6 +215,17 @@ def main():
help="The name of the xrpld binary for local networks (default: xrpld)",
default="xrpld",
)
parser_cn.add_argument(
"--name",
type=str,
required=False,
default=None,
help=(
"Optional cluster base name: creates workspace/{name}-cluster "
"(default: build version or branch id). "
"Start with: xrpld-netgen up --name {name}-cluster"
),
)
# update:node
parser_un = subparsers.add_parser("update:node", help="Update Node Version")
parser_un.add_argument(
Expand Down Expand Up @@ -343,6 +356,17 @@ def main():
choices=["Memory", "NuDB"],
default="NuDB",
)
parser_us.add_argument(
"--name",
type=str,
required=False,
default=None,
help=(
"Optional workspace directory slug: deploys to workspace/{protocol}-{name} "
"(default: build version). Use the same value with down:standalone --name "
"{protocol}-{name}."
),
)
# down:standalone
parser_ds = subparsers.add_parser("down:standalone", help="Down Standalone")
parser_ds.add_argument("--name", required=False, help="The name of the network")
Expand Down Expand Up @@ -406,7 +430,8 @@ def main():
print(f" - Network Name: {NAME}")
print(f" - Protocol: {PROTOCOL}")
print(f" - Build Version: {BUILD_VERSION}")
return run_stop([f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/stop.sh"])
rel = standalone_workspace_dirname(PROTOCOL, BUILD_VERSION, None)
return run_stop([f"{basedir}/{rel}/stop.sh"])

# MANAGE NETWORK/STANDALONE
if args.command == "up":
Expand Down Expand Up @@ -487,6 +512,7 @@ def main():
NODEDB_TYPE = args.nodedb_type
LOCAL = args.local
BINARY_NAME = args.binary_name
NETWORK_LABEL = (args.name or "").strip() or None

import_vl_key: str = (
"ED87E0EA91AAFFA130B78B75D2CC3E53202AA1BD8AB3D5E7BAC530C8440E328501"
Expand Down Expand Up @@ -537,6 +563,8 @@ def main():
if LOCAL:
print(f" - Binary Name: {BINARY_NAME}")
print(" - Deployment: Local (native processes, no Docker for nodes)")
if NETWORK_LABEL is not None:
print(f" - Cluster name: {NETWORK_LABEL}")

if LOCAL:
# Create local network without Docker
Expand All @@ -553,6 +581,7 @@ def main():
GENESIS,
QUORUM,
NODEDB_TYPE,
NETWORK_LABEL,
)
else:
# Create traditional Docker-based network
Expand All @@ -568,6 +597,7 @@ def main():
GENESIS,
QUORUM,
NODEDB_TYPE,
NETWORK_LABEL,
)

if args.command == "update:node":
Expand Down Expand Up @@ -615,6 +645,7 @@ def main():
BUILD_VERSION = args.version
IPFS_SERVER = args.ipfs
NODEDB_TYPE = args.nodedb_type
STANDALONE_LABEL = (args.name or "").strip() or None

if PROTOCOL == "xahau" and not IMPORT_KEY:
IMPORT_KEY: str = (
Expand Down Expand Up @@ -651,6 +682,8 @@ def main():
print(f" - Build Version: {BUILD_VERSION}")
print(f" - IPFS Server: {IPFS_SERVER}")
print(f" - Node DB: {NODEDB_TYPE}")
if STANDALONE_LABEL is not None:
print(f" - Standalone directory slug: {STANDALONE_LABEL}")

if BUILD_TYPE == "image":
create_standalone_image(
Expand All @@ -664,6 +697,7 @@ def main():
BUILD_VERSION,
IPFS_SERVER,
NODEDB_TYPE,
STANDALONE_LABEL,
)
else:
create_standalone_binary(
Expand All @@ -677,10 +711,14 @@ def main():
BUILD_VERSION,
IPFS_SERVER,
NODEDB_TYPE,
STANDALONE_LABEL,
)

deploy_dir = standalone_workspace_dirname(
PROTOCOL, BUILD_VERSION, STANDALONE_LABEL
)
run_start(
[f"{basedir}/{PROTOCOL}-{BUILD_VERSION}/start.sh"],
[f"{basedir}/{deploy_dir}/start.sh"],
PROTOCOL,
BUILD_VERSION,
"standalone",
Expand Down
Loading
Loading