From d1ab3be89a1d44567bb56406bb21fac9d530f0ac Mon Sep 17 00:00:00 2001 From: Vijit Singh Date: Thu, 23 Jul 2026 21:36:05 -0500 Subject: [PATCH] =?UTF-8?q?feat(config):=20node=20LAN=20exposure=20switche?= =?UTF-8?q?s=20=E2=80=94=20zmq=5Flan=5Faccess=20+=20tari=20grpc=5Flan=5Fac?= =?UTF-8?q?cess=20(#760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The serving side of the remote-node modes. monero.rpc_lan_access existed but published only the RPC — a remote P2Pool needs the ZMQ feed too, and the Tari base node published nothing at all, so serving another stack meant hand-run socat forwards (what the #78 bench setup did on gouda). - monero.zmq_lan_access: publishes monerod's ZMQ (18083); a separate key from rpc_lan_access on purpose — that key's documented use is wallets, and widening it would change existing deployments' exposure. - tari.grpc_lan_access: publishes the bundled Tari base node's gRPC (18142); loopback by default, matching the console-wallet publish's trust boundary. Ignored in remote mode (the service is profiled off). - Both flips to 0.0.0.0 are DEST in describe_change (host-only, like MONERO_RPC_BIND), with the no-auth trust warning in the message. - Docs: config rows + the remote-node sections now name the serving-side switches; CHANGELOG entry. - Tier 1: describe_change direction tests + the #523 black-box render extended for all three binds (default localhost, true → 0.0.0.0). Closes #760. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 8 +++++ .../web/static/configlogic.mjs | 2 ++ config.reference.json | 2 ++ docker-compose.yml | 10 ++++++ docs/configuration.md | 9 +++++ pithead | 36 +++++++++++++++++++ tests/stack/run.sh | 12 ++++++- tests/stack/test_compose.sh | 2 ++ 8 files changed, 80 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac006a98..ada3ffef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ per the process in [`docs/dev/releasing.md`](docs/dev/releasing.md). ### Added +- **Node LAN exposure switches (#760)** — the serving side of the remote-node modes. `monero.zmq_lan_access` + publishes monerod's ZMQ feed on the LAN (with the existing `rpc_lan_access`, everything a remote + P2Pool needs), and `tari.grpc_lan_access` publishes the bundled Tari base node's gRPC, so one + stack's synced nodes can serve other stacks running `monero.mode`/`tari.mode: remote` (#103). + Both default off, publish loopback-only otherwise, and flipping either to the LAN is a + host-confirmed destructive change. The Tari gRPC and ZMQ feeds carry no authentication — + trusted networks only (see the remote-node sections in `docs/configuration.md`). + - **Remote Tari node (#103).** `tari.mode: remote` merge-mines against a Tari base node running elsewhere instead of the bundled one: set `tari.remote.host` (required) and `tari.remote.grpc_port` (default `18142`). Remote mode skips the bundled `tari` container, its diff --git a/build/dashboard/mining_dashboard/web/static/configlogic.mjs b/build/dashboard/mining_dashboard/web/static/configlogic.mjs index b4e68453..74fe2b76 100644 --- a/build/dashboard/mining_dashboard/web/static/configlogic.mjs +++ b/build/dashboard/mining_dashboard/web/static/configlogic.mjs @@ -112,9 +112,11 @@ export const LOGICAL_GROUPS = [ "monero.prune", "monero.remote", "monero.rpc_lan_access", + "monero.zmq_lan_access", "monero.clearnet_initial_sync", "tari.mode", "tari.remote", + "tari.grpc_lan_access", "tari.clearnet_initial_sync", ], }, diff --git a/config.reference.json b/config.reference.json index fadd0c1e..18185d80 100644 --- a/config.reference.json +++ b/config.reference.json @@ -14,6 +14,7 @@ "prep_blocks_threads": "auto", "out_peers": 48, "rpc_lan_access": false, + "zmq_lan_access": false, "data_dir": "auto", "mem_limit": "auto", @@ -30,6 +31,7 @@ "view_key": "", "spend_public_key": "", "payout_scan_birthday": "auto", + "grpc_lan_access": false, "clearnet_initial_sync": false, "data_dir": "auto", "mem_limit": "auto", diff --git a/docker-compose.yml b/docker-compose.yml index dbcd7235..7d4f8958 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,6 +117,10 @@ services: # the internal Docker network regardless. Set monero.rpc_lan_access:true in config.json # to publish on the LAN (0.0.0.0) for wallets on other machines. - "${MONERO_RPC_BIND:-127.0.0.1}:18081:18081" + # ZMQ block-notification feed (#760): loopback by default; set monero.zmq_lan_access:true to + # publish on the LAN — with rpc_lan_access, the serving side of monero.mode:remote (a remote + # p2pool needs both RPC and ZMQ). No auth on ZMQ pub: trusted LAN only. + - "${MONERO_ZMQ_BIND:-127.0.0.1}:18083:18083" networks: mining_net: ipv4_address: ${NETWORK_PREFIX:-172.28.0}.26 @@ -262,6 +266,12 @@ services: command: - --disable-splash-screen - --non-interactive + ports: + # Tari base-node gRPC for other machines (#760): loopback by default (host tools only; the + # same trust boundary as the console-wallet's loopback gRPC publish). Set + # tari.grpc_lan_access:true in config.json to publish on the LAN (0.0.0.0) — the serving + # side of tari.mode:remote (#103). Plaintext, unauthenticated: trusted LAN only (#754). + - "${TARI_GRPC_BIND:-127.0.0.1}:18142:18142" networks: mining_net: ipv4_address: ${NETWORK_PREFIX:-172.28.0}.27 diff --git a/docs/configuration.md b/docs/configuration.md index 25b02b57..f7f76da9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -105,6 +105,7 @@ control channel will commit, are unaffected either way. | `monero.prep_blocks_threads` | `auto` | Block-verification threads during sync. `auto` = host cores − 2, clamped to 4–8. | | `monero.out_peers` | `48` | monerod's outbound peer target (8–1024). Over Tor each outbound peer is roughly one long-lived circuit, so this is the main steady-state lever on Tor's CPU (#595). Keep the default while syncing (more peers = more download bandwidth over Tor); once synced, `32` — the count P2Pool recommends for clearnet — cuts monerod's circuit maintenance by a third. | | `monero.rpc_lan_access` | `false` | `true` publishes the node's RPC on the LAN (`0.0.0.0`) for wallets on other machines; default is localhost-only. | +| `monero.zmq_lan_access` | `false` | `true` publishes the node's ZMQ block-notification feed (`18083`) on the LAN. With `rpc_lan_access`, this is the serving side of `monero.mode: remote` — a remote P2Pool needs both RPC and ZMQ. ZMQ has no authentication: trusted networks only. | | `monero.remote.host` / `rpc_port` / `zmq_port` | — / `18081` / `18083` | Remote node connection details (used when `mode` is `remote`). | | `monero.data_dir` | `auto` | Where the Monero blockchain lives on the host. `auto` = `./data/monero`. Point this at an existing `.bitmonero` directory to reuse a synced node. See [Reusing an existing node](#reusing-an-existing-node). | | `monero.mem_limit` | `auto` | Upper limit on the monerod container's memory, so a leak/runaway OOM-restarts monerod alone instead of the host's OOM-killer picking a victim. `auto` is a generous ceiling (6 GB) that won't trip during normal operation or initial sync. monerod's OOM-triggering memory is small (~0.1 GiB at rest, ~1–3 GiB during sync) while its multi-GB blockchain DB is reclaimable, memory-mapped page cache that the kernel evicts under pressure rather than OOM-killing. Lower it only to free RAM. Raise it for a full (unpruned) node doing a heavy initial sync on a fast disk, or if a low-RAM host ever OOMs monerod during IBD (it restarts and resumes; the on-disk chain is transactional, no data loss). Accepts any Docker memory value, e.g. `"8g"`. (Tari has its own `tari.mem_limit`; the dashboard, P2Pool, Tor, and the proxies are small and carry fixed conservative ceilings in `docker-compose.yml`.) | @@ -115,6 +116,7 @@ control channel will commit, are unaffected either way. | `tari.payout_scan_birthday` | `auto` | Where the view-only Tari wallet starts scanning on first creation (#462). Unlike Monero's block-height restore point, a Tari birthday is **days since the Unix epoch** (a u16, 0–65535). `auto` = today when the wallet is first made, so it tracks payouts forward without rescanning from genesis. Set an earlier day to backfill older payouts (slower first scan). Only affects the first wallet creation; ignored once the wallet exists. | | `tari.clearnet_initial_sync` | `false` | Privacy-relevant, default off. `true` makes the Tari base node sync over clearnet instead of Tor: it switches the P2P transport to TCP, re-enables the `seeds.tari.com` DNS seed (the bundled onion `peer_seeds` are unreachable without Tor), and stops advertising its onion. Your node's IP becomes visible to the Tari P2P network while it's on, plus one DNS lookup of `seeds.tari.com`. `pithead` warns loudly (apply/status/doctor/up). The dashboard switches Tari back to Tor automatically once it's synced (#234), so you can leave this `true`. Full threat model: [Privacy › Optional clearnet initial sync](privacy.md#optional-clearnet-initial-sync-off-by-default). | | `tari.remote.host` / `grpc_port` | — / `18142` | Remote Tari base node connection details (used when `tari.mode` is `remote`). See [Remote Tari node](#remote-tari-node). | +| `tari.grpc_lan_access` | `false` | `true` publishes the local Tari base node's gRPC (`18142`) on the LAN — the serving side of `tari.mode: remote`, so other stacks can merge-mine against this node. The gRPC is plaintext and unauthenticated: trusted networks only (see [Remote Tari node](#remote-tari-node)). Ignored in `remote` mode (no bundled node runs). | | `tari.data_dir` | `auto` | Where the Tari node data lives on the host. `auto` = `./data/tari`. | | `tari.mem_limit` | `auto` | Upper limit on the Tari container's memory, so a runaway Tari restarts cleanly on its own instead of dragging down the whole host. `auto` picks a safe size for your machine. Leave it unless you want to give Tari less RAM (to free it for other apps) or more (if it ever restarts too often). Accepts any Docker memory value, e.g. `"8g"`. | | `p2pool.pool` | `mini` | Which P2Pool sidechain to mine: `main`, `mini`, or `nano`. `mini` (the default) has a lower share difficulty than `main`, so a typical home rig finds shares far more often: smoother, more frequent PPLNS payouts instead of long dry spells. Raise to `main` for high hashrate (large farms); drop to `nano` for a single low-power rig. | @@ -448,6 +450,9 @@ To connect to an external Monero node instead of running one locally, set `moner control; general-purpose public "open node" endpoints do not work, since they don't expose ZMQ. - If the remote node requires RPC authentication, set `monero.node_username` / `node_password` to match it; otherwise leave them out. +- If the remote node is another Pithead stack, the serving side is two switches on that stack: + `monero.rpc_lan_access: true` (RPC, digest-auth'd with its `node_username`/`node_password`) + and `monero.zmq_lan_access: true` (the ZMQ feed). Switching between `local` and `remote` is a disruptive change, so `apply` will confirm before applying it. @@ -481,6 +486,10 @@ To merge-mine against a Tari base node running elsewhere instead of the bundled exactly this (`c_base_node_b_mining_allow_methods.toml`) so `get_new_block_template_with_coinbases` and `submit_block` are reachable. +- If the remote node is another Pithead stack, that is one switch on the serving side: + `tari.grpc_lan_access: true` publishes its bundled node's gRPC on the LAN. (For the Monero + half of the same arrangement, the serving stack sets `monero.rpc_lan_access` and + `monero.zmq_lan_access`.) - Leave `grpc_authentication` and `grpc_tls_enabled` off on the remote node. p2pool's Tari merge-mine client dials plaintext, unauthenticated gRPC and has no way to speak either — turning them on there just breaks the connection. diff --git a/pithead b/pithead index a913c242..45d7328b 100755 --- a/pithead +++ b/pithead @@ -3713,6 +3713,15 @@ render_env() { tari_grpc_addr="${TARI_REMOTE_HOST}:${TARI_REMOTE_GRPC_PORT}" fi + # Tari gRPC LAN exposure (#760), mirroring monerod's rpc_lan_access above. Default + # localhost-only: in-stack consumers reach the node over the internal Docker network + # regardless, so the published port only serves other machines — the serving side of the + # remote mode (#103). The gRPC is plaintext and unauthenticated: trusted LAN only (#754 + # trust model). N/A in remote mode (the tari service is profile-gated off; nothing binds). + local tari_grpc_bind tari_grpc_lan + tari_grpc_lan=$(jq -r '.tari.grpc_lan_access // false' "$CONFIG_FILE") + if [ "$tari_grpc_lan" == "true" ]; then tari_grpc_bind="0.0.0.0"; else tari_grpc_bind="127.0.0.1"; fi + # On-chain payout confirmation (#381): the view-only wallet-rpc service only starts when its # compose profile is active, which is only when a view key is set on a local node. Off = no # container, dashboard unchanged. PAYOUT_CONFIRM_ENABLED is set by parse_and_validate_config @@ -3773,6 +3782,15 @@ render_env() { rpc_lan=$(jq -r '.monero.rpc_lan_access // false' "$CONFIG_FILE") if [ "$rpc_lan" == "true" ]; then rpc_bind="0.0.0.0"; else rpc_bind="127.0.0.1"; fi + # monerod ZMQ LAN exposure (#760). A separate key from rpc_lan_access on purpose: that key's + # documented use is wallets (RPC only), and widening it to also open ZMQ would change the + # exposure of existing deployments. A node SERVING remote stacks (#103's other half) needs + # both: rpc_lan_access for the RPC, zmq_lan_access for the block-notification feed p2pool + # requires. ZMQ pub has no auth — trusted LAN only. + local zmq_bind zmq_lan + zmq_lan=$(jq -r '.monero.zmq_lan_access // false' "$CONFIG_FILE") + if [ "$zmq_lan" == "true" ]; then zmq_bind="0.0.0.0"; else zmq_bind="127.0.0.1"; fi + # P2Pool pool type → flags + p2p port local pool_type p2pool_flags p2pool_port pool_type=$(jq -r '.p2pool.pool // "mini"' "$CONFIG_FILE") @@ -4123,10 +4141,12 @@ CLEARNET_STATE_DIR=$CLEARNET_STATE_DIR MONERO_PREP_THREADS=$prep_threads MONERO_OUT_PEERS=$out_peers MONERO_RPC_BIND=$rpc_bind +MONERO_ZMQ_BIND=$zmq_bind MONERO_NODE_HOST=$mono_host MONERO_RPC_PORT=$rpc_port MONERO_ZMQ_PORT=$zmq_port TARI_GRPC_ADDRESS=$tari_grpc_addr +TARI_GRPC_BIND=$tari_grpc_bind COMPOSE_PROFILES=$profiles DASHBOARD_SECURE=$DASHBOARD_SECURE DASHBOARD_ONION_ENABLED=$DASHBOARD_ONION_ENABLED @@ -4661,6 +4681,22 @@ describe_change() { msg="Monero RPC bind address: $old → $new." fi ;; + MONERO_ZMQ_BIND) + if [ "$new" == "0.0.0.0" ]; then + flag=DEST + msg="Monero ZMQ will be EXPOSED on your LAN ($old → $new) — it has no auth, trusted networks only." + else + msg="Monero ZMQ bind address: $old → $new." + fi + ;; + TARI_GRPC_BIND) + if [ "$new" == "0.0.0.0" ]; then + flag=DEST + msg="Tari gRPC will be EXPOSED on your LAN ($old → $new) — it is plaintext and unauthenticated, trusted networks only." + else + msg="Tari gRPC bind address: $old → $new." + fi + ;; STRATUM_BIND) if [ "$new" == "0.0.0.0" ]; then flag=DEST diff --git a/tests/stack/run.sh b/tests/stack/run.sh index ff6471a4..611bce22 100755 --- a/tests/stack/run.sh +++ b/tests/stack/run.sh @@ -424,6 +424,11 @@ echo "== unit: describe_change ==" assert_contains "prune disable is DEST" "$(run_sourced "$SANDBOX" describe_change MONERO_PRUNE 1 0)" "DEST" assert_contains "prune enable is CONFIRM" "$(run_sourced "$SANDBOX" describe_change MONERO_PRUNE 0 1)" "CONFIRM" assert_contains "rpc lan is DEST" "$(run_sourced "$SANDBOX" describe_change MONERO_RPC_BIND 127.0.0.1 0.0.0.0)" "DEST" +# LAN exposure of the no-auth node feeds (#760): opening to 0.0.0.0 is DEST in that direction only. +assert_contains "zmq lan is DEST" "$(run_sourced "$SANDBOX" describe_change MONERO_ZMQ_BIND 127.0.0.1 0.0.0.0)" "DEST" +assert_contains "zmq close is INFO" "$(run_sourced "$SANDBOX" describe_change MONERO_ZMQ_BIND 0.0.0.0 127.0.0.1)" "INFO" +assert_contains "tari grpc lan is DEST" "$(run_sourced "$SANDBOX" describe_change TARI_GRPC_BIND 127.0.0.1 0.0.0.0)" "DEST" +assert_contains "tari grpc close is INFO" "$(run_sourced "$SANDBOX" describe_change TARI_GRPC_BIND 0.0.0.0 127.0.0.1)" "INFO" assert_contains "stratum open is DEST" "$(run_sourced "$SANDBOX" describe_change STRATUM_BIND 127.0.0.1 0.0.0.0)" "DEST" assert_contains "stratum lan is INFO" "$(run_sourced "$SANDBOX" describe_change STRATUM_BIND 0.0.0.0 127.0.0.1)" "INFO" # Stratum port (#172/#719): changing it disconnects every rig until repointed — an operator-intent @@ -3387,10 +3392,15 @@ seed_env printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json" out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)" assert_eq "rpc_lan_access default binds monerod RPC to localhost" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_RPC_BIND)" "127.0.0.1" +# The #760 siblings default localhost-only the same way: ZMQ and the Tari gRPC publish. +assert_eq "zmq_lan_access default binds monerod ZMQ to localhost" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_ZMQ_BIND)" "127.0.0.1" +assert_eq "grpc_lan_access default binds tari gRPC to localhost" "$(run_sourced "$V" env_get_file "$V/.env" TARI_GRPC_BIND)" "127.0.0.1" seed_env -printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p","rpc_lan_access":true,"prep_blocks_threads":6}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json" +printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p","rpc_lan_access":true,"zmq_lan_access":true,"prep_blocks_threads":6}, "tari":{"wallet_address":"T","grpc_lan_access":true}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json" out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)" assert_eq "rpc_lan_access true binds monerod RPC to all interfaces" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_RPC_BIND)" "0.0.0.0" +assert_eq "zmq_lan_access true binds monerod ZMQ to all interfaces" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_ZMQ_BIND)" "0.0.0.0" +assert_eq "grpc_lan_access true binds tari gRPC to all interfaces" "$(run_sourced "$V" env_get_file "$V/.env" TARI_GRPC_BIND)" "0.0.0.0" assert_eq "prep_blocks_threads override reflected verbatim" "$(run_sourced "$V" env_get_file "$V/.env" MONERO_PREP_THREADS)" "6" echo "== black-box: monero.out_peers renders to .env, bounds enforced (#595) ==" diff --git a/tests/stack/test_compose.sh b/tests/stack/test_compose.sh index fe1b8543..f04edce6 100755 --- a/tests/stack/test_compose.sh +++ b/tests/stack/test_compose.sh @@ -47,10 +47,12 @@ PROXY_DONATE_LEVEL=1 MONERO_PRUNE=1 MONERO_PREP_THREADS=4 MONERO_RPC_BIND=127.0.0.1 +MONERO_ZMQ_BIND=127.0.0.1 MONERO_NODE_HOST=172.28.0.26 MONERO_RPC_PORT=18081 MONERO_ZMQ_PORT=18083 TARI_GRPC_ADDRESS=172.28.0.27:18142 +TARI_GRPC_BIND=127.0.0.1 COMPOSE_PROFILES=local_node,local_tari DASHBOARD_SECURE=true HOST_IP=box.lan