diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 494093623..bfe4ded1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,7 +102,7 @@ jobs: core_test sharechain_test share_test btc_share_test \ test_threading test_weights \ test_header_chain test_mempool test_template_builder \ - test_doge_chain test_compact_blocks test_dash_x11_kat test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate \ + test_doge_chain test_compact_blocks test_dash_x11_kat test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source \ test_multiaddress_pplns test_pplns_stress \ test_hash_link test_decay_pplns \ test_pplns_consensus \ @@ -252,7 +252,7 @@ jobs: test_threading test_weights \ test_header_chain test_mempool test_template_builder \ test_doge_chain test_compact_blocks test_dash_x11_kat \ - test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate \ + test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source \ test_hash_link test_decay_pplns \ test_pplns_consensus \ test_v36_script_sorting test_v36_cross_impl_refhash \ diff --git a/src/c2pool/main_dash.cpp b/src/c2pool/main_dash.cpp index f2a201407..b26980396 100644 --- a/src/c2pool/main_dash.cpp +++ b/src/c2pool/main_dash.cpp @@ -50,6 +50,7 @@ #include // NetService (dashd RPC endpoint) #include // dash::coin::NodeRPC — external-dashd submitblock arm (slice 3) +#include // dash::coin::select_dash_work -- embedded-gbt live-wire + dashd fallback (S8 capstone) #include // dash.conf creds resolution (rpcpassword off argv) #include #include // dash::Node — sharechain pool-node (NodeBridge) @@ -457,7 +458,25 @@ int run_mine_block(bool testnet, const std::string& rpc_endpoint, // 1) Pull the template. std::cout << "[mine] fetching block template (getblocktemplate)...\n"; - dash::coin::DashWorkData work = rpc.getwork(); + // Work source (S8 embedded_gbt live-wire capstone): PREFER the locally + // assembled embedded template via build_embedded_workdata, fall back to + // dashd getblocktemplate. The dashd arm is RETAINED as fallback + the + // [GBT-XCHECK] cross-check -- never removed. + // + // NOTE: NodeImpl does not yet hold the embedded coin-state (masternode + // list + mempool + header tip) that build_embedded_workdata consumes, so + // emb.has_state stays false here and the selector routes to the dashd + // fallback today. Populating that in-process state is the flagged next + // sub-slice; once it lands, set emb.has_state=true and the embedded arm + // goes live with zero change to this call site. + dash::coin::EmbeddedWorkInputs emb; // has_state=false until node-held coin-state lands + dash::coin::WorkSelection sel = + dash::coin::select_dash_work(emb, [&]{ return rpc.getwork(); }); + dash::coin::DashWorkData work = std::move(sel.work); + std::cout << "[mine] work source: " + << (sel.source == dash::coin::WorkSource::Embedded + ? "EMBEDDED (build_embedded_workdata)" + : "dashd getblocktemplate (fallback)") << "\n"; std::cout << "[mine] template: height=" << work.m_height << " bits=0x" << std::hex << work.m_bits << std::dec << " prev=" << work.m_previous_block.GetHex().substr(0, 16) << "..." diff --git a/src/impl/dash/coin/work_source.hpp b/src/impl/dash/coin/work_source.hpp new file mode 100644 index 000000000..8a91e9c8c --- /dev/null +++ b/src/impl/dash/coin/work_source.hpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +#pragma once + +/// Phase C-TEMPLATE step 5 (capstone): embedded-vs-dashd work-source selector. +/// +/// build_embedded_workdata() (embedded_gbt.hpp) is source-complete and pinned +/// by test_dash_embedded_gbt (oracle: frstrtr/p2pool-dash getwork(), +/// older-than-v35 semantics). What was missing is a LIVE consumer: the running +/// node's get_work path must PREFER the locally-assembled embedded template and +/// fall back to dashd getblocktemplate only when the embedded coin-state bundle +/// is unavailable. +/// +/// select_dash_work() is that branch point. When the EmbeddedWorkInputs bundle +/// is viable (masternode list + mempool + header-tip params all present) it +/// assembles the template LOCALLY via build_embedded_workdata() — the +/// oracle-parity path. Otherwise it invokes the supplied dashd fallback. +/// +/// The external-daemon (dashd RPC) arm is NEVER removed: it is BOTH the fallback +/// AND the [GBT-XCHECK] cross-check. viable()==false must always route there. + +#include // build_embedded_workdata +#include // MnStateMachine +#include // Mempool +#include // DashWorkData + +#include + +#include +#include +#include +#include + +namespace dash { +namespace coin { + +/// The inputs build_embedded_workdata() consumes, plus a validity flag. For the +/// embedded path to be taken, has_state must be true AND both pointers non-null +/// (viable()). Until NodeImpl carries embedded coin-state in-process, callers +/// leave has_state=false and the selector routes to the dashd fallback. +struct EmbeddedWorkInputs { + bool has_state{false}; + uint32_t prev_height{0}; + uint256 prev_hash; + const MnStateMachine* mnstates{nullptr}; + const Mempool* mempool{nullptr}; + uint32_t bits_for_next{0}; + uint32_t mtp_at_tip{0}; + uint8_t address_version{0}; + uint8_t address_p2sh_version{0}; + // Optional seams. 0 => use build_embedded_workdata's own SAFE-ADDITIVE + // defaults (std::time(nullptr) / 0x20000000 BIP9 baseline). + uint32_t curtime{0}; + uint32_t version{0}; + + bool viable() const { + return has_state && mnstates != nullptr && mempool != nullptr; + } +}; + +enum class WorkSource { Embedded, DashdFallback }; + +struct WorkSelection { + DashWorkData work; + WorkSource source{WorkSource::DashdFallback}; +}; + +/// Injectable core — the routing decision, testable without a live daemon or a +/// populated MN/mempool harness. Production binds embedded_builder to the real +/// build_embedded_workdata() closure (see the 2-arg overload below). +inline WorkSelection select_dash_work( + const EmbeddedWorkInputs& emb, + const std::function& embedded_builder, + const std::function& dashd_fallback) +{ + if (emb.viable()) + return WorkSelection{embedded_builder(), WorkSource::Embedded}; + return WorkSelection{dashd_fallback(), WorkSource::DashdFallback}; +} + +/// Production entry point: builds the embedded template from `emb` when viable, +/// else calls `dashd_fallback` (the retained external-daemon getblocktemplate +/// arm). dashd_fallback is REQUIRED — it is the always-reachable safety path. +inline WorkSelection select_dash_work( + const EmbeddedWorkInputs& emb, + const std::function& dashd_fallback) +{ + return select_dash_work( + emb, + [&emb]() { + return build_embedded_workdata( + emb.prev_height, emb.prev_hash, *emb.mnstates, *emb.mempool, + emb.bits_for_next, emb.mtp_at_tip, + emb.address_version, emb.address_p2sh_version, + emb.curtime ? emb.curtime + : static_cast(std::time(nullptr)), + emb.version ? emb.version : 0x20000000u); + }, + dashd_fallback); +} + +} // namespace coin +} // namespace dash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d72432ed4..052e6bf4e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -504,6 +504,23 @@ if (BUILD_TESTING AND GTest_FOUND) target_link_libraries(test_dash_embedded_gbt PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39) gtest_add_tests(test_dash_embedded_gbt "" AUTO) + # DASH embedded-vs-dashd work-source selector (S8 embedded_gbt live-wire + # capstone): select_dash_work() prefers the locally-assembled embedded + # template (build_embedded_workdata) and falls back to dashd + # getblocktemplate when the coin-state bundle is not viable. Routing + + # retained-fallback KAT; harness-free (embedded builder injected as a stub, + # oracle-parity already pinned by test_dash_embedded_gbt). Same link set as + # the embedded_gbt capstone (work_source.hpp includes embedded_gbt.hpp). + add_executable(test_dash_work_source test_dash_work_source.cpp) + target_link_libraries(test_dash_work_source PRIVATE + GTest::gtest_main GTest::gtest + dash_x11 core + nlohmann_json::nlohmann_json + ${Boost_LIBRARIES} + ) + target_link_libraries(test_dash_work_source PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39) + gtest_add_tests(test_dash_work_source "" AUTO) + # DASH CSimplifiedMNListDiff (mnlistdiff wire, S8.1 leaf): vendored diff # round-trip + dashcore field-layout pin + apply_diff semantics. Header-only # over the SimplifiedMNList leaf + coin/transaction; no ltc/pool SCC dep. diff --git a/test/test_dash_work_source.cpp b/test/test_dash_work_source.cpp new file mode 100644 index 000000000..a4480e8a7 --- /dev/null +++ b/test/test_dash_work_source.cpp @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +/// +/// KAT for dash::coin::select_dash_work — the embedded-vs-dashd work-source +/// selector (S8 embedded_gbt live-wire capstone). Proves the routing contract +/// and the RETAINED dashd fallback, without a live daemon or a populated +/// MN/mempool harness (the embedded builder is injected as a stub; its +/// oracle-parity output is already pinned by test_dash_embedded_gbt). +/// +/// Contract under test: +/// 1. viable() bundle -> WorkSource::Embedded, embedded builder run, +/// fallback NEVER touched. +/// 2. has_state=false -> WorkSource::DashdFallback, fallback run. +/// 3. viable but null mnstates -> fallback (defensive null-guard). +/// 4. viable but null mempool -> fallback (defensive null-guard). + +#include + +#include + +using dash::coin::EmbeddedWorkInputs; +using dash::coin::WorkSource; +using dash::coin::WorkSelection; +using dash::coin::select_dash_work; +using dash::coin::DashWorkData; +using dash::coin::MnStateMachine; +using dash::coin::Mempool; + +namespace { + +// Distinguishable sentinels so we can prove WHICH closure produced the result. +constexpr uint32_t EMB_SENTINEL_HEIGHT = 0xE3BEDDEDu & 0xffffffu; // "embedded" +constexpr uint32_t DASHD_SENTINEL_HEIGHT = 999'999u; + +DashWorkData embedded_stub(bool& ran) { + ran = true; + DashWorkData w; + w.m_height = EMB_SENTINEL_HEIGHT; + return w; +} + +DashWorkData dashd_stub(bool& ran) { + ran = true; + DashWorkData w; + w.m_height = DASHD_SENTINEL_HEIGHT; + return w; +} + +} // namespace + +// 1) Viable bundle routes to the EMBEDDED builder; fallback is not invoked. +TEST(DashWorkSource, ViableRoutesEmbedded) +{ + MnStateMachine mn; + Mempool mp; + EmbeddedWorkInputs emb; + emb.has_state = true; + emb.mnstates = &mn; + emb.mempool = ∓ + ASSERT_TRUE(emb.viable()); + + bool emb_ran = false, fb_ran = false; + WorkSelection sel = select_dash_work( + emb, + [&] { return embedded_stub(emb_ran); }, + [&] { return dashd_stub(fb_ran); }); + + EXPECT_EQ(sel.source, WorkSource::Embedded); + EXPECT_TRUE(emb_ran); + EXPECT_FALSE(fb_ran); + EXPECT_EQ(sel.work.m_height, EMB_SENTINEL_HEIGHT); +} + +// 2) No embedded state -> the RETAINED dashd getblocktemplate fallback runs. +TEST(DashWorkSource, NoStateRoutesDashdFallback) +{ + EmbeddedWorkInputs emb; // has_state defaults false + ASSERT_FALSE(emb.viable()); + + bool emb_ran = false, fb_ran = false; + WorkSelection sel = select_dash_work( + emb, + [&] { return embedded_stub(emb_ran); }, + [&] { return dashd_stub(fb_ran); }); + + EXPECT_EQ(sel.source, WorkSource::DashdFallback); + EXPECT_FALSE(emb_ran); + EXPECT_TRUE(fb_ran); + EXPECT_EQ(sel.work.m_height, DASHD_SENTINEL_HEIGHT); +} + +// 3) has_state true but mnstates null -> not viable -> fallback (null-guard). +TEST(DashWorkSource, NullMnStatesRoutesFallback) +{ + Mempool mp; + EmbeddedWorkInputs emb; + emb.has_state = true; + emb.mnstates = nullptr; + emb.mempool = ∓ + EXPECT_FALSE(emb.viable()); + + bool emb_ran = false, fb_ran = false; + WorkSelection sel = select_dash_work( + emb, + [&] { return embedded_stub(emb_ran); }, + [&] { return dashd_stub(fb_ran); }); + + EXPECT_EQ(sel.source, WorkSource::DashdFallback); + EXPECT_FALSE(emb_ran); + EXPECT_TRUE(fb_ran); +} + +// 4) has_state true but mempool null -> not viable -> fallback (null-guard). +TEST(DashWorkSource, NullMempoolRoutesFallback) +{ + MnStateMachine mn; + EmbeddedWorkInputs emb; + emb.has_state = true; + emb.mnstates = &mn; + emb.mempool = nullptr; + EXPECT_FALSE(emb.viable()); + + bool emb_ran = false, fb_ran = false; + WorkSelection sel = select_dash_work( + emb, + [&] { return embedded_stub(emb_ran); }, + [&] { return dashd_stub(fb_ran); }); + + EXPECT_EQ(sel.source, WorkSource::DashdFallback); + EXPECT_FALSE(emb_ran); + EXPECT_TRUE(fb_ran); +}