From efa7db23f616d39bdcebf733b38075c825e66d5c Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 02:15:04 +0000 Subject: [PATCH 1/4] Revert "ltc: de-scope set_coin_node wiring to PR-C so A compiles standalone" This reverts commit ad05cc030f773bf91f1b32be41e7b1ac724a3f03. --- src/c2pool/c2pool_refactored.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index 0c9f81d8..d4052cb9 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -46,6 +46,12 @@ #include #include #include +// NOTE: must follow node.hpp/messages.hpp. coin_node.hpp pulls in +// btclibs/serialize.h, which #undefs READWRITE and redefines it to the +// (s, ser_action, ...) form. Included earlier, the MESSAGE_FIELDS macros in +// messages.hpp expand against that wrong READWRITE and fail to compile under +// AppleClang/arm64 ("use of undeclared identifier s / ser_action"). +#include #include // Chain seed discovery @@ -1502,6 +1508,10 @@ int main(int argc, char* argv[]) { // ── Coin node: embedded (Phase 4) or RPC (legacy) ───────────────── ltc::interfaces::Node coin_node; std::unique_ptr node_rpc; + // P2 WorkView seam: web_server holds a core::coin::ICoinNode*; the + // concrete ltc::coin::CoinNode owns the embedded/rpc decision + the + // full WorkData coin-side. Declared before web_server so it outlives it. + std::unique_ptr coin_iface; // Phase 4 embedded objects (alive for the duration of integrated mode) std::unique_ptr embedded_chain; @@ -1971,10 +1981,14 @@ int main(int argc, char* argv[]) { // Wire live coin-daemon RPC so getblocktemplate/submitblock use real data if (!embedded_ltc) { - web_server.set_coin_rpc(node_rpc.get(), &coin_node); + coin_iface = std::make_unique( + /*embedded*/nullptr, /*rpc*/node_rpc.get()); + web_server.set_coin_node(coin_iface.get()); } else if (embedded_broadcaster && embedded_chain) { // Wire embedded node + header-sync callback (now that web_server is alive) - web_server.set_embedded_node(embedded_node.get()); + coin_iface = std::make_unique( + /*embedded*/embedded_node.get(), /*rpc*/node_rpc.get()); + web_server.set_coin_node(coin_iface.get()); // Wire block verification for embedded mode auto* mi = web_server.get_mining_interface(); @@ -7150,12 +7164,16 @@ int main(int argc, char* argv[]) { solo_node_rpc->connect(NetService(rpc_host, static_cast(rpc_port)), rpc_user + ":" + rpc_pass); + // P2 WorkView seam: own the CoinNode before solo_server so it outlives it. + auto solo_coin_iface = std::make_unique( + /*embedded*/nullptr, /*rpc*/solo_node_rpc.get()); + // Create a minimal web server for solo mining (Stratum only) core::WebServer solo_server(ioc, http_host, 8083, settings->m_testnet, nullptr, blockchain); // Wire coin daemon so solo Stratum gets live GBT + submitblock - solo_server.set_coin_rpc(solo_node_rpc.get(), &solo_coin_node); + solo_server.set_coin_node(solo_coin_iface.get()); // Configure payout system for solo server solo_server.set_payout_manager(payout_manager.get()); From d1012a46c6b18a86e3b5c7870379788dd111b872 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 02:15:24 +0000 Subject: [PATCH 2/4] core/web_server: retype coin-node seam to core::coin::ICoinNode web_server is SHARED core but held raw pointers to CONCRETE impl/ltc types (ltc::coin::NodeRPC, ltc::interfaces::Node, ltc::coin::CoinNodeInterface), which left undefined ltc:: coin-RPC symbols (NodeRPC::getwork/submit_block_hex) in the BTC link and kept btc smoke red. Collapse the three concrete pointers + two setters (set_coin_rpc / set_embedded_node) in both MiningInterface and WebServer into a single core::coin::ICoinNode* + set_coin_node(). The concrete per-coin node (ltc::coin::CoinNode / btc::coin::CoinNode) now makes the embedded-vs-rpc decision coin-side and crosses the seam only via get_work_view() (agnostic WorkView slice), submit_block_hex() (2-arg, no mweb), is_embedded() and has_rpc(). Full per-coin WorkData (incl. m_txs) never leaves the coin. No behavior change: refresh_work() keeps local wd (now a WorkView with the same m_data/m_hashes/m_latency fields), submit path splits on has_rpc()/ is_embedded(), source labels + status JSON keys preserved, and the WebServer initial-template-fetch timer stays rpc-only via has_rpc(). Pairs with the reverted de-scope (574e2444) that re-wires the set_coin_node callers in c2pool_refactored.cpp. --- src/core/web_server.cpp | 68 ++++++++++++++++------------------------- src/core/web_server.hpp | 30 ++++++------------ 2 files changed, 36 insertions(+), 62 deletions(-) diff --git a/src/core/web_server.cpp b/src/core/web_server.cpp index cde79edf..2884296a 100644 --- a/src/core/web_server.cpp +++ b/src/core/web_server.cpp @@ -4,9 +4,10 @@ #include "address_utils.hpp" #include "socket.hpp" -// Real coin daemon RPC (optional - only linked when set_coin_rpc() is called) +// Real coin daemon access (optional - only active when set_coin_node() is called) #include #include +#include // Phase 4: embedded coin node interface #include #include @@ -1139,17 +1140,10 @@ std::vector MiningInterface::get_operator_message_blob() const // ─── Live coin-daemon integration ──────────────────────────────────────────── -void MiningInterface::set_coin_rpc(ltc::coin::NodeRPC* rpc, ltc::interfaces::Node* coin) +void MiningInterface::set_coin_node(core::coin::ICoinNode* node) { - m_coin_rpc = rpc; - m_coin_node = coin; - LOG_INFO << "MiningInterface: coin RPC " << (rpc ? "connected" : "disconnected"); -} - -void MiningInterface::set_embedded_node(ltc::coin::CoinNodeInterface* node) -{ - m_embedded_node = node; - LOG_INFO << "MiningInterface: embedded coin node " << (node ? "connected" : "disconnected"); + m_coin_node = node; + LOG_INFO << "MiningInterface: coin node " << (node ? "attached" : "detached"); } void MiningInterface::set_on_block_submitted(std::function fn) @@ -2153,7 +2147,7 @@ MiningInterface::build_connection_coinbase( void MiningInterface::refresh_work() { - if (!m_coin_rpc && !m_embedded_node) return; + if (!m_coin_node) return; // Serialize refresh_work calls — two concurrent threads (e.g. embedded LTC // header callback + stratum submit) hitting build_coinbase_parts() in parallel @@ -2163,13 +2157,10 @@ void MiningInterface::refresh_work() if (!refresh_lock.owns_lock()) return; // another refresh in progress, skip try { - // Phase 4: prefer embedded node; fall back to RPC (HybridCoinNode pattern) - auto wd = m_embedded_node ? m_embedded_node->getwork() - : m_coin_rpc->getwork(); - - // Update the coin node's Variable so submit_block() can read it - if (m_coin_node) - m_coin_node->work.set(wd); + // P2 WorkView seam: the concrete coin node makes the embedded-vs-RPC + // decision internally, retains the full per-coin WorkData coin-side + // (work.set), and returns only the agnostic slice (m_data/m_hashes/m_latency). + auto wd = m_coin_node->get_work_view(); // Collect tx hashes from WorkData std::vector tx_hashes_hex; @@ -2725,9 +2716,9 @@ nlohmann::json MiningInterface::submitblock(const std::string& hex_data, const s } } - if (m_coin_rpc) { + if (m_coin_node && m_coin_node->has_rpc()) { try { - bool accepted = m_coin_rpc->submit_block_hex(hex_data, "", false); + bool accepted = m_coin_node->submit_block_hex(hex_data, false); LOG_INFO << "Block forwarded to coin daemon"; if (accepted) { // Notify P2P layer with stale_info=0 (none — accepted) @@ -2747,7 +2738,7 @@ nlohmann::json MiningInterface::submitblock(const std::string& hex_data, const s } return {{"error", std::string(e.what())}}; } - } else if (m_embedded_node) { + } else if (m_coin_node && m_coin_node->is_embedded()) { // Phase 4 embedded mode: no daemon — relay the block directly via P2P. // on_block_relay is wired to CoinBroadcaster::submit_block_raw(). size_t block_size = hex_data.size() / 2; @@ -4420,10 +4411,10 @@ nlohmann::json MiningInterface::rest_local_stats() auto now_s = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); if (m_last_work_update_time > 0 && now_s - m_last_work_update_time > 60) { - std::string src = m_embedded_node ? "EMBEDDED LTC NODE" : "LTC DAEMON"; + std::string src = (m_coin_node && m_coin_node->is_embedded()) ? "EMBEDDED LTC NODE" : "LTC DAEMON"; warnings.push_back("LOST CONTACT WITH " + src + " for " + std::to_string(now_s - m_last_work_update_time) + "s! " - + (m_embedded_node ? "No new blocks received from P2P peers." + + ((m_coin_node && m_coin_node->is_embedded()) ? "No new blocks received from P2P peers." : "Check that it isn't frozen or dead!")); } @@ -4445,10 +4436,10 @@ nlohmann::json MiningInterface::rest_local_stats() for (const auto& ci : chain_infos) { int64_t threshold = 180; // 3 minutes default (covers DOGE 1min blocks) if (ci.last_update_age_s > threshold) { - std::string src = m_embedded_node ? " EMBEDDED NODE" : " DAEMON"; + std::string src = (m_coin_node && m_coin_node->is_embedded()) ? " EMBEDDED NODE" : " DAEMON"; warnings.push_back("LOST CONTACT WITH " + ci.symbol + src + " for " + std::to_string(ci.last_update_age_s) + "s!" - + (m_embedded_node ? " No new blocks from P2P peers." : "")); + + ((m_coin_node && m_coin_node->is_embedded()) ? " No new blocks from P2P peers." : "")); } } } @@ -4693,8 +4684,8 @@ nlohmann::json MiningInterface::rest_web_currency_info() result["explorer_url"] = m_explorer_url; // Mode indicators for conditional UI - result["embedded"] = (m_embedded_node != nullptr); - result["has_rpc"] = (m_coin_rpc != nullptr); + result["embedded"] = (m_coin_node && m_coin_node->is_embedded()); + result["has_rpc"] = (m_coin_node && m_coin_node->has_rpc()); // p2pool share version for the current coin — consumed by the // bundled @c2pool/sharechain-explorer to classify share cells. @@ -7349,7 +7340,7 @@ nlohmann::json MiningInterface::mining_submit(const std::string& username, const LOG_INFO << "Solo mining share accepted - primary payout address: " << payout_address; // Check if share meets network difficulty and attempt block submission - if ((m_coin_rpc || m_embedded_node) && !extranonce1.empty()) { + if (m_coin_node && !extranonce1.empty()) { std::string block_hex = build_block_from_stratum(extranonce1, extranonce2, ntime, nonce, job); if (!block_hex.empty()) { // Check merged mining targets for every share (aux targets are lower) @@ -7740,7 +7731,7 @@ nlohmann::json MiningInterface::mining_submit(const std::string& username, const } // Attempt block construction + submission only when PoW meets blockchain target. - if ((m_coin_rpc || m_embedded_node) && !extranonce1.empty()) { + if (m_coin_node && !extranonce1.empty()) { std::string block_hex = build_block_from_stratum(extranonce1, extranonce2, ntime, nonce, job); if (!block_hex.empty()) { // Check merged mining targets for every share (aux targets are lower) @@ -8044,7 +8035,7 @@ bool WebServer::start() // One-shot initial template fetch: populate the cache before any events fire. // After this, all template updates are event-driven (bestblock, best_share_changed). // p2pool has no recurring refresh timer — Twisted reactor events handle everything. - if (m_coin_rpc_) { + if (m_coin_node_ && m_coin_node_->has_rpc()) { auto timer = std::make_shared(ioc_); timer->expires_after(std::chrono::milliseconds(500)); timer->async_wait([this, timer](beast::error_code ec) { @@ -8152,18 +8143,11 @@ void WebServer::trigger_work_refresh_debounced() } } -void WebServer::set_coin_rpc(ltc::coin::NodeRPC* rpc, ltc::interfaces::Node* coin) -{ - m_coin_rpc_ = rpc; - m_coin_node_ = coin; - mining_interface_->set_coin_rpc(rpc, coin); - LOG_INFO << "WebServer: coin RPC " << (rpc ? "attached" : "detached"); -} - -void WebServer::set_embedded_node(ltc::coin::CoinNodeInterface* node) +void WebServer::set_coin_node(core::coin::ICoinNode* node) { - mining_interface_->set_embedded_node(node); - LOG_INFO << "WebServer: embedded coin node " << (node ? "attached" : "detached"); + m_coin_node_ = node; + mining_interface_->set_coin_node(node); + LOG_INFO << "WebServer: coin node " << (node ? "attached" : "detached"); } void WebServer::set_best_share_hash_fn(std::function fn) diff --git a/src/core/web_server.hpp b/src/core/web_server.hpp index 64f16dbc..9b17c1c2 100644 --- a/src/core/web_server.hpp +++ b/src/core/web_server.hpp @@ -42,9 +42,7 @@ using AddressValidationResult = c2pool::address::AddressValidationResult; using BlockchainAddressValidator = c2pool::address::BlockchainAddressValidator; // Forward declarations for optional coin daemon integration (avoid layering violation) -namespace ltc { namespace coin { class NodeRPC; } } -namespace ltc { namespace coin { class CoinNodeInterface; } } -namespace ltc { namespace interfaces { struct Node; } } +namespace core { namespace coin { struct ICoinNode; } } namespace core { @@ -649,11 +647,9 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server, void set_payout_manager(c2pool::payout::PayoutManager* manager) { m_payout_manager_ptr = manager; } c2pool::payout::PayoutManager* get_payout_manager_ptr() const { return m_payout_manager_ptr; } - // Wire a live coin-daemon RPC connection so getblocktemplate/submitblock use real data - void set_coin_rpc(ltc::coin::NodeRPC* rpc, ltc::interfaces::Node* coin = nullptr); - // Wire an embedded coin node (Phase 4) — used instead of RPC when set. - // refresh_work() will call node->getwork(); block submissions relay via on_block_relay. - void set_embedded_node(ltc::coin::CoinNodeInterface* node); + // Wire the per-coin node (embedded and/or RPC) behind the agnostic seam. + // refresh_work() calls get_work_view(); submissions go via submit_block_hex(). + void set_coin_node(core::coin::ICoinNode* node); // Fetch a fresh block template from the coin daemon and cache it void refresh_work(); // Return the most recently cached block template (empty json if unavailable) @@ -965,11 +961,8 @@ class MiningInterface : public jsonrpccxx::JsonRpc2Server, // Payout system integration c2pool::payout::PayoutManager* m_payout_manager_ptr = nullptr; - // Real coin daemon connection (replaces mock LitecoinRpcClient) - ltc::coin::NodeRPC* m_coin_rpc = nullptr; - ltc::interfaces::Node* m_coin_node = nullptr; - // Phase 4: embedded coin node (preferred over RPC when set) - ltc::coin::CoinNodeInterface* m_embedded_node = nullptr; + // Per-coin node behind the agnostic seam (embedded and/or RPC, decided coin-side) + core::coin::ICoinNode* m_coin_node = nullptr; // io_context for scheduling async verification timers boost::asio::io_context* m_context = nullptr; std::thread::id m_main_thread_id; // set by set_io_context() @@ -1485,9 +1478,8 @@ class WebServer // Payout system integration c2pool::payout::PayoutManager* payout_manager_ptr_ = nullptr; - // Optional coin daemon RPC (enables real getblocktemplate + submitblock) - ltc::coin::NodeRPC* m_coin_rpc_ = nullptr; - ltc::interfaces::Node* m_coin_node_ = nullptr; + // Per-coin node behind the agnostic seam (enables getblocktemplate + submitblock) + core::coin::ICoinNode* m_coin_node_ = nullptr; public: WebServer(net::io_context& ioc, const std::string& address, uint16_t port, bool testnet = false); @@ -1530,10 +1522,8 @@ class WebServer void set_explorer_enabled(bool enabled); void set_explorer_url(const std::string& url); - // Wire a live coin-daemon RPC connection for block template generation - void set_coin_rpc(ltc::coin::NodeRPC* rpc, ltc::interfaces::Node* coin = nullptr); - // Wire an embedded coin node (Phase 4 — preferred over RPC when set) - void set_embedded_node(ltc::coin::CoinNodeInterface* node); + // Wire the per-coin node (embedded and/or RPC) behind the agnostic seam + void set_coin_node(core::coin::ICoinNode* node); // Forward block-found callback to the underlying MiningInterface void set_on_block_submitted(std::function fn); // Forward P2P block relay callback to the underlying MiningInterface From 40d6828e786565fc1c1839e9d9db230d83c4f266 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 02:34:56 +0000 Subject: [PATCH 3/4] build(c2pool-btc): link ltc_coin to resolve ltc protocol-handler tx ctors web_server retype removed the coin-node coupling, but c2pool-btc still links the ltc OBJECT lib for web_server share-message/config symbols (later-B-phase decouple). That pulls in ltc protocol handlers needing ltc::coin::{Mutable,}Transaction ctors, which live in ltc_coin. c2pool/c2pool_enhanced already link ltc_coin; mirror that for c2pool-btc. btc-target-only. --- src/c2pool/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/c2pool/CMakeLists.txt b/src/c2pool/CMakeLists.txt index 2d7ad277..540b9412 100644 --- a/src/c2pool/CMakeLists.txt +++ b/src/c2pool/CMakeLists.txt @@ -148,6 +148,7 @@ target_link_libraries(c2pool-btc c2pool_merged_mining core ltc + ltc_coin # ltc OBJECT lib pulls in protocol handlers needing ltc::coin::{Mutable,}Transaction ctors btc btc_stratum nlohmann_json::nlohmann_json From c75393a6906f8bbfa0653fcc72fc9ab15cafa754 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 02:57:19 +0000 Subject: [PATCH 4/4] test(phase4): migrate set_embedded_node -> set_coin_node callers web_server retype (d1012a46) renamed the production seam MiningInterface::set_embedded_node(CoinNodeInterface*) -> set_coin_node(core::coin::ICoinNode*). The phase4 test callers still named the old API and passed a CoinNodeInterface mock, breaking the Linux x86_64 test-suite build. Wrap the existing CoinNodeInterface mock/embedded node in the production ltc::coin::CoinNode adapter (the concrete ICoinNode) exactly as c2pool_refactored.cpp:1984/1990 does, then call set_coin_node. The adapter funnels getwork() through get_work_view(), so each test template (height=1 mock / chain.height()+1 live) is byte-for-byte preserved. Pure API-rename migration; test intent unchanged. --- test/test_phase4_embedded.cpp | 11 +++++++---- test/test_phase4_live.cpp | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/test/test_phase4_embedded.cpp b/test/test_phase4_embedded.cpp index f4619103..cac55ba2 100644 --- a/test/test_phase4_embedded.cpp +++ b/test/test_phase4_embedded.cpp @@ -20,7 +20,7 @@ /// 15. HeaderChain::get_header returns correct entry for tip /// 16. block_rel_height via HeaderChain: genesis depth = 1 /// 17. block_rel_height via HeaderChain: unknown hash → 0 -/// 18. MiningInterface::set_embedded_node accepts CoinNodeInterface* +/// 18. MiningInterface::set_coin_node accepts core::coin::ICoinNode* /// 19. refresh_work with embedded node populates cached template /// 20. refresh_work falls back to RPC when embedded node is null @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -312,19 +313,21 @@ TEST(Phase4ChainDepthTest, UnknownHashReturnsNoEntry) { TEST(Phase4MiningInterfaceTest, SetEmbeddedNodeAcceptsNull) { core::MiningInterface mi(/*testnet=*/false, nullptr, c2pool::address::Blockchain::LITECOIN); - EXPECT_NO_THROW(mi.set_embedded_node(nullptr)); + EXPECT_NO_THROW(mi.set_coin_node(nullptr)); } TEST(Phase4MiningInterfaceTest, SetEmbeddedNodeAcceptsMock) { core::MiningInterface mi(false, nullptr, c2pool::address::Blockchain::LITECOIN); MockCoinNode mock; - EXPECT_NO_THROW(mi.set_embedded_node(&mock)); + ltc::coin::CoinNode node(&mock, /*rpc=*/nullptr); + EXPECT_NO_THROW(mi.set_coin_node(&node)); } TEST(Phase4MiningInterfaceTest, RefreshWorkWithEmbeddedNodePopulatesTemplate) { core::MiningInterface mi(false, nullptr, c2pool::address::Blockchain::LITECOIN); MockCoinNode mock; - mi.set_embedded_node(&mock); + ltc::coin::CoinNode node(&mock, /*rpc=*/nullptr); + mi.set_coin_node(&node); EXPECT_NO_THROW(mi.refresh_work()); diff --git a/test/test_phase4_live.cpp b/test/test_phase4_live.cpp index 5d9de5f0..8dbd02b9 100644 --- a/test/test_phase4_live.cpp +++ b/test/test_phase4_live.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -239,7 +240,8 @@ TEST_F(Phase4LiveTest, MiningInterfaceRefreshWorkWithEmbeddedNode) // Wire embedded node into MiningInterface EmbeddedCoinNode embedded(chain, pool, true); core::MiningInterface mi(/*testnet=*/true, nullptr, c2pool::address::Blockchain::LITECOIN); - mi.set_embedded_node(&embedded); + ltc::coin::CoinNode coin_iface(&embedded, /*rpc=*/nullptr); + mi.set_coin_node(&coin_iface); EXPECT_NO_THROW(mi.refresh_work());