Skip to content

Commit 1094551

Browse files
committed
dgb(phase-b): SSOT for desired-version work/count tally + KAT
Lift the per-version accumulation core of ShareTracker::get_desired_version_weights (and its flat-count diagnostic sibling get_desired_version_counts) into coin/desired_version_tally.hpp as free functions over already-resolved (desired_version, work) pairs. The work-weighted map is the consensus input to the V36 60%-by-work switch rule (share_check step 2) and the #288 AutoRatchet activation tail guard; the flat-count map is diagnostics-only and never the gate. Oracle: p2pool-dgb-scrypt data.py:918-922 get_desired_version_counts, which is already work-weighted (res[dv] += target_to_average_attempts(share.target)). The per-share weight equals ShareIndex::work = chain::target_to_average_attempts( bits_to_target(m_bits)); the KAT anchors that equivalence non-circularly via ttaa over 2^k-1 targets (hand-derived 2^(256-k)). FENCED, additive, header-only: share_tracker.hpp is NOT rewired (byte-identity delegation is the follow-on). The chain-walk and lookbehind clamp stay in ShareTracker (clamp is the separate chain_walk_window SSOT). Consensus-neutral. Registered in both the dir CMakeLists.txt and the build.yml --target allowlist. KAT 6/6 green.
1 parent 7082ea2 commit 1094551

4 files changed

