diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc1cd08c9..71231993d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ - dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test v37_test \ + dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_pool_efficiency_test v37_test \ -j$(nproc) - name: Run tests @@ -216,7 +216,7 @@ jobs: dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ - dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ + dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_pool_efficiency_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ -j$(nproc) diff --git a/src/impl/dgb/pool_efficiency.hpp b/src/impl/dgb/pool_efficiency.hpp new file mode 100644 index 000000000..bed04828c --- /dev/null +++ b/src/impl/dgb/pool_efficiency.hpp @@ -0,0 +1,60 @@ +#pragma once + +// SSOT for the DGB pool EFFICIENCY / REAL-HASHRATE diagnostics — the two pure +// arithmetic steps the status loop applies when reporting pool performance: +// (a) the stale proportion over a recent share window, and +// (b) the efficiency-adjusted "real" pool hashrate that scales the raw +// attempts/s up to compensate for stale (orphan + DOA) work. +// +// These are currently OPEN-CODED inline in node.cpp's diagnostics loop (the +// `stale_prop = (orphan + doa) / total_recent` reduction and the +// `real_pool_hs = pool_hs / (1 - stale_prop)` guarded division feeding the +// "Pool: Expected time to block:" line). A silent drift — a flipped +// guard, a wrong denominator, or dropping the div-by-~0 floor — would misreport +// a node's effective hashrate and expected-time-to-block with NO compile error, +// which is operator-facing behavior the V36 master-compat invariant pins to the +// p2pool reference. Lifting the arithmetic to a single header-only SSOT lets a +// KAT pin it exactly against the oracle. +// +// Oracle: p2pool-dgb-scrypt main.py status loop: +// real_att_s = p2pool_data.get_pool_attempts_per_second(...) / (1 - stale_prop) +// with stale_prop derived from the recent orphan/DOA counts over the window, +// and the (1 - stale_prop) division guarded against the empty / ~all-stale +// case so it never divides by ~zero. +// +// Per-coin isolation: dgb/ only. Header-only, additive; this slice does NOT yet +// rewire node.cpp (that is the byte-identity-fenced delegation follow-on) — it +// pins the math as free functions so the KAT exercises them with no NodeImpl / +// ShareTracker standup. Consensus-neutral: pure arithmetic, no value semantics +// changed. + +#include + +namespace dgb { + +// Stale proportion over a recent share window. +// stale_prop = (orphan_count + doa_count) / total_recent +// An empty window (total_recent == 0) reports 0.0 rather than NaN, matching the +// inline reduction in node.cpp's diagnostics loop. +inline double compute_stale_prop(uint64_t orphan_count, uint64_t doa_count, + uint64_t total_recent) { + if (total_recent == 0) + return 0.0; + return static_cast(orphan_count + doa_count) / + static_cast(total_recent); +} + +// Efficiency-adjusted "real" pool hashrate. +// real = pool_hs / (1 - stale_prop) when stale_prop < 0.999 AND pool_hs > 0 +// real = pool_hs otherwise +// The guard mirrors the oracle: when stale_prop approaches 1 (≥ 0.999) the +// (1 - stale_prop) denominator approaches zero and the adjustment is suppressed +// to avoid a blow-up; with no measured hashrate (pool_hs <= 0) there is nothing +// to scale. +inline double compute_real_pool_hashrate(double pool_hs, double stale_prop) { + if (stale_prop < 0.999 && pool_hs > 0) + return pool_hs / (1.0 - stale_prop); + return pool_hs; +} + +} // namespace dgb diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index e2b2c3cd7..c02b310b7 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -671,6 +671,17 @@ if (BUILD_TESTING AND GTest_FOUND) target_link_libraries(dgb_expected_time_to_block_test PRIVATE GTest::gtest_main GTest::gtest) gtest_add_tests(dgb_expected_time_to_block_test "" AUTO) + # dgb_pool_efficiency_test: FENCED, additive KAT pinning the pool + # EFFICIENCY / REAL-HASHRATE diagnostics arithmetic in pool_efficiency.hpp vs + # the p2pool main.py status-loop oracle (stale_prop = (orphan+doa)/total; + # real_att_s = pool_hs / (1 - stale_prop) with the near-all-stale guard). + # Pure header ( only) -> links only GTest. node.cpp NOT yet rewired + # (delegation is the byte-identity follow-on). MUST also be in the build.yml + # --target allowlist (#143 NOT_BUILT trap). + add_executable(dgb_pool_efficiency_test pool_efficiency_test.cpp) + target_link_libraries(dgb_pool_efficiency_test PRIVATE + GTest::gtest_main GTest::gtest) + gtest_add_tests(dgb_pool_efficiency_test "" AUTO) # dgb_think_p4_head_keys_test: FENCED, additive KAT pinning the think() # Phase-4 head-score KEY construction in think_p4_head_keys.hpp vs the diff --git a/src/impl/dgb/test/pool_efficiency_test.cpp b/src/impl/dgb/test/pool_efficiency_test.cpp new file mode 100644 index 000000000..f9fb6bb74 --- /dev/null +++ b/src/impl/dgb/test/pool_efficiency_test.cpp @@ -0,0 +1,88 @@ +// dgb::compute_stale_prop / compute_real_pool_hashrate — pool efficiency KAT. +// +// FENCED conformance test (no production code touched). Pins the pool +// EFFICIENCY / REAL-HASHRATE diagnostics arithmetic lifted into +// pool_efficiency.hpp against the p2pool-dgb-scrypt oracle main.py status loop: +// real_att_s = get_pool_attempts_per_second(...) / (1 - stale_prop) +// with stale_prop = (orphan + doa) / total_recent over the recent share window. +// +// The expected values here are HAND-DERIVED from the oracle formulas (not +// produced by calling the helper under test), so the test is non-circular: it +// independently recomputes the oracle expression and asserts the helper matches. +// +// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the +// build.yml --target allowlist, or it becomes a #143-style NOT_BUILT sentinel +// that reds master. + +#include + +#include + +namespace { + +// ---- compute_stale_prop ------------------------------------------------- + +TEST(DgbPoolEfficiency, StalePropEmptyWindowIsZero) { + // total_recent == 0 -> 0.0, never NaN. + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(0, 0, 0), 0.0); + // Even with (impossibly) non-zero counts, an empty window short-circuits. + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(7, 3, 0), 0.0); +} + +TEST(DgbPoolEfficiency, StalePropNoStalesIsZero) { + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(0, 0, 100), 0.0); +} + +TEST(DgbPoolEfficiency, StalePropOrphanPlusDoaOverTotal) { + // 3 orphan + 2 doa over 100 recent -> 5/100 = 0.05 (hand-derived). + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(3, 2, 100), 0.05); + // 1 orphan + 0 doa over 4 -> 0.25. + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(1, 0, 4), 0.25); + // All stale: 50 over 50 -> 1.0. + EXPECT_DOUBLE_EQ(dgb::compute_stale_prop(30, 20, 50), 1.0); +} + +// ---- compute_real_pool_hashrate ---------------------------------------- + +TEST(DgbPoolEfficiency, RealHashrateZeroStaleIsIdentity) { + // stale_prop == 0 -> divisor is 1 -> unchanged. + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(1000.0, 0.0), 1000.0); +} + +TEST(DgbPoolEfficiency, RealHashrateScalesByInverseEfficiency) { + // Oracle: pool_hs / (1 - stale_prop). Recompute the oracle expression + // independently (non-circular) and assert the helper equals it. + const double pool_hs = 1000.0; + const double stale_prop = 0.05; + const double oracle = pool_hs / (1.0 - stale_prop); // ~1052.6315789... + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(pool_hs, stale_prop), oracle); + // Sanity anchor on the magnitude (hand: 1000/0.95 > 1052, < 1053). + EXPECT_GT(dgb::compute_real_pool_hashrate(pool_hs, stale_prop), 1052.0); + EXPECT_LT(dgb::compute_real_pool_hashrate(pool_hs, stale_prop), 1053.0); +} + +TEST(DgbPoolEfficiency, RealHashrateGuardSuppressesNearAllStale) { + // stale_prop == 0.999 is NOT < 0.999 -> guard holds -> identity, no blow-up. + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(1000.0, 0.999), 1000.0); + // Above the threshold likewise suppressed. + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(1000.0, 1.0), 1000.0); +} + +TEST(DgbPoolEfficiency, RealHashrateNoMeasuredWorkIsIdentity) { + // pool_hs <= 0 -> nothing to scale. + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(0.0, 0.05), 0.0); + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(-5.0, 0.05), -5.0); +} + +// ---- end-to-end chain mirroring node.cpp diagnostics -------------------- + +TEST(DgbPoolEfficiency, ChainStalePropThenRealHashrateMatchesOracle) { + // node.cpp: stale_prop from counts, then real_pool_hs from raw aps. + const double sp = dgb::compute_stale_prop(3, 2, 100); // 0.05 + EXPECT_DOUBLE_EQ(sp, 0.05); + const double pool_hs = 2000.0; + const double oracle = pool_hs / (1.0 - sp); // 2000/0.95 + EXPECT_DOUBLE_EQ(dgb::compute_real_pool_hashrate(pool_hs, sp), oracle); +} + +} // namespace