Skip to content

Commit 438de7d

Browse files
authored
dgb(Phase-B): SSOT expected-time-to-block diagnostic + non-circular KAT (#390)
Lift the final expected-time-to-block arithmetic (etb_secs = average_attempts / real_pool_hs, with the uint64-overflow 1e18 sentinel) out of node.cpp diagnostics into a header-only SSOT, expected_time_to_block.hpp, mirroring the pool_efficiency.hpp lift. The bits->target->average_attempts conversion stays at the call site on the existing chain SSOTs; this captures only the final combine + sentinel. FENCED: dgb/ only, header-only (<cstdint>), consensus-neutral pure arithmetic. node.cpp NOT yet rewired (the byte-identity delegation that folds real-hashrate + get_stale_counts + this is the follow-on). KAT is non-circular (oracle expression recomputed independently vs the p2pool main.py status loop). 7/7 green. Registered in the dgb test CMakeLists and BOTH build.yml --target allowlist arms. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent e305835 commit 438de7d

4 files changed

Lines changed: 177 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
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 \
8585
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 \
8686
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
87-
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 v37_test \
87+
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 \
8888
-j$(nproc)
8989
9090
- name: Run tests
@@ -216,7 +216,7 @@ jobs:
216216
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 \
217217
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 \
218218
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
219-
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 test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
219+
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 \
220220
v37_test \
221221
-j$(nproc)
222222
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma once
2+
3+
// SSOT for the DGB EXPECTED-TIME-TO-BLOCK diagnostic — the final pure-arithmetic
4+
// step the status loop applies when reporting how long the pool is expected to
5+
// take to find a block:
6+
// etb_secs = average_attempts / real_pool_hs
7+
// where average_attempts is the block-target's average attempts (the low 64 bits
8+
// of chain::target_to_average_attempts(bits_to_target(block_bits))) and
9+
// real_pool_hs is the efficiency-adjusted pool hashrate from pool_efficiency.hpp.
10+
//
11+
// This is currently OPEN-CODED inline in node.cpp's diagnostics loop (the
12+
// `etb_secs = block_aps.GetLow64() / real_pool_hs` division plus the
13+
// near-overflow sentinel) feeding the "Pool: <hashrate> ... Expected time to
14+
// block: <duration>" line. A silent drift — a flipped numerator/denominator or
15+
// dropping the overflow sentinel — would misreport a node's expected time to
16+
// block with NO compile error, which is operator-facing behavior the V36
17+
// master-compat invariant pins to the p2pool reference. Lifting the arithmetic
18+
// to a single header-only SSOT lets a KAT pin it exactly against the oracle.
19+
//
20+
// Oracle: p2pool-dgb-scrypt main.py status loop:
21+
// 'Expected time to block: %s' % format_dt(
22+
// 2**256 / current_work.value['bits'].target / real_att_s)
23+
// i.e. average_attempts(target) / real_att_s, where the c2pool path computes
24+
// average_attempts via the existing chain::target_to_average_attempts SSOT and
25+
// guards the case where that count overflows uint64 while the target is
26+
// non-null (reporting a 1e18 sentinel rather than a meaningless small value).
27+
//
28+
// Per-coin isolation: dgb/ only. Header-only, additive; this slice does NOT yet
29+
// rewire node.cpp (that is the byte-identity-fenced delegation follow-on that
30+
// also folds in get_stale_counts + pool_efficiency). It pins the math as a free
31+
// function so the KAT exercises it with no NodeImpl / ShareTracker standup. The
32+
// bits->target->average_attempts conversion stays at the call site on the
33+
// already-verified chain SSOTs; this function captures only the final combine +
34+
// sentinel. Consensus-neutral: pure arithmetic, no value semantics changed.
35+
36+
#include <cstdint>
37+
38+
namespace dgb {
39+
40+
// Expected seconds for the pool to find a block.
41+
// etb_secs = average_attempts / real_pool_hs
42+
//
43+
// average_attempts : static_cast<double>(block_aps.GetLow64()), the low 64
44+
// bits of the block target's average-attempts count.
45+
// real_pool_hs : efficiency-adjusted pool hashrate (hashes/s).
46+
// average_attempts_overflowed : block_aps.IsNull() — the average-attempts count
47+
// does not fit in uint64 (its low64 is meaningless).
48+
// block_target_nonzero : !block_target.IsNull() — the block target is set.
49+
//
50+
// With no measured hashrate (real_pool_hs <= 0) there is nothing to divide by, so
51+
// the caller never enters this path and etb stays 0; the same guard here keeps
52+
// the function standalone-safe (never divides by zero). When the average-attempts
53+
// count overflows uint64 but the target is genuinely non-null, the low64 division
54+
// would yield a bogus tiny number, so a 1e18 sentinel is reported instead —
55+
// matching the inline guard in node.cpp's diagnostics loop.
56+
inline double compute_expected_time_to_block(double average_attempts,
57+
double real_pool_hs,
58+
bool average_attempts_overflowed,
59+
bool block_target_nonzero) {
60+
if (real_pool_hs <= 0.0)
61+
return 0.0;
62+
double etb_secs = average_attempts / real_pool_hs;
63+
if (average_attempts_overflowed && block_target_nonzero)
64+
etb_secs = 1e18;
65+
return etb_secs;
66+
}
67+
68+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,18 @@ if (BUILD_TESTING AND GTest_FOUND)
660660
dgb_coin pool sharechain)
661661
gtest_add_tests(dgb_think_p6_desired_cutoff_test "" AUTO)
662662

663+
# dgb_expected_time_to_block_test: FENCED, additive KAT pinning the
664+
# EXPECTED-TIME-TO-BLOCK diagnostic arithmetic in expected_time_to_block.hpp
665+
# vs the p2pool main.py status-loop oracle (average_attempts(target) /
666+
# real_att_s, with the uint64-overflow 1e18 sentinel). Pure header
667+
# (<cstdint> only) -> links only GTest. node.cpp NOT yet rewired (delegation
668+
# is the byte-identity follow-on). MUST also be in the build.yml --target
669+
# allowlist (#143 NOT_BUILT trap).
670+
add_executable(dgb_expected_time_to_block_test expected_time_to_block_test.cpp)
671+
target_link_libraries(dgb_expected_time_to_block_test PRIVATE
672+
GTest::gtest_main GTest::gtest)
673+
gtest_add_tests(dgb_expected_time_to_block_test "" AUTO)
674+
663675
# dgb_think_p4_head_keys_test: FENCED, additive KAT pinning the think()
664676
# Phase-4 head-score KEY construction in think_p4_head_keys.hpp vs the
665677
# p2pool data.py think() oracle (selection key (adjusted_work,-reason,-ts)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// dgb::compute_expected_time_to_block — expected-time-to-block KAT.
2+
//
3+
// FENCED conformance test (no production code touched). Pins the
4+
// EXPECTED-TIME-TO-BLOCK diagnostic arithmetic lifted into
5+
// expected_time_to_block.hpp against the p2pool-dgb-scrypt oracle main.py status
6+
// loop:
7+
// 'Expected time to block: %s' % format_dt(
8+
// 2**256 / current_work.value['bits'].target / real_att_s)
9+
// == average_attempts(target) / real_att_s, with the uint64-overflow sentinel.
10+
//
11+
// The expected values here are HAND-DERIVED from the oracle formula (not produced
12+
// by calling the helper under test), so the test is non-circular: it
13+
// independently recomputes the oracle expression and asserts the helper matches.
14+
//
15+
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
16+
// build.yml --target allowlist, or it becomes a #143-style NOT_BUILT sentinel
17+
// that reds master.
18+
19+
#include <impl/dgb/expected_time_to_block.hpp>
20+
21+
#include <gtest/gtest.h>
22+
23+
namespace {
24+
25+
// ---- core division ------------------------------------------------------
26+
27+
TEST(DgbExpectedTimeToBlock, EtbIsAverageAttemptsOverRealHashrate) {
28+
// Oracle: average_attempts / real_pool_hs. Recompute independently.
29+
const double avg = 1.0e12;
30+
const double real_hs = 1.0e6;
31+
const double oracle = avg / real_hs; // 1e6 seconds (hand-derived)
32+
EXPECT_DOUBLE_EQ(
33+
dgb::compute_expected_time_to_block(avg, real_hs, false, true), oracle);
34+
EXPECT_DOUBLE_EQ(
35+
dgb::compute_expected_time_to_block(avg, real_hs, false, true), 1.0e6);
36+
}
37+
38+
TEST(DgbExpectedTimeToBlock, FasterPoolHasLowerEtb) {
39+
const double avg = 1.0e12;
40+
// 10x the hashrate -> 1/10th the expected time.
41+
EXPECT_DOUBLE_EQ(
42+
dgb::compute_expected_time_to_block(avg, 1.0e7, false, true), 1.0e5);
43+
}
44+
45+
// ---- no-measured-hashrate guard ----------------------------------------
46+
47+
TEST(DgbExpectedTimeToBlock, NoHashrateIsZeroNotDivByZero) {
48+
// real_pool_hs <= 0 -> nothing to divide by -> 0.0, never inf/NaN.
49+
EXPECT_DOUBLE_EQ(
50+
dgb::compute_expected_time_to_block(1.0e12, 0.0, false, true), 0.0);
51+
EXPECT_DOUBLE_EQ(
52+
dgb::compute_expected_time_to_block(1.0e12, -5.0, false, true), 0.0);
53+
}
54+
55+
// ---- overflow sentinel --------------------------------------------------
56+
57+
TEST(DgbExpectedTimeToBlock, OverflowWithNonNullTargetReportsSentinel) {
58+
// average-attempts count does not fit in uint64 AND target is non-null:
59+
// the low64 division is meaningless -> 1e18 sentinel.
60+
EXPECT_DOUBLE_EQ(
61+
dgb::compute_expected_time_to_block(123.0, 1.0e6, true, true), 1.0e18);
62+
}
63+
64+
TEST(DgbExpectedTimeToBlock, OverflowWithNullTargetDoesNotTriggerSentinel) {
65+
// block_target_nonzero == false -> sentinel suppressed, plain division stands.
66+
const double avg = 2.0e9;
67+
const double real_hs = 1.0e3;
68+
EXPECT_DOUBLE_EQ(
69+
dgb::compute_expected_time_to_block(avg, real_hs, true, false),
70+
avg / real_hs); // 2e6, hand-derived
71+
}
72+
73+
TEST(DgbExpectedTimeToBlock, NoOverflowIgnoresSentinelRegardlessOfTarget) {
74+
// average_attempts_overflowed == false -> sentinel never applies.
75+
const double avg = 4.0e9;
76+
const double real_hs = 2.0e3;
77+
EXPECT_DOUBLE_EQ(
78+
dgb::compute_expected_time_to_block(avg, real_hs, false, true),
79+
avg / real_hs); // 2e6
80+
}
81+
82+
// ---- end-to-end chain mirroring node.cpp diagnostics --------------------
83+
84+
TEST(DgbExpectedTimeToBlock, ChainRealHashrateThenEtbMatchesOracle) {
85+
// node.cpp path: raw aps -> real_pool_hs (1 - stale_prop scaling) -> etb.
86+
const double pool_hs = 1.0e6;
87+
const double stale_prop = 0.05;
88+
const double real_hs = pool_hs / (1.0 - stale_prop); // pool_efficiency oracle
89+
const double avg = 1.0e12;
90+
const double oracle = avg / real_hs; // etb oracle
91+
EXPECT_DOUBLE_EQ(
92+
dgb::compute_expected_time_to_block(avg, real_hs, false, true), oracle);
93+
}
94+
95+
} // namespace

0 commit comments

Comments
 (0)