Skip to content

Commit 7748a3b

Browse files
committed
dgb(phase-b): pool-attempts-per-second SSOT + conformance KAT
Lift the pure arithmetic core of ShareTracker::get_pool_attempts_per_second into header-only coin/pool_attempts_per_second.hpp: attempts_per_second = attempts / max(1, near_ts - far_ts) with the dist>=2 + near-in-chain + far-resolved guards and the integer divide. Pinned non-circularly against the p2pool-dgb-scrypt data.py oracle get_pool_attempts_per_second (attempts // time, time<=0 -> 1, integer path). FENCED, dgb-tree-local, additive. share_tracker.hpp NOT rewired -- the byte-identity delegation is the follow-on; the lifted body is a verbatim copy of the inline (same int32_t span, same uint288 divide) so that follow-on is provably value-identical. The chain walk (get_nth_parent_hash / get_delta) stays in ShareTracker. Test registered in the dir CMakeLists allowlist and both build.yml --target arms (#143 NOT_BUILT trap). 10/10 green; consensus-neutral pure arithmetic.
1 parent 199ae0c commit 7748a3b

4 files changed

Lines changed: 198 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 dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_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 dgb_expected_time_to_block_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_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 dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_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 dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_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 dgb_expected_time_to_block_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_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 dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
220220
v37_test \
221221
-j$(nproc)
222222
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma once
2+
3+
// SSOT for the DGB POOL-ATTEMPTS-PER-SECOND estimator -- the pure arithmetic
4+
// core of ShareTracker::get_pool_attempts_per_second(): given the resolved
5+
// near/far endpoints of a sharechain window, compute the pool hashrate estimate
6+
//
7+
// attempts_per_second = attempts / max(1, near_ts - far_ts)
8+
//
9+
// where attempts is the cumulative-work delta over the window (work, or min_work
10+
// when the min-work variant is requested) and the time span is clamped to a
11+
// strictly-positive denominator. This figure feeds the operator-facing "Pool:
12+
// <hashrate>" status line AND the PPLNS / expected-time-to-block windows, so a
13+
// silent drift (flipped sign on the span, dropping the <=0 clamp, a non-integer
14+
// divide) would misreport pool hashrate with NO compile error -- operator-facing
15+
// behavior the V36 master-compat invariant pins to the p2pool reference.
16+
//
17+
// Oracle: p2pool-dgb-scrypt data.py get_pool_attempts_per_second(tracker,
18+
// previous_share_hash, dist, min_work=False, integer=False):
19+
// assert dist >= 2
20+
// near = tracker.items[previous_share_hash]
21+
// far = tracker.items[tracker.get_nth_parent_hash(previous_share_hash, dist-1)]
22+
// attempts = tracker.get_delta(near.hash, far.hash).work # or .min_work
23+
// time = near.timestamp - far.timestamp
24+
// if time <= 0: time = 1
25+
// return attempts//time # the integer=True path c2pool always takes
26+
//
27+
// The chain-walk halves of the oracle -- locating `far` via get_nth_parent_hash
28+
// and summing the work delta via get_delta -- stay inside ShareTracker (they need
29+
// the skip-list / TrackerView). This header captures ONLY the endpoint-resolution
30+
// guards and the final clamp+divide as a free function over already-resolved
31+
// inputs, so a KAT can pin the arithmetic with no NodeImpl / ShareTracker standup.
32+
//
33+
// Per-coin isolation: dgb/ only. Header-only, additive. This slice does NOT yet
34+
// rewire share_tracker.hpp -- that is the byte-identity delegation follow-on.
35+
// The lifted body is a verbatim copy of the inline arithmetic (same int32_t span
36+
// type, same uint288 divide), so the follow-on is provably value-identical.
37+
// Consensus-neutral: pure arithmetic, no value semantics changed.
38+
39+
#include <cstdint>
40+
41+
#include <core/uint256.hpp> // uint288
42+
43+
namespace dgb {
44+
45+
// Resolved endpoints of a get_pool_attempts_per_second window. The caller
46+
// performs the chain walk (dist guard, near lookup, far via get_nth_parent_hash,
47+
// work delta via get_delta) and hands the results here.
48+
struct PoolAttemptsInputs {
49+
int32_t dist; // requested window depth; oracle asserts dist >= 2
50+
bool near_in_chain; // previous_share_hash present in the tracker
51+
bool far_resolved; // get_nth_parent_hash(dist-1) found AND in the tracker
52+
uint288 attempts; // get_delta(near, far).work (or .min_work) -- caller picks
53+
uint32_t near_ts; // near.timestamp
54+
uint32_t far_ts; // far.timestamp
55+
};
56+
57+
// attempts_per_second over the resolved window.
58+
//
59+
// Returns 0 when the window cannot be formed: dist < 2 (oracle assert), the near
60+
// share is absent, or the far endpoint did not resolve. Otherwise the time span
61+
// is near_ts - far_ts clamped up to 1 (matching the oracle `if time <= 0: time =
62+
// 1`), and the result is the integer division attempts / span (the integer=True
63+
// path c2pool always uses). uint288 throughout -- no overflow on the cumulative
64+
// work numerator.
65+
inline uint288 compute_pool_attempts_per_second(const PoolAttemptsInputs& in) {
66+
if (in.dist < 2) return uint288(0);
67+
if (!in.near_in_chain) return uint288(0);
68+
if (!in.far_resolved) return uint288(0);
69+
70+
int32_t time_span = static_cast<int32_t>(in.near_ts) - static_cast<int32_t>(in.far_ts);
71+
if (time_span <= 0) time_span = 1;
72+
73+
return in.attempts / uint288(time_span);
74+
}
75+
76+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,19 @@ if (BUILD_TESTING AND GTest_FOUND)
708708
target_link_libraries(dgb_expected_time_to_block_test PRIVATE
709709
GTest::gtest_main GTest::gtest)
710710
gtest_add_tests(dgb_expected_time_to_block_test "" AUTO)
711+
712+
# dgb_pool_attempts_per_second_test: FENCED, additive KAT pinning the pure
713+
# arithmetic core of ShareTracker::get_pool_attempts_per_second lifted into
714+
# coin/pool_attempts_per_second.hpp vs the p2pool data.py oracle
715+
# (attempts // max(1, near_ts - far_ts), with the dist>=2 + endpoint guards).
716+
# Links core for uint288. share_tracker.hpp NOT yet rewired (delegation is the
717+
# byte-identity follow-on). MUST also be in the build.yml --target allowlist
718+
# (#143 NOT_BUILT trap).
719+
add_executable(dgb_pool_attempts_per_second_test pool_attempts_per_second_test.cpp)
720+
target_link_libraries(dgb_pool_attempts_per_second_test PRIVATE
721+
GTest::gtest_main GTest::gtest
722+
core c2pool_hashrate c2pool_merged_mining c2pool_payout c2pool_storage)
723+
gtest_add_tests(dgb_pool_attempts_per_second_test "" AUTO)
711724
# dgb_pool_efficiency_test: FENCED, additive KAT pinning the pool
712725
# EFFICIENCY / REAL-HASHRATE diagnostics arithmetic in pool_efficiency.hpp vs
713726
# the p2pool main.py status-loop oracle (stale_prop = (orphan+doa)/total;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// dgb::compute_pool_attempts_per_second -- pool attempts/second KAT.
2+
//
3+
// FENCED conformance test (no production code touched). Pins the pure arithmetic
4+
// core of ShareTracker::get_pool_attempts_per_second, lifted into
5+
// pool_attempts_per_second.hpp, against the p2pool-dgb-scrypt oracle data.py
6+
// get_pool_attempts_per_second:
7+
// assert dist >= 2
8+
// attempts = get_delta(near, far).work # or .min_work
9+
// time = near.timestamp - far.timestamp
10+
// if time <= 0: time = 1
11+
// return attempts//time
12+
//
13+
// All expected values are HAND-DERIVED from that oracle expression (not produced
14+
// by calling the helper under test), so the test is non-circular: it
15+
// independently recomputes the oracle result and asserts the helper matches.
16+
//
17+
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
18+
// build.yml --target allowlist, or it becomes a #143-style NOT_BUILT sentinel
19+
// that reds master.
20+
21+
#include <impl/dgb/coin/pool_attempts_per_second.hpp>
22+
23+
#include <gtest/gtest.h>
24+
25+
namespace {
26+
27+
using dgb::PoolAttemptsInputs;
28+
using dgb::compute_pool_attempts_per_second;
29+
30+
// Convenience: a fully-resolved window (both endpoints present).
31+
PoolAttemptsInputs window(int32_t dist, const uint288& attempts,
32+
uint32_t near_ts, uint32_t far_ts) {
33+
return PoolAttemptsInputs{dist, /*near_in_chain=*/true, /*far_resolved=*/true,
34+
attempts, near_ts, far_ts};
35+
}
36+
37+
// ---- dist guard (oracle: assert dist >= 2) ------------------------------
38+
39+
TEST(DgbPoolAttempts, DistBelowTwoIsZero) {
40+
// dist 1 / 0 / negative -> no window -> 0, even with attempts set.
41+
EXPECT_EQ(compute_pool_attempts_per_second(window(1, uint288(1000), 100, 90)), uint288(0));
42+
EXPECT_EQ(compute_pool_attempts_per_second(window(0, uint288(1000), 100, 90)), uint288(0));
43+
EXPECT_EQ(compute_pool_attempts_per_second(window(-5, uint288(1000), 100, 90)), uint288(0));
44+
}
45+
46+
TEST(DgbPoolAttempts, DistExactlyTwoIsAllowed) {
47+
// dist == 2 is the minimum valid window; 1000 attempts over 10s -> 100.
48+
EXPECT_EQ(compute_pool_attempts_per_second(window(2, uint288(1000), 100, 90)), uint288(100));
49+
}
50+
51+
// ---- endpoint-resolution guards -----------------------------------------
52+
53+
TEST(DgbPoolAttempts, NearNotInChainIsZero) {
54+
PoolAttemptsInputs in{2, /*near_in_chain=*/false, /*far_resolved=*/true,
55+
uint288(1000), 100, 90};
56+
EXPECT_EQ(compute_pool_attempts_per_second(in), uint288(0));
57+
}
58+
59+
TEST(DgbPoolAttempts, FarUnresolvedIsZero) {
60+
PoolAttemptsInputs in{2, /*near_in_chain=*/true, /*far_resolved=*/false,
61+
uint288(1000), 100, 90};
62+
EXPECT_EQ(compute_pool_attempts_per_second(in), uint288(0));
63+
}
64+
65+
// ---- core divide: attempts / span ---------------------------------------
66+
67+
TEST(DgbPoolAttempts, AttemptsOverSpan) {
68+
// 6000 work over (200-150)=50s -> 120 (hand-derived).
69+
EXPECT_EQ(compute_pool_attempts_per_second(window(5, uint288(6000), 200, 150)), uint288(120));
70+
}
71+
72+
TEST(DgbPoolAttempts, IntegerTruncation) {
73+
// Oracle uses integer division: 1000 // 7 = 142 (142.857... truncated).
74+
EXPECT_EQ(compute_pool_attempts_per_second(window(3, uint288(1000), 107, 100)), uint288(142));
75+
}
76+
77+
// ---- time-span clamp (oracle: if time <= 0: time = 1) -------------------
78+
79+
TEST(DgbPoolAttempts, ZeroSpanClampsToOne) {
80+
// near_ts == far_ts -> span 0 -> clamp to 1 -> attempts/1 = attempts.
81+
EXPECT_EQ(compute_pool_attempts_per_second(window(2, uint288(777), 500, 500)), uint288(777));
82+
}
83+
84+
TEST(DgbPoolAttempts, NegativeSpanClampsToOne) {
85+
// far newer than near (span -10) -> clamp to 1 -> attempts unchanged.
86+
EXPECT_EQ(compute_pool_attempts_per_second(window(2, uint288(555), 90, 100)), uint288(555));
87+
}
88+
89+
// ---- attempts is used verbatim (work vs min_work both flow through) -----
90+
91+
TEST(DgbPoolAttempts, AttemptsValuePassedThroughRegardlessOfSource) {
92+
// The function is agnostic to whether attempts came from delta.work or
93+
// delta.min_work -- the caller selects; pin that the chosen value divides.
94+
EXPECT_EQ(compute_pool_attempts_per_second(window(4, uint288(2000), 220, 200)), uint288(100)); // work-like
95+
EXPECT_EQ(compute_pool_attempts_per_second(window(4, uint288(400), 220, 200)), uint288(20)); // min_work-like
96+
}
97+
98+
// ---- wide numerator: no overflow truncation at 64 bits ------------------
99+
100+
TEST(DgbPoolAttempts, WideAttemptsNoOverflow) {
101+
// attempts = 2^200, span 2 -> 2^199. Far beyond uint64; uint288 holds it.
102+
uint288 big = uint288(1); big <<= 200;
103+
uint288 half = uint288(1); half <<= 199;
104+
EXPECT_EQ(compute_pool_attempts_per_second(window(2, big, 12, 10)), half);
105+
}
106+
107+
} // namespace

0 commit comments

Comments
 (0)