Lines changed: 219 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 nmc_mempool_name_test nmc_block_broadcast_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_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_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_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_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 nmc_mempool_name_test nmc_block_broadcast_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_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_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_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
220220
v37_test \
221221
-j$(nproc)
222222
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#pragma once
2+
3+
// SSOT for the DGB DESIRED-VERSION TALLY -- the pure accumulation core of
4+
// ShareTracker::get_desired_version_weights() (and its flat-count diagnostic
5+
// sibling get_desired_version_counts()). Walking back over a clamped
6+
// [.., CHAIN_LENGTH] window of shares, each share's m_desired_version is
7+
// bucketed and either its work is summed (the consensus map) or its occurrence
8+
// counted (the diagnostic map). The work-weighted map is the CONSENSUS input to
9+
// the V36 60%-by-work switch rule (share_check step 2) and the #288 AutoRatchet
10+
// activation tail guard; the flat-count map is diagnostics/KAT only and is
11+
// NEVER the gate.
12+
//
13+
// Oracle: p2pool-dgb-scrypt data.py:918-922 get_desired_version_counts:
14+
// def get_desired_version_counts(tracker, best_share_hash, dist):
15+
// res = {}
16+
// for share in tracker.get_chain(best_share_hash, dist):
17+
// res[share.desired_version] = res.get(share.desired_version, 0) \
18+
// + bitcoin_data.target_to_average_attempts(share.target)
19+
// return res
20+
//
21+
// NOTE the oracle's get_desired_version_counts is ALREADY work-weighted -- each
22+
// share contributes target_to_average_attempts(share.target), NOT 1. c2pool
23+
// splits this into two accessors: get_desired_version_weights (the true oracle
24+
// match -- weight = ShareIndex::work = chain::target_to_average_attempts(
25+
// chain::bits_to_target(m_bits)), the CONSENSUS gate input) and
26+
// get_desired_version_counts (a flat occurrence count, diagnostics-only, never
27+
// the gate -- see the share_tracker.hpp comment, #406 and #288). This header
28+
// captures ONLY the per-version accumulation over already-resolved
29+
// (desired_version, work) pairs as free functions; the chain-walk and the
30+
// lookbehind clamp stay inside ShareTracker (the clamp is the separate
31+
// chain_walk_window SSOT). share_tracker.hpp is NOT rewired this slice
32+
// (byte-identity delegation is the follow-on).
33+
//
34+
// Per-coin isolation: dgb/ only. Header-only, additive, consensus-neutral (pure
35+
// std::map accumulation, no value semantics changed). MUST appear in BOTH this
36+
// dir CMakeLists.txt AND the build.yml --target allowlist, or it becomes a #143
37+
// NOT_BUILT sentinel.
38+
39+
#include <core/uint256.hpp> // uint288
40+
41+
#include <cstdint>
42+
#include <map>
43+
#include <vector>
44+
45+
namespace dgb {
46+
47+
// One share's contribution to the tally: its desired_version and its work
48+
// (= chain::target_to_average_attempts(chain::bits_to_target(m_bits)), the same
49+
// value ShareIndex::work caches). Mirrors the (dv, idx->work) pair the inline
50+
// loop reads per share in get_desired_version_weights.
51+
struct VersionWork {
52+
uint64_t desired_version;
53+
uint288 work;
54+
};
55+
56+
// Work-weighted version tally -- the CONSENSUS gate input. Mirrors verbatim the
57+
// inline body:
58+
// for (share in window) weights[dv] = weights[dv] + idx->work;
59+
// std::map value-initializes uint288(0) on first touch of a version key, exactly
60+
// matching the inline weights[dv] + idx->work (and the oracle
61+
// res.get(dv, 0) + attempts).
62+
inline std::map<uint64_t, uint288>
63+
accumulate_version_weights(const std::vector<VersionWork>& window)
64+
{
65+
std::map<uint64_t, uint288> weights;
66+
for (const auto& s : window)
67+
weights[s.desired_version] = weights[s.desired_version] + s.work;
68+
return weights;
69+
}
70+
71+
// Flat occurrence count -- DIAGNOSTICS/KAT ONLY, never the consensus gate. Each
72+
// share counts as 1 regardless of its work. Mirrors verbatim the inline body:
73+
// for (share in window) counts[dv]++;
74+
inline std::map<uint64_t, int32_t>
75+
accumulate_version_counts(const std::vector<uint64_t>& window)
76+
{
77+
std::map<uint64_t, int32_t> counts;
78+
for (uint64_t dv : window)
79+
counts[dv]++;
80+
return counts;
81+
}
82+
83+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,24 @@ if (BUILD_TESTING AND GTest_FOUND)
748748
target_link_libraries(dgb_binomial_conf_interval_test PRIVATE
749749
GTest::gtest_main GTest::gtest)
750750
gtest_add_tests(dgb_binomial_conf_interval_test "" AUTO)
751+
# dgb_desired_version_tally_test: FENCED, additive KAT pinning the
752+
# version->work / version->count accumulation core lifted into
753+
# coin/desired_version_tally.hpp vs the p2pool-dgb-scrypt data.py:918-922
754+
# get_desired_version_counts oracle (res[dv] += target_to_average_attempts(
755+
# share.target)). The work-weighted map is the CONSENSUS 60%-by-work switch
756+
# input (share_check step 2 / #288 AutoRatchet tail guard); the flat-count
757+
# accumulator is diagnostics-only and pinned just to lock the documented
758+
# divergence. The work-equivalence anchor uses chain::target_to_average_attempts
759+
# over 2^k-1 targets (hand-derived ttaa), so links core for uint288 +
760+
# target_utils. share_tracker.hpp NOT yet rewired (delegation is the
761+
# byte-identity follow-on). MUST also be in the build.yml --target allowlist
762+
# (#143 NOT_BUILT trap).
763+
add_executable(dgb_desired_version_tally_test desired_version_tally_test.cpp)
764+
target_link_libraries(dgb_desired_version_tally_test PRIVATE
765+
GTest::gtest_main GTest::gtest
766+
core
767+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
768+
gtest_add_tests(dgb_desired_version_tally_test "" AUTO)
751769
# dgb_pool_efficiency_test: FENCED, additive KAT pinning the pool
752770
# EFFICIENCY / REAL-HASHRATE diagnostics arithmetic in pool_efficiency.hpp vs
753771
# the p2pool main.py status-loop oracle (stale_prop = (orphan+doa)/total;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// dgb desired-version tally -- version->work / version->count accumulation KAT.
2+
//
3+
// FENCED, additive (no production code touched this slice). Pins
4+
// src/impl/dgb/coin/desired_version_tally.hpp against the p2pool-dgb-scrypt
5+
// data.py:918-922 get_desired_version_counts oracle:
6+
// res = {}
7+
// for share in tracker.get_chain(best_share_hash, dist):
8+
// res[share.desired_version] = res.get(share.desired_version, 0) \
9+
// + bitcoin_data.target_to_average_attempts(share.target)
10+
// return res
11+
//
12+
// The oracle weights each share by target_to_average_attempts(share.target);
13+
// c2pool exposes that as get_desired_version_weights (CONSENSUS gate input) with
14+
// per-share weight = ShareIndex::work = chain::target_to_average_attempts(
15+
// chain::bits_to_target(m_bits)). The flat-count accumulator is diagnostics-only
16+
// and has NO oracle equivalent (the oracle has no flat count) -- it is pinned
17+
// here only to lock the documented divergence (each share == 1, work ignored).
18+
//
19+
// Every expectation is hand-derived from the oracle formula. The work-equivalence
20+
// anchor uses targets whose target_to_average_attempts is exactly computable by
21+
// hand: ttaa(t) = 2^256 / (t + 1), so for t = 2^k - 1, t + 1 = 2^k and
22+
// ttaa = 2^(256-k). The chain-walk + lookbehind clamp stay in ShareTracker; this
23+
// header lifts ONLY the per-version map accumulation. share_tracker.hpp is NOT
24+
// rewired (delegation is the byte-identity follow-on). Pure header (uint288) ->
25+
// links core only. MUST appear in BOTH this dir CMakeLists.txt AND the build.yml
26+
// --target allowlist, or it becomes a #143 NOT_BUILT sentinel.
27+
28+
#include <impl/dgb/coin/desired_version_tally.hpp>
29+
30+
#include <core/uint256.hpp> // uint288
31+
#include <core/target_utils.hpp> // chain::target_to_average_attempts
32+
33+
#include <gtest/gtest.h>
34+
35+
namespace {
36+
37+
uint288 u(uint64_t n) { return uint288(n); }
38+
39+
} // namespace
40+
41+
// --- empty window -> empty maps (the actual<=0 early-return shape) ----------
42+
TEST(DgbDesiredVersionTally, EmptyWindowYieldsEmptyMaps) {
43+
EXPECT_TRUE(dgb::accumulate_version_weights({}).empty());
44+
EXPECT_TRUE(dgb::accumulate_version_counts({}).empty());
45+
}
46+
47+
// --- single share: one bucket, weight == its work, count == 1 ---------------
48+
TEST(DgbDesiredVersionTally, SingleShareSingleBucket) {
49+
auto w = dgb::accumulate_version_weights({{36, u(42)}});
50+
ASSERT_EQ(w.size(), 1u);
51+
EXPECT_EQ(w[36], u(42));
52+
53+
auto c = dgb::accumulate_version_counts({36});
54+
ASSERT_EQ(c.size(), 1u);
55+
EXPECT_EQ(c[36], 1);
56+
}
57+
58+
// --- same version, many shares: weights sum, count increments ---------------
59+
TEST(DgbDesiredVersionTally, SameVersionAccumulates) {
60+
auto w = dgb::accumulate_version_weights({{36, u(10)}, {36, u(20)}, {36, u(30)}});
61+
ASSERT_EQ(w.size(), 1u);
62+
EXPECT_EQ(w[36], u(60)); // 10 + 20 + 30
63+
64+
auto c = dgb::accumulate_version_counts({36, 36, 36});
65+
EXPECT_EQ(c[36], 3);
66+
}
67+
68+
// --- multiple versions are bucketed independently ---------------------------
69+
TEST(DgbDesiredVersionTally, MultipleVersionsBucketed) {
70+
auto w = dgb::accumulate_version_weights(
71+
{{35, u(5)}, {36, u(11)}, {35, u(7)}});
72+
ASSERT_EQ(w.size(), 2u);
73+
EXPECT_EQ(w[35], u(12)); // 5 + 7
74+
EXPECT_EQ(w[36], u(11));
75+
76+
auto c = dgb::accumulate_version_counts({35, 36, 35});
77+
EXPECT_EQ(c[35], 2);
78+
EXPECT_EQ(c[36], 1);
79+
}
80+
81+
// --- NON-CIRCULAR oracle anchor: per-share weight == target_to_average_attempts,
82+
// summed per version, matches data.py:918 res[dv] += ttaa(target). ---------
83+
// t = 2^255-1 -> ttaa = 2 ; t = 2^254-1 -> ttaa = 4 ; t = 2^253-1 -> ttaa = 8
84+
TEST(DgbDesiredVersionTally, WorkWeightedMatchesOracleAttempts) {
85+
uint256 t255, t254, t253;
86+
t255.SetHex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // 2^255-1
87+
t254.SetHex("3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // 2^254-1
88+
t253.SetHex("1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // 2^253-1
89+
90+
const uint288 a255 = chain::target_to_average_attempts(t255);
91+
const uint288 a254 = chain::target_to_average_attempts(t254);
92+
const uint288 a253 = chain::target_to_average_attempts(t253);
93+
94+
// Hand-derived ttaa = 2^(256-k). These also non-circularly pin the work def.
95+
ASSERT_EQ(a255, u(2));
96+
ASSERT_EQ(a254, u(4));
97+
ASSERT_EQ(a253, u(8));
98+
99+
// v36 has two shares (work 2 and 4), v35 has one share (work 8).
100+
auto w = dgb::accumulate_version_weights(
101+
{{36, a255}, {35, a253}, {36, a254}});
102+
ASSERT_EQ(w.size(), 2u);
103+
EXPECT_EQ(w[36], u(6)); // 2 + 4
104+
EXPECT_EQ(w[35], u(8)); // 8
105+
}
106+
107+
// --- flat count is NOT work-weighted: pins the documented divergence ---------
108+
TEST(DgbDesiredVersionTally, FlatCountIgnoresWork) {
109+
// Three v36 shares with wildly different work: weights sum the work,
110+
// counts treat each as exactly 1 (diagnostics-only, never the gate).
111+
auto w = dgb::accumulate_version_weights({{36, u(1)}, {36, u(1000)}, {36, u(7)}});
112+
EXPECT_EQ(w[36], u(1008));
113+
114+
auto c = dgb::accumulate_version_counts({36, 36, 36});
115+
EXPECT_EQ(c[36], 3); // not 1008 -- work is ignored
116+
}

0 commit comments

Comments
 (0)