Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/c2pool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions src/c2pool/c2pool_refactored.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
#include <impl/ltc/coin/block.hpp>
#include <impl/ltc/node.hpp>
#include <impl/ltc/messages.hpp>
// 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 <impl/ltc/coin/coin_node.hpp>
#include <impl/ltc/config.hpp>

// Chain seed discovery
Expand Down Expand Up @@ -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<ltc::coin::NodeRPC> 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<ltc::coin::CoinNode> coin_iface;

// Phase 4 embedded objects (alive for the duration of integrated mode)
std::unique_ptr<ltc::coin::HeaderChain> embedded_chain;
Expand Down Expand Up @@ -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<ltc::coin::CoinNode>(
/*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<ltc::coin::CoinNode>(
/*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();
Expand Down Expand Up @@ -7150,12 +7164,16 @@ int main(int argc, char* argv[]) {
solo_node_rpc->connect(NetService(rpc_host, static_cast<uint16_t>(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<ltc::coin::CoinNode>(
/*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());
Expand Down
68 changes: 26 additions & 42 deletions src/core/web_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <impl/ltc/coin/rpc.hpp>
#include <impl/ltc/coin/node_interface.hpp>
#include <core/coin/node_iface.hpp>
// Phase 4: embedded coin node interface
#include <impl/ltc/coin/template_builder.hpp>
#include <impl/ltc/share_messages.hpp>
Expand Down Expand Up @@ -1139,17 +1140,10 @@ std::vector<unsigned char> 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<void(const std::string&, int)> fn)
Expand Down Expand Up @@ -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
Expand All @@ -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<WorkData> 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<std::string> tx_hashes_hex;
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -4420,10 +4411,10 @@ nlohmann::json MiningInterface::rest_local_stats()
auto now_s = std::chrono::duration_cast<std::chrono::seconds>(
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!"));
}

Expand All @@ -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." : ""));
}
}
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<net::steady_timer>(ioc_);
timer->expires_after(std::chrono::milliseconds(500));
timer->async_wait([this, timer](beast::error_code ec) {
Expand Down Expand Up @@ -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<uint256()> fn)
Expand Down
30 changes: 10 additions & 20 deletions src/core/web_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<void(const std::string&, int)> fn);
// Forward P2P block relay callback to the underlying MiningInterface
Expand Down
11 changes: 7 additions & 4 deletions test/test_phase4_embedded.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -31,6 +31,7 @@
#include <impl/ltc/coin/mempool.hpp>
#include <impl/ltc/coin/transaction.hpp>
#include <core/web_server.hpp>
#include <impl/ltc/coin/coin_node.hpp>
#include <core/address_validator.hpp>

#include <string>
Expand Down Expand Up @@ -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());

Expand Down
4 changes: 3 additions & 1 deletion test/test_phase4_live.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <impl/ltc/coin/header_chain.hpp>
#include <impl/ltc/coin/mempool.hpp>
#include <impl/ltc/coin/template_builder.hpp>
#include <impl/ltc/coin/coin_node.hpp>
#include <c2pool/merged/coin_broadcaster.hpp>
#include <core/web_server.hpp>
#include <core/address_validator.hpp>
Expand Down Expand Up @@ -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());

Expand Down
Loading