From f03ee77a2014471dbd727f3fa44e97e9d3f12ff5 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 28 Jun 2026 06:52:19 +0000 Subject: [PATCH] dgb: runtime min-protocol-version 95%-ratchet (1400->3500) conforming to oracle Step-2 of the proto-floor conformance (step-1 #599 landed the cold floor 1400 + ratchet TARGET const 3500). Adds the pure SSOT free function dgb::ratchet_min_protocol_version (coin/desired_version_tally.hpp) mirroring p2pool-dgb-scrypt update_min_protocol_version (data.py:857-863): best-VERSION work-weight >= floor(total_weight * 95 / 100) => lift MINIMUM_PROTOCOL_VERSION 1400 -> 3500. Reads the same work-weighted desired-version map (get_desired_version_weights) over the same window [CHAIN_LENGTH*9//10, CHAIN_LENGTH//10] as the 60% switch gate (share_check step 2); this is its runtime sibling, lifting the inbound P2P handshake floor instead of rejecting a share. Integer math floor-divides exactly like the oracle (NOT cross-multiply -- the two differ at the boundary). KAT dgb_min_protocol_ratchet_test (9 cases, all hand-derived): guard, unanimous, exactly-95%, just-below, floor-div boundary (sum=101/best=95: ratchets under floor-div, would not under cross-mult), best-absent, empty-window oracle dict branch, work-weighted-not-flat-count, and consensus-magnitude (2^256-scale, no overflow). Registered in test CMakeLists + build.yml allowlist. Pure/additive: no production call-site rewired this slice (runtime wiring into the per-best-share loop is the follow-on, mirroring step-1 ship-core-then-wire). Consensus-bearing accept-floor value -> surface for operator merge-tap. --- .github/workflows/build.yml | 4 +- src/impl/dgb/coin/desired_version_tally.hpp | 48 ++++++++ src/impl/dgb/test/CMakeLists.txt | 14 +++ .../dgb/test/min_protocol_ratchet_test.cpp | 112 ++++++++++++++++++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/impl/dgb/test/min_protocol_ratchet_test.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2edf0fca0..b8f249764 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,7 +96,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 nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_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 dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_share_target_retarget_test dgb_share_bits_oracle_pin_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 dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_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_share_target_retarget_test dgb_share_bits_oracle_pin_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 dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test v37_test \ -j8 - name: Run tests @@ -241,7 +241,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 nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_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 dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_share_target_retarget_test dgb_share_bits_oracle_pin_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 dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_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_share_target_retarget_test dgb_share_bits_oracle_pin_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 dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ -j8 diff --git a/src/impl/dgb/coin/desired_version_tally.hpp b/src/impl/dgb/coin/desired_version_tally.hpp index 6879931ff..d79b7aec4 100644 --- a/src/impl/dgb/coin/desired_version_tally.hpp +++ b/src/impl/dgb/coin/desired_version_tally.hpp @@ -80,4 +80,52 @@ accumulate_version_counts(const std::vector& window) return counts; } +// Runtime P2P accept-floor RATCHET -- SSOT for the oracle update_min_protocol_version +// (p2pool-dgb-scrypt data.py:857-863), the runtime sibling of the 60% version-switch +// gate (share_check.hpp step 2). Both read the SAME work-weighted desired-version map +// over the SAME window [CHAIN_LENGTH*9//10, CHAIN_LENGTH//10] behind the best share's +// parent (main.py:216 / data.py:562 call-site); this one lifts the inbound handshake +// floor instead of rejecting a share. +// +// Oracle (data.py:857): +// def update_min_protocol_version(counts, share): +// minpver = getattr(share.net, 'MINIMUM_PROTOCOL_VERSION', 1400) +// newminpver = share.MINIMUM_PROTOCOL_VERSION +// if (counts is not None) and (minpver < newminpver): +// if counts.get(share.VERSION, 0) >= sum(counts.itervalues())*95//100: +// share.net.MINIMUM_PROTOCOL_VERSION = newminpver +// +// `counts` here is get_desired_version_counts -- WORK-WEIGHTED by +// target_to_average_attempts(share.target), keyed by desired_version, looked up by the +// best share's VERSION. c2pool's get_desired_version_weights is the byte-faithful +// match (see accumulate_version_weights above), so this function consumes that map +// directly. Pure: no I/O, inputs unmutated; returns the (possibly lifted) floor. +// +// Integer math is the oracle's exactly: ratchet iff +// best_weight >= floor(total_weight * 95 / 100). +// We floor-divide (total*95)/100 and NOT the cross-multiplied best*100 >= total*95 +// idiom -- the two differ at the boundary when (total*95) % 100 != 0 (then floor-div +// accepts best == floor(...), cross-mult rejects it), and the oracle floor-divides. +inline uint32_t ratchet_min_protocol_version( + const std::map& version_weights, + int64_t best_version, + uint32_t current_floor, + uint32_t target_floor) +{ + if (current_floor >= target_floor) // oracle: minpver < newminpver guard + return current_floor; + + uint288 best_weight; + uint288 total_weight; + for (const auto& [ver, w] : version_weights) { + total_weight = total_weight + w; + if (static_cast(ver) == best_version) + best_weight = best_weight + w; + } + // oracle: counts.get(share.VERSION, 0) >= sum(counts.itervalues())*95//100 + if (best_weight >= (total_weight * uint32_t(95)) / uint32_t(100)) + return target_floor; + return current_floor; +} + } // namespace dgb diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index 4755e7daf..e4a26acb2 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -901,6 +901,20 @@ if (BUILD_TESTING AND GTest_FOUND) core c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage) gtest_add_tests(dgb_desired_version_tally_test "" AUTO) + # dgb_min_protocol_ratchet_test: FENCED, additive KAT pinning the runtime + # P2P accept-floor ratchet dgb::ratchet_min_protocol_version (coin/ + # desired_version_tally.hpp) vs the p2pool-dgb-scrypt update_min_protocol_version + # oracle (data.py:857-863): best-VERSION work-weight >= floor(total*95/100) lifts + # MINIMUM_PROTOCOL_VERSION 1400->3500. Same work-weighted map / window as the 60% + # switch gate; floor-div boundary case pins the oracle math (not cross-multiply). + # Pure header -> links core only. MUST also be in the build.yml --target allowlist + # (#143 NOT_BUILT trap). + add_executable(dgb_min_protocol_ratchet_test min_protocol_ratchet_test.cpp) + target_link_libraries(dgb_min_protocol_ratchet_test PRIVATE + GTest::gtest_main GTest::gtest + core + c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage) + gtest_add_tests(dgb_min_protocol_ratchet_test "" AUTO) # dgb_naughty_propagation_test: FENCED, additive KAT pinning the # NAUGHTY-PROPAGATION ancestor-punishment generation arithmetic in # coin/naughty_propagation.hpp vs the p2pool-dgb-scrypt data.py:543-549 diff --git a/src/impl/dgb/test/min_protocol_ratchet_test.cpp b/src/impl/dgb/test/min_protocol_ratchet_test.cpp new file mode 100644 index 000000000..80f3c2d81 --- /dev/null +++ b/src/impl/dgb/test/min_protocol_ratchet_test.cpp @@ -0,0 +1,112 @@ +// dgb runtime P2P accept-floor RATCHET KAT. +// +// FENCED, additive (no production code touched -- pins the pure free function +// dgb::ratchet_min_protocol_version in coin/desired_version_tally.hpp). Mirrors +// the p2pool-dgb-scrypt update_min_protocol_version oracle (data.py:857-863): +// def update_min_protocol_version(counts, share): +// minpver = getattr(share.net, "MINIMUM_PROTOCOL_VERSION", 1400) +// newminpver = share.MINIMUM_PROTOCOL_VERSION +// if (counts is not None) and (minpver < newminpver): +// if counts.get(share.VERSION, 0) >= sum(counts.itervalues())*95//100: +// share.net.MINIMUM_PROTOCOL_VERSION = newminpver +// +// counts is the WORK-WEIGHTED get_desired_version_counts map (each share weighted +// by target_to_average_attempts, keyed by desired_version, looked up by the best +// share VERSION) -- c2pool exposes it as get_desired_version_weights. The cold +// floor 1400 / ratchet target 3500 are the oracle p2p.py:153 getattr fallback and +// data.py:81 BaseShare.MINIMUM_PROTOCOL_VERSION (== config_pool.hpp +// MINIMUM_PROTOCOL_VERSION / SHARE_MINIMUM_PROTOCOL_VERSION, step-1 #599). +// +// Every expectation is hand-derived from the oracle formula. MUST appear in BOTH +// this dir CMakeLists.txt AND the build.yml --target allowlist, or it becomes a +// #143 NOT_BUILT sentinel. + +#include + +#include // uint288 + +#include + +namespace { + +uint288 u(uint64_t n) { return uint288(n); } + +// 2^k as a uint288 -- the scale of a real target_to_average_attempts (ttaa(2^k-1) +// = 2^(256-k)), so the boundary/overflow cases run at consensus magnitudes. +uint288 pow2(unsigned k) { + uint288 v = u(1); + for (unsigned i = 0; i < k; ++i) v = v * uint32_t(2); + return v; +} + +constexpr uint32_t COLD = 1400; // oracle p2p.py:153 getattr fallback +constexpr uint32_t TARGET = 3500; // oracle data.py:81 BaseShare.MINIMUM_PROTOCOL_VERSION + +} // namespace + +// --- guard: floor already at/above target -> no-op (minpver < newminpver false) +TEST(DgbMinProtocolRatchet, AlreadyAtTargetIsNoOp) { + EXPECT_EQ(dgb::ratchet_min_protocol_version({{36, u(100)}}, 36, TARGET, TARGET), TARGET); + EXPECT_EQ(dgb::ratchet_min_protocol_version({{36, u(100)}}, 36, TARGET + 1, TARGET), TARGET + 1); +} + +// --- unanimous support -> ratchet cold floor up to target ------------------- +TEST(DgbMinProtocolRatchet, UnanimousRatchets) { + EXPECT_EQ(dgb::ratchet_min_protocol_version({{36, u(100)}}, 36, COLD, TARGET), TARGET); +} + +// --- exactly 95% (sum=100, best=95) -> 95 >= 100*95//100=95 -> ratchet ------- +TEST(DgbMinProtocolRatchet, Exactly95PercentRatchets) { + auto w = std::map{{36, u(95)}, {35, u(5)}}; + EXPECT_EQ(dgb::ratchet_min_protocol_version(w, 36, COLD, TARGET), TARGET); +} + +// --- just below 95% (sum=100, best=94) -> 94 >= 95 false -> stays cold ------- +TEST(DgbMinProtocolRatchet, JustBelow95PercentStaysCold) { + auto w = std::map{{36, u(94)}, {35, u(6)}}; + EXPECT_EQ(dgb::ratchet_min_protocol_version(w, 36, COLD, TARGET), COLD); +} + +// --- floor-div vs cross-multiply BOUNDARY: sum=101, best=95. +// oracle floor: 95 >= floor(101*95/100)=floor(95.95)=95 -> RATCHET. +// cross-mult (best*100 >= sum*95): 9500 >= 9595 false -> would NOT ratchet. +// This case proves the function floor-divides like the oracle, not cross-mults. +TEST(DgbMinProtocolRatchet, FloorDivBoundaryRatchets) { + auto w = std::map{{36, u(95)}, {35, u(6)}}; // sum=101 + EXPECT_EQ(dgb::ratchet_min_protocol_version(w, 36, COLD, TARGET), TARGET); +} + +// --- best version absent from the map -> best_weight 0 -> stays cold --------- +TEST(DgbMinProtocolRatchet, BestVersionAbsentStaysCold) { + EXPECT_EQ(dgb::ratchet_min_protocol_version({{35, u(100)}}, 36, COLD, TARGET), COLD); +} + +// --- empty window: 0 >= 0*95//100=0 -> oracle ratchets (counts={} != None). +// The None-guard is the CALL-SITE job (oracle main.py:216 only calls when +// len(shares) > CHAIN_LENGTH); the pure function mirrors the dict-branch. +TEST(DgbMinProtocolRatchet, EmptyWindowMirrorsOracleDictBranch) { + EXPECT_EQ(dgb::ratchet_min_protocol_version({}, 36, COLD, TARGET), TARGET); +} + +// --- work-weighted, NOT flat count: version 35 has the most SHARES but version +// 36 holds the dominant WORK. ratchet keys on work (get_desired_version_weights). +// Build the map through accumulate_version_weights to exercise the real pipe. +TEST(DgbMinProtocolRatchet, WorkWeightedNotFlatCount) { + // ten tiny v35 shares (work 1 each = 10) + one heavy v36 share (work 1000). + std::vector window; + for (int i = 0; i < 10; ++i) window.push_back({35, u(1)}); + window.push_back({36, u(1000)}); // total=1010, best(36)=1000 >= 1010*95//100=959 + auto w = dgb::accumulate_version_weights(window); + EXPECT_EQ(dgb::ratchet_min_protocol_version(w, 36, COLD, TARGET), TARGET); + // flat count would give v35 ten shares vs v36 one -> would NOT ratchet on 36. +} + +// --- consensus-magnitude weights (2^256-scale), no overflow in total*95 ------ +TEST(DgbMinProtocolRatchet, ConsensusMagnitudeNoOverflow) { + // 96 * 2^256 at v36, 4 * 2^256 at v35: total=100*2^256, best=96*2^256 >= 95% -> ratchet. + uint288 unit = pow2(256); + uint288 best = unit; for (int i = 1; i < 96; ++i) best = best + unit; // 96 * 2^256 + uint288 rest = unit; for (int i = 1; i < 4; ++i) rest = rest + unit; // 4 * 2^256 + auto w = std::map{{36, best}, {35, rest}}; + EXPECT_EQ(dgb::ratchet_min_protocol_version(w, 36, COLD, TARGET), TARGET); +}