From fce2a2b7bf4fcb8c28b83d9b497f48d47665c95f Mon Sep 17 00:00:00 2001 From: frstrtr Date: Tue, 21 Jul 2026 07:48:49 +0000 Subject: [PATCH] dash(web): --external-ip override for the dashboard Stratum URL (NAT/port-mapped nodes) The dashboard Stratum-URL card renders nodeInfo.external_ip. On a node that NATs out through a shared gateway (both hotel DASH nodes: LAN 192.168.1.x behind one gateway), the auto-detected OUTBOUND IP is not the address miners dial -- they reach the external-mapped hosts. Add --external-ip (alias --stratum-advertise / --public-host) to main_dash, feeding the existing MiningInterface::set_external_ip() seam (parity with main_ltc.cpp:1626). rest_node_info() then serves the operator-advertised host verbatim; unset keeps the honest-absent 0.0.0.0 sentinel so the dashboard auto-detect / window.location.hostname fallback is unchanged (no regression). Web layer only, non-consensus. Regression-locks the flag -> served-external_ip plumbing with two KATs (unset honest-absent; operator host served verbatim). --- src/c2pool/main_dash.cpp | 28 +++++++++++++++++++++++-- test/test_web_honesty_regression.cpp | 31 +++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/c2pool/main_dash.cpp b/src/c2pool/main_dash.cpp index 715f7a1ed..242fef84f 100644 --- a/src/c2pool/main_dash.cpp +++ b/src/c2pool/main_dash.cpp @@ -199,6 +199,7 @@ void print_banner(const char* argv0) << " [--listen [HOST:]PORT] [--addnode HOST:PORT]... [--connect HOST:PORT]...\n" << " [--stratum [HOST:]PORT] [--coin-p2p-connect HOST:PORT]...\n" << " [--web-port PORT] [--web-host ADDR] [--dashboard-dir PATH]\n" + << " [--external-ip ADDR]\n" << " [--embedded-utxo]\n" << " [--give-author PCT] [-f|--fee PCT] [--node-owner-address ADDR]\n" << " [--redistribute pplns|fee|boost|donate]\n" @@ -229,6 +230,10 @@ void print_banner(const char* argv0) << " are bound to the REAL DASH tracker; local hashrate comes from the\n" << " DASH stratum acceptor. If stratum and web ports collide the web\n" << " port moves to stratum+1.\n" + << " --external-ip ADDR (alias --stratum-advertise / --public-host)\n" + << " overrides the miner-facing host shown in the dashboard Stratum\n" + << " URL -- for NAT / port-mapped nodes whose outbound IP is not the\n" + << " address miners dial; unset => auto-detect (no regression).\n" << "Consensus: X11 PoW + block identity; 2.5 min spacing; 5 DASH post-V20\n" << " base, -1/14 per 210240; masternode payment 3/4 of block value.\n"; } @@ -395,7 +400,8 @@ int run_node(bool testnet, const std::string& rpc_endpoint, const std::string& redistribute_mode, bool no_p2p_relay, bool embedded_mainnet, - const std::string& coin_zmq_hashblock) + const std::string& coin_zmq_hashblock, + const std::string& external_ip) { namespace io = boost::asio; @@ -568,6 +574,12 @@ int run_node(bool testnet, const std::string& rpc_endpoint, #endif mi->set_worker_port(stratum_port); // display only (see divergence note) mi->set_p2p_port(bind_port); + // Serve the operator-advertised miner-facing host (--external-ip) so + // the dashboard Stratum URL renders the NAT-external address miners + // actually dial, not the auto-detected outbound gateway IP. Unset => + // MiningInterface keeps its auto-detect path (no regression). + if (!external_ip.empty()) + mi->set_external_ip(external_ip); // p2pool-compat protocol_version for /local_stats. The core seam // documents this exact value for DASH (web_server.hpp:565) and it // matches the sharechain SSOT floor. @@ -2267,6 +2279,9 @@ int main(int argc, char** argv) std::string dashboard_dir = "web-static"; // --dashboard-dir static asset root std::string redistribute_mode = "pplns"; // --redistribute pplns|fee|boost|donate std::string coin_zmq_hashblock; // --coin-zmq-hashblock ENDPOINT (opt-in dashd ZMQ hashblock instant tip-notify, e.g. tcp://127.0.0.1:28332) + // Operator-supplied miner-facing host for the dashboard Stratum URL. + // Empty => auto-detect the outbound public IP (current behaviour). + std::string external_ip; // --external-ip / --stratum-advertise / --public-host for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "--version") == 0) { std::cout << "c2pool-dash " << C2POOL_VERSION << "\n"; @@ -2337,6 +2352,14 @@ int main(int argc, char** argv) } else if (std::strcmp(argv[i], "--web-host") == 0 && i + 1 < argc) web_host = argv[++i]; + // Miner-facing host override for the dashboard Stratum URL. Both hotel + // nodes NAT out through one gateway, so the auto-detected outbound IP + // is NOT the address miners reach; the operator advertises the real + // external-mapped host here. Aliases match how the flag is referenced. + else if ((std::strcmp(argv[i], "--external-ip") == 0 || + std::strcmp(argv[i], "--stratum-advertise") == 0 || + std::strcmp(argv[i], "--public-host") == 0) && i + 1 < argc) + external_ip = argv[++i]; else if (std::strcmp(argv[i], "--dashboard-dir") == 0 && i + 1 < argc) dashboard_dir = argv[++i]; else if (std::strcmp(argv[i], "--stratum") == 0 && i + 1 < argc) { @@ -2418,7 +2441,8 @@ int main(int argc, char** argv) embedded_utxo, dev_donation, node_owner_fee, node_owner_address, redistribute_mode, no_p2p_relay, embedded_mainnet, - coin_zmq_hashblock); + coin_zmq_hashblock, + external_ip); } return run_selftest(); } \ No newline at end of file diff --git a/test/test_web_honesty_regression.cpp b/test/test_web_honesty_regression.cpp index 54dacb45f..f295e9c14 100644 --- a/test/test_web_honesty_regression.cpp +++ b/test/test_web_honesty_regression.cpp @@ -574,4 +574,33 @@ TEST(WebHonestyRegression, PatronSendmanySelfLabelsAsStubNeverFabricatesPayouts) << "never label-as-stub while surfacing payout splits -- silent lie"; EXPECT_EQ(p.value("total", std::string{}), "12.5") << "the echoed total must be the operator-supplied value, not invented"; -} \ No newline at end of file +} +// ── Stratum-URL external_ip override (DASH NAT/port-mapped nodes) ────────── +// The dashboard Stratum-URL card renders nodeInfo.external_ip +// (dashboard.html:2889-2892). When a node NATs out through a shared gateway +// (both hotel DASH nodes: LAN 192.168.1.x, one public 31.172.65.125), the +// auto-detected OUTBOUND IP is NOT the address miners dial -- they reach the +// external-mapped hosts (109.161.57.3 / 109.161.52.148). c2pool-dash exposes +// --external-ip (alias --stratum-advertise / --public-host) which feeds +// set_external_ip(); rest_node_info() must then SERVE that operator-supplied +// host verbatim so the Stratum URL is truthful. Unset must stay honest-absent +// ("0.0.0.0"), leaving the auto-detect / window.location.hostname fallback -- +// no regression. Pins the flag -> served-external_ip plumbing. +TEST(WebHonestyRegression, NodeInfoExternalIpUnsetIsHonestlyUnspecified) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::DASH); + json ni = mi.rest_node_info(); + EXPECT_EQ(ni.value("external_ip", std::string{}), "0.0.0.0") + << "unset external_ip must serve the honest-absent sentinel so the " + "dashboard falls back to auto-detect / window.location.hostname"; +} + +TEST(WebHonestyRegression, NodeInfoExternalIpServesOperatorAdvertisedHost) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::DASH); + // Operator advertises the real miner-facing external-mapped host (primary + // hotel node), NOT the auto-detected 31.172.65.125 NAT gateway. + mi.set_external_ip("109.161.57.3"); + json ni = mi.rest_node_info(); + EXPECT_EQ(ni.value("external_ip", std::string{}), "109.161.57.3") + << "served external_ip must be the operator-advertised miner-facing " + "host so the dashboard Stratum URL is not the wrong NAT IP"; +}