Skip to main content

making it easy to expand daemons via minimal changes

structured config mapping & data-driven system

Yes — the pattern you have now works, but it locks you into hardcoding each daemon. Since you already have a config with VTRC_DAEMONS, you can make this fully data-driven so adding/removing daemons doesn’t require touching your Python code.

Here’s a more expandable approach:


1. Use a structured config mapping

Instead of keeping RPC connection parameters scattered as individual variables, you can organize them in a dict keyed by daemon name. Since your environment variables already follow a pattern, you can dynamically load them.

import os

# list of daemons to enable
VTRC_DAEMONS = [
"verusd",
"verusdvarrr",
# "verusdvdex", # can enable/disable by commenting
]

# function to load connection details from env using prefix
def load_rpc_config(name: str):
prefix = name.replace("verusd", "").replace("verus", "") or name
return {
"user": os.environ[f"{prefix}_rpc_user"],
"password": os.environ[f"{prefix}_rpc_password"],
"port": os.environ[f"{prefix}_rpc_port"],
"host": os.environ[f"{prefix}_rpc_host"],
}

# full config dict
DAEMON_CONFIGS = {daemon: load_rpc_config(daemon) for daemon in VTRC_DAEMONS}

2. Use it in your setup_rpc_connections

Now your setup method can be generic:

def setup_rpc_connections(self):
"""Set up RPC connections for all configured daemons."""
self.daemonrpc = {}
for daemon_name, cfg in DAEMON_CONFIGS.items():
self.daemonrpc[daemon_name] = NodeRpc(
cfg["user"],
cfg["password"],
cfg["port"],
cfg["host"],
)

3. Benefits

  • Add new daemon → only touch VTRC_DAEMONS and environment variables, no Python changes.
  • Centralized config → all RPC connection details live in one place (DAEMON_CONFIGS).
  • Scalable → if you end up with 10–20 daemons, your Python code doesn’t grow — only the config changes.

👉 Question: do you want to keep the daemon constants (VTRC_VERUSD, VTRC_VERUSDVARRR, etc.), or would you prefer using plain strings ("verusd", "verusdvarrr") everywhere? If you want to keep the constants for clarity, I can show you how to wire them into this setup without hardcoding.

using daemon constants

Perfect 👍 — keeping the constants is a good move for safety and IDE support.

Here’s how you can keep the constants, but still make the system data-driven:


1. Define your constants & list of enabled daemons

# constants
VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"

# which daemons to activate
VTRC_DAEMONS = [VTRC_VERUSD, VTRC_VERUSDVARRR]

2. Generic loader for RPC configs

We’ll derive env var names based on the daemon constant. For example:

  • verusdvrsc_rpc_user (your VRSC naming convention)
  • verusdvarrrvarrr_rpc_user
  • verusdvdexvdex_rpc_user

So we just need a mapping from daemon → prefix:

import os

DAEMON_ENV_PREFIX = {
VTRC_VERUSD: "vrsc",
VTRC_VERUSDVARRR: "varrr",
VTRC_VERUSDVDEX: "vdex",
}

Now we can load configs generically:

def load_rpc_config(daemon: str):
prefix = DAEMON_ENV_PREFIX[daemon]
return {
"user": os.environ[f"{prefix}_rpc_user"],
"password": os.environ[f"{prefix}_rpc_password"],
"port": os.environ[f"{prefix}_rpc_port"],
"host": os.environ[f"{prefix}_rpc_host"],
}

3. Build the configs & connections

DAEMON_CONFIGS = {
daemon: load_rpc_config(daemon) for daemon in VTRC_DAEMONS
}

def setup_rpc_connections(self):
"""Set up RPC connections for all configured daemons."""
self.daemonrpc = {
daemon: NodeRpc(
cfg["user"], cfg["password"], cfg["port"], cfg["host"]
)
for daemon, cfg in DAEMON_CONFIGS.items()
}

✅ Advantages

  • Constants stay in code → IDE catches typos (VTRC_VERUSD, not "verusd").
  • Expandability → add to VTRC_DAEMONS and DAEMON_ENV_PREFIX, no touching main logic.
  • Clear mapping between constants and environment variable prefixes.

Would you like me to also show you a config-file driven version (e.g. .toml or .yaml) so you don’t have to expand DAEMON_ENV_PREFIX manually each time, or do you prefer keeping it strictly in Python + env vars?

Toml vs Yaml

Got it ✅ — let’s look at both TOML and YAML approaches side by side. The main difference is just in how the config file is structured and parsed.


🔹 1. Example TOML Config (config.toml)

# Which daemons are active
active_daemons = ["verusd", "verusdvarrr"]

[daemons.verusd]
prefix = "vrsc"
rpc_user = "user1"
rpc_password = "pass1"
rpc_port = "27486"
rpc_host = "127.0.0.1"

[daemons.verusdvarrr]
prefix = "varrr"
rpc_user = "user2"
rpc_password = "pass2"
rpc_port = "27487"
rpc_host = "127.0.0.1"

[daemons.verusdvdex]
prefix = "vdex"
rpc_user = "user3"
rpc_password = "pass3"
rpc_port = "27488"
rpc_host = "127.0.0.1"

Python Loader for TOML

import tomllib  # Python 3.11+, else `pip install tomli`

VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"

with open("config.toml", "rb") as f:
config = tomllib.load(f)

VTRC_DAEMONS = config["active_daemons"]

def setup_rpc_connections(self):
self.daemonrpc = {}
for daemon in VTRC_DAEMONS:
cfg = config["daemons"][daemon]
self.daemonrpc[daemon] = NodeRpc(
cfg["rpc_user"], cfg["rpc_password"], cfg["rpc_port"], cfg["rpc_host"]
)

🔹 2. Example YAML Config (config.yaml)

# Which daemons are active
active_daemons:
- verusd
- verusdvarrr

daemons:
verusd:
prefix: vrsc
rpc_user: user1
rpc_password: pass1
rpc_port: 27486
rpc_host: 127.0.0.1

verusdvarrr:
prefix: varrr
rpc_user: user2
rpc_password: pass2
rpc_port: 27487
rpc_host: 127.0.0.1

verusdvdex:
prefix: vdex
rpc_user: user3
rpc_password: pass3
rpc_port: 27488
rpc_host: 127.0.0.1

Python Loader for YAML

import yaml  # pip install pyyaml

VTRC_VERUSD = "verusd"
VTRC_VERUSDVARRR = "verusdvarrr"
VTRC_VERUSDVDEX = "verusdvdex"

with open("config.yaml", "r") as f:
config = yaml.safe_load(f)

VTRC_DAEMONS = config["active_daemons"]

def setup_rpc_connections(self):
self.daemonrpc = {}
for daemon in VTRC_DAEMONS:
cfg = config["daemons"][daemon]
self.daemonrpc[daemon] = NodeRpc(
cfg["rpc_user"], cfg["rpc_password"], cfg["rpc_port"], cfg["rpc_host"]
)

🔹 3. Benefits of TOML vs YAML

  • TOML → strict, less error-prone, great for configs (used in Python packaging).
  • YAML → more flexible, widely used (esp. for infra like Docker/K8s), but indentation mistakes can bite you.

👉 Do you prefer embedding sensitive values (user/password) in the TOML/YAML, or would you rather pull them from environment variables (e.g. rpc_password = ${ENV_VAR} style)?

I can show you how to support env var expansion in both cases if you want to keep secrets out of the config file.