From 66f754b3e8e6787993ab35e04f5c0e8ea8b31818 Mon Sep 17 00:00:00 2001 From: Alec Muffett Date: Fri, 5 Jun 2026 17:10:39 +0400 Subject: [PATCH 1/4] ltc: ltc::coin::CoinNode + c2pool_refactored retype for P2 WorkView seam Cluster A of the web_server core-seam fold (base 4c4b08b2, btc-embedded P2 WorkView seam). Additive LTC reference impl of core::coin::ICoinNode: - NEW src/impl/ltc/coin/coin_node.{hpp,cpp}: concrete CoinNode collapsing the embedded-vs-rpc getwork decision and the full WorkData (incl. m_txs) retention coin-side; exposes only the agnostic WorkView + 2-arg submit_block_hex across the seam. MWEB arity bridged with mweb="" (the sole pre-seam caller already passed ""); standing flag noted in-source. - src/c2pool/c2pool_refactored.cpp: retype the integrated rpc/embedded and solo wiring sites from set_coin_rpc/set_embedded_node(concrete ltc types) to set_coin_node(ICoinNode*); own the CoinNode before web_server so it outlives it. - src/impl/ltc/coin/CMakeLists.txt: add coin_node sources. Pushed for btc-heap-opt to fetch and fold with Cluster B (btc OBJECT lib + btc mirror) into one signed head. Does not link standalone (btc side still red until B). Linux x86_64 four-coin ctest to be re-run first-hand at the combined nm-clean head before the seam-gate flip. --- src/c2pool/c2pool_refactored.cpp | 19 +++++++-- src/impl/ltc/coin/CMakeLists.txt | 1 + src/impl/ltc/coin/coin_node.cpp | 60 ++++++++++++++++++++++++++ src/impl/ltc/coin/coin_node.hpp | 73 ++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 src/impl/ltc/coin/coin_node.cpp create mode 100644 src/impl/ltc/coin/coin_node.hpp diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index 0c9f81d88..9423e9d39 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -1502,6 +1503,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 +1976,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 +7159,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()); diff --git a/src/impl/ltc/coin/CMakeLists.txt b/src/impl/ltc/coin/CMakeLists.txt index 27a92440a..b0a4f99fd 100644 --- a/src/impl/ltc/coin/CMakeLists.txt +++ b/src/impl/ltc/coin/CMakeLists.txt @@ -6,6 +6,7 @@ set(ltc_coin_impl p2p_node.cpp p2p_node.hpp p2p_messages.hpp node.hpp + coin_node.hpp coin_node.cpp ) set(ltc_coin_interface diff --git a/src/impl/ltc/coin/coin_node.cpp b/src/impl/ltc/coin/coin_node.cpp new file mode 100644 index 000000000..da551fa7a --- /dev/null +++ b/src/impl/ltc/coin/coin_node.cpp @@ -0,0 +1,60 @@ +#include "coin_node.hpp" + +#include +#include + +namespace ltc +{ + +namespace coin +{ + +core::coin::WorkView CoinNode::get_work_view() +{ + // No work source configured at all -> empty view. + if (!m_embedded && !m_rpc) + return {}; + + // Embedded preferred, external RPC fallback. getwork() throws + // std::runtime_error when no template can be produced -- propagated to the + // caller (web_server), matching the ICoinNode contract. Folds the old + // web_server.cpp:2167-2168 embedded-vs-rpc decision coin-side. + rpc::WorkData wd = m_embedded ? m_embedded->getwork() : m_rpc->getwork(); + + // Retain the FULL WorkData (incl. m_txs) coin-side. Variable::set takes its + // argument BY VALUE, so this copies wd; sequenced BEFORE the std::move()s + // below so the copy completes first -- never move out of wd beforehand. + // (Pre-seam this was web_server.cpp:2171-2172 m_coin_node->work.set(wd), + // which only ran in rpc mode; keeping it unconditional here is strictly + // safe -- the agnostic slice still leaves, the payload still stays.) + work.set(wd); + + core::coin::WorkView v; + v.m_data = std::move(wd.m_data); + v.m_hashes = std::move(wd.m_hashes); + v.m_latency = wd.m_latency; + return v; +} + +bool CoinNode::submit_block_hex(const std::string& block_hex, bool ignore_failure) +{ + // Guard sits ahead of the submit: in embedded-only mode m_rpc can be null. + // Returning false is the correct "no RPC sink" result (mirrors the old + // web_server.cpp:2728 `if (m_coin_rpc)` guard). + if (!m_rpc) + return false; + + // MWEB arity bridge: ltc NodeRPC::submit_block_hex is the 3-arg + // (block_hex, mweb, ignore_failure) form. The ICoinNode contract is the + // 2-arg coin-agnostic form, so we supply mweb="" here. The sole pre-seam + // caller (web_server.cpp:2730) already passed "" for mweb, so this is + // behaviour-preserving. FLAG (per integrator, for review): when MWEB + // submit grows a non-empty extension-block path, mweb must NOT be hard-"" + // -- it would need a coin-side source (e.g. the retained WorkData) rather + // than a new arg on the shared seam, since mweb is LTC-specific. + return m_rpc->submit_block_hex(block_hex, "", ignore_failure); +} + +} // namespace coin + +} // namespace ltc diff --git a/src/impl/ltc/coin/coin_node.hpp b/src/impl/ltc/coin/coin_node.hpp new file mode 100644 index 000000000..96dff30a0 --- /dev/null +++ b/src/impl/ltc/coin/coin_node.hpp @@ -0,0 +1,73 @@ +#pragma once + +// --------------------------------------------------------------------------- +// ltc::coin::CoinNode -- concrete core::coin::ICoinNode for LTC (family-1 P2 +// WorkView seam). This is the REFERENCE impl the btc::coin::CoinNode mirrors +// (piece-5(c)); the control flow / sequencing here are the load-bearing part. +// +// m_embedded : CoinNodeInterface* -- EmbeddedCoinNode in-process source +// m_rpc : NodeRPC* -- external coin-RPC client +// work : Variable -- full per-coin WorkData kept coin-side +// +// web_server (shared core) historically held raw pointers to CONCRETE ltc types +// (ltc::coin::NodeRPC, ltc::interfaces::Node) and called getwork()/submit on +// them directly -- that names ltc:: symbols in web_server.cpp.o and breaks the +// BTC link. This type collapses the embedded-vs-rpc decision plus the full +// WorkData retention coin-side, and exposes only the coin-agnostic +// core::coin::ICoinNode contract across the seam (WorkView, 2-arg submit). +// +// MWEB arity: ltc::coin::NodeRPC::submit_block_hex is the 3-arg +// (block_hex, mweb, ignore_failure) LTC form. The ICoinNode override is the +// coin-agnostic 2-arg form; it forwards to the 3-arg rpc with mweb="" coin-side. +// The sole pre-seam caller (web_server.cpp:2730) already passed mweb="", so +// dropping the mweb slot across the seam is behaviour-preserving for MWEB +// submit. (See coin_node.cpp for the forward + the standing flag.) +// --------------------------------------------------------------------------- + +#include + +#include +#include +#include + +#include "rpc.hpp" +#include "rpc_data.hpp" +#include "template_builder.hpp" + +namespace ltc +{ + +namespace coin +{ + +class CoinNode : public core::coin::ICoinNode +{ + // Embedded in-process template source (EmbeddedCoinNode, via the + // CoinNodeInterface base). May be null when the node runs RPC-only. + // is_embedded() reports its presence. + CoinNodeInterface* m_embedded = nullptr; + + // External coin-RPC client. May be null in embedded-only mode; it is the + // sole sink for submit_block_hex(). has_rpc() reports its presence. + NodeRPC* m_rpc = nullptr; + + // Full per-coin WorkData (incl. vector m_txs) retained + // coin-side via work.set(wd); only the agnostic WorkView slice crosses the + // seam into core/web_server. Replaces the old ltc::interfaces::Node::work + // slot that web_server used to reach through m_coin_node. + Variable work; + +public: + CoinNode(CoinNodeInterface* embedded, NodeRPC* rpc) + : m_embedded(embedded), m_rpc(rpc) {} + + core::coin::WorkView get_work_view() override; + bool submit_block_hex(const std::string& block_hex, bool ignore_failure) override; + + bool is_embedded() const override { return m_embedded != nullptr; } + bool has_rpc() const override { return m_rpc != nullptr; } +}; + +} // namespace coin + +} // namespace ltc From f69bd24809917e5af48855907069cd211e8a5085 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Wed, 10 Jun 2026 23:37:41 +0000 Subject: [PATCH 2/4] =?UTF-8?q?ltc:=20fix=20macOS/arm64=20build=20?= =?UTF-8?q?=E2=80=94=20order=20coin=5Fnode.hpp=20include=20after=20message?= =?UTF-8?q?s.hpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-A inserted #include before node.hpp in c2pool_refactored.cpp. coin_node.hpp transitively pulls btclibs/serialize.h, which #undefs READWRITE and redefines it to the (s, ser_action, ...) form. With the include placed first, the MESSAGE_FIELDS macros in messages.hpp (reached via node.hpp) expand against that wrong READWRITE, yielding "use of undeclared identifier s / ser_action" under AppleClang/arm64. Move the include to after node.hpp/messages.hpp so the preprocessor state at the messages.hpp expansion point is identical to the pre-PR-A build (green on all platforms). coin_node.hpp and the following config.hpp use no serialize macros, so nothing downstream is affected. Behaviour-preserving; additive. --- src/c2pool/c2pool_refactored.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index 9423e9d39..d4052cb9b 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -44,9 +44,14 @@ #include #include #include -#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 From 716489f446775f58480c88c0bc02b26b715714d5 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 00:49:20 +0000 Subject: [PATCH 3/4] ltc: re-assert core READWRITE in base_p2p_messages.hpp (macro collision) btclibs/serialize.h re-#defines READWRITE to the legacy (s, ser_action) contract and was winning by include order over core/pack.hpp (formatter, stream), breaking every C2POOL_SERIALIZE_METHODS body in this header (use of undeclared identifier s / ser_action on macOS arm64, Linux, Analyze). Re-assert the core contract so the header is include-order independent; mirrors btc/coin/p2p_messages.hpp. No message semantics change. --- src/impl/bitcoin_family/coin/base_p2p_messages.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/impl/bitcoin_family/coin/base_p2p_messages.hpp b/src/impl/bitcoin_family/coin/base_p2p_messages.hpp index 5848e2bd8..c28f660df 100644 --- a/src/impl/bitcoin_family/coin/base_p2p_messages.hpp +++ b/src/impl/bitcoin_family/coin/base_p2p_messages.hpp @@ -19,6 +19,19 @@ #include #include +// READWRITE collision guard. +// The C2POOL_SERIALIZE_METHODS bodies below rely on core/pack.hpp's READWRITE +// contract (formatter.action(stream, ...)). btclibs/serialize.h, pulled +// transitively into some translation units, re-#defines READWRITE to the legacy +// (s, ser_action) contract and can win by include order, breaking every body here +// ("use of undeclared identifier 's' / 'ser_action'" on macOS arm64 / Linux). +// Re-assert the core contract so this header is include-order-independent; mirrors +// btc/coin/p2p_messages.hpp, whose include chain keeps the core macro active. +#ifdef READWRITE +# undef READWRITE +#endif +#define READWRITE(...) formatter.action(stream, __VA_ARGS__) + namespace bitcoin_family { namespace coin From ad05cc030f773bf91f1b32be41e7b1ac724a3f03 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 11 Jun 2026 00:59:14 +0000 Subject: [PATCH 4/4] ltc: de-scope set_coin_node wiring to PR-C so A compiles standalone c2pool_refactored.cpp wired the new web_server.set_coin_node(CoinNode*) seam, but that API only lands with the PR-C web_server ICoinNode retype. On master it does not exist, so the Linux x86_64 aggregate failed to compile A standalone (missing-member), independent of the macOS READWRITE fix. Revert this file to master (restore set_coin_rpc/set_embedded_node). The macOS arm64 fix (base_p2p_messages.hpp READWRITE re-assert) and the additive ltc::coin::CoinNode type stay in A; CoinNode depends only on the core seam foundation (node_iface.hpp + work_view.hpp) already on master, so it compiles standalone. Wiring CoinNode into web_server moves to PR-C. --- src/c2pool/c2pool_refactored.cpp | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index d4052cb9b..0c9f81d88 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -46,12 +46,6 @@ #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 @@ -1508,10 +1502,6 @@ 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; @@ -1981,14 +1971,10 @@ int main(int argc, char* argv[]) { // Wire live coin-daemon RPC so getblocktemplate/submitblock use real data if (!embedded_ltc) { - coin_iface = std::make_unique( - /*embedded*/nullptr, /*rpc*/node_rpc.get()); - web_server.set_coin_node(coin_iface.get()); + web_server.set_coin_rpc(node_rpc.get(), &coin_node); } else if (embedded_broadcaster && embedded_chain) { // Wire embedded node + header-sync callback (now that web_server is alive) - coin_iface = std::make_unique( - /*embedded*/embedded_node.get(), /*rpc*/node_rpc.get()); - web_server.set_coin_node(coin_iface.get()); + web_server.set_embedded_node(embedded_node.get()); // Wire block verification for embedded mode auto* mi = web_server.get_mining_interface(); @@ -7164,16 +7150,12 @@ 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_node(solo_coin_iface.get()); + solo_server.set_coin_rpc(solo_node_rpc.get(), &solo_coin_node); // Configure payout system for solo server solo_server.set_payout_manager(payout_manager.get());