Skip to content

Commit cd431e6

Browse files
committed
dgb: lift naughty-propagation ancestor-punishment into coin SSOT + KAT
Phase-B conformance pillar. Lift the pure-integer naughty-generation arithmetic from share_tracker.hpp:561-577 into a header-only SSOT coin/naughty_propagation.hpp and pin it against the p2pool-dgb-scrypt data.py:543-549 oracle with a non-circular KAT. Oracle rule: when a share's previous_share_hash exists and the parent is naughty (>0), child.naughty = 1 + parent.naughty, reset to 0 once it would exceed 6 generations; a non-naughty parent leaves the child's counter untouched. The rule is consensus-bearing -- best_descendent (data.py:812-813) skips naughty children, so it decides which sharechain tails the pool refuses to build on. A silent drift in the +1 step or the 6-generation reset clamp would re-rank tails with no compile error, diverging from the V36 master-compat reference. naughty_child_generation() captures the bare +1/clamp arithmetic; propagate_naughty_from_parent() adds the "parent is naughty" guard and returns std::nullopt when no propagation applies (clean parent). FENCED: header-only, additive, share_tracker.hpp NOT rewired -- the inline body computes the identical value, so the byte-identity delegation is a follow-on. Pure unsigned arithmetic, links only GTest. Target wired into CMakeLists.txt + both build.yml allowlist sites. KAT 4/4 green, expectations hand-derived from the oracle.
1 parent a1da5e6 commit cd431e6

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 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 dgb_desired_version_tally_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 dgb_naughty_propagation_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 dgb_desired_version_tally_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 dgb_naughty_propagation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
220220
v37_test \
221221
-j$(nproc)
222222
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma once
2+
3+
// SSOT for DGB NAUGHTY-PROPAGATION ancestor punishment -- the pure-integer
4+
// generation arithmetic that taints the descendants of a share which built (or
5+
// would build) an invalid block. A share is marked naughty=1 when it carries an
6+
// excessive block reward (data.py:535-536); thereafter EVERY descendant inherits
7+
// an incrementing generation counter, and that counter is what get_emphemeral
8+
// dispute / best-share selection reads to walk back off a naughty tail
9+
// (data.py:608-611, best_descendent skips naughty children at data.py:812-813).
10+
//
11+
// The propagation rule is consensus-bearing: it decides which shares the pool
12+
// will refuse to build on and which descendants it punishes, so a silent drift
13+
// in the "+1 per generation" step or the "reset after 6 generations" clamp would
14+
// re-rank sharechain tails with NO compile error, diverging operator-facing chain
15+
// selection from the p2pool reference the V36 master-compat invariant pins.
16+
//
17+
// Oracle: p2pool-dgb-scrypt data.py:543-549, evaluated for every share at
18+
// construction after the excessive-reward check:
19+
//
20+
// if self.share_data['previous_share_hash'] and \
21+
// tracker.items[self.share_data['previous_share_hash']].naughty:
22+
// print "naughty ancestor found %i generations ago" % ...
23+
// self.naughty = 1 + tracker.items[self.share_data['previous_share_hash']].naughty
24+
// if self.naughty > 6:
25+
// self.naughty = 0
26+
//
27+
// Two observable facts pinned here:
28+
// 1. The guard is "parent EXISTS and parent.naughty is truthy". Python treats
29+
// naughty==0 as falsy, so a non-naughty parent leaves self.naughty UNTOUCHED
30+
// (it keeps whatever the excessive-reward branch set -- 0 normally, 1 if this
31+
// very share is itself a bad-reward share). Propagation NEVER zeroes an
32+
// already-naughty share whose parent happens to be clean.
33+
// 2. When the parent IS naughty (1..6), the child generation is parent+1, and
34+
// a child that would reach generation 7 wraps back to 0 -- i.e. punishment
35+
// spans exactly 6 generations (parent gen 1 -> ... -> gen 6 kept; the would-be
36+
// gen-7 descendant is forgiven). "third and fourth generation" in the oracle
37+
// comment is hyperbole; the code clamps at 6.
38+
//
39+
// Per-coin isolation: dgb/ only. Header-only, additive. This slice does NOT yet
40+
// rewire share_tracker.hpp -- that is the byte-identity delegation follow-on. The
41+
// inline body at share_tracker.hpp:561-577 computes the identical value
42+
// (parent_idx->naughty + 1, then > 6 -> 0), so the follow-on is provably
43+
// value-identical. Pure unsigned arithmetic -> no core link needed.
44+
45+
#include <cstdint>
46+
#include <optional>
47+
48+
namespace dgb {
49+
50+
// Child naughty generation given a NAUGHTY parent (parent_naughty >= 1).
51+
// Oracle data.py:547-549: self.naughty = 1 + parent.naughty; if > 6 -> 0.
52+
// Precondition: caller has already established the parent is naughty; this is the
53+
// bare arithmetic so a KAT can pin the +1 step and the 6-generation reset clamp.
54+
inline std::uint32_t naughty_child_generation(std::uint32_t parent_naughty)
55+
{
56+
std::uint32_t child = 1u + parent_naughty; // data.py:547
57+
if (child > 6u) // data.py:548
58+
child = 0u; // data.py:549 -- reset after 6 generations
59+
return child;
60+
}
61+
62+
// Full propagation INCLUDING the oracle's "parent is naughty" guard
63+
// (data.py:543). Returns the value to assign to the child's naughty counter, or
64+
// std::nullopt when no propagation applies -- i.e. the parent is not naughty, in
65+
// which case the oracle leaves self.naughty untouched (the caller must NOT
66+
// overwrite it). This mirrors the inline guard at share_tracker.hpp:567
67+
// (`parent_idx->naughty > 0`): assign only inside the if-branch.
68+
inline std::optional<std::uint32_t> propagate_naughty_from_parent(std::uint32_t parent_naughty)
69+
{
70+
if (parent_naughty == 0u) // data.py:543 -- non-naughty parent is falsy
71+
return std::nullopt; // leave child's naughty as-is
72+
return naughty_child_generation(parent_naughty);
73+
}
74+
75+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,20 @@ if (BUILD_TESTING AND GTest_FOUND)
766766
core
767767
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
768768
gtest_add_tests(dgb_desired_version_tally_test "" AUTO)
769+
# dgb_naughty_propagation_test: FENCED, additive KAT pinning the
770+
# NAUGHTY-PROPAGATION ancestor-punishment generation arithmetic in
771+
# coin/naughty_propagation.hpp vs the p2pool-dgb-scrypt data.py:543-549
772+
# oracle (child = 1 + parent.naughty; reset to 0 after 6 generations; the
773+
# "parent is naughty" guard leaves a clean-parent child untouched). The rule
774+
# decides which sharechain tails the pool refuses to build on (best_descendent
775+
# skips naughty children), so it is consensus-bearing. Pure unsigned
776+
# arithmetic -> links only GTest. share_tracker.hpp:561-577 NOT yet rewired
777+
# (delegation is the byte-identity follow-on). MUST also be in the build.yml
778+
# --target allowlist (#143 NOT_BUILT trap).
779+
add_executable(dgb_naughty_propagation_test naughty_propagation_test.cpp)
780+
target_link_libraries(dgb_naughty_propagation_test PRIVATE
781+
GTest::gtest_main GTest::gtest)
782+
gtest_add_tests(dgb_naughty_propagation_test "" AUTO)
769783
# dgb_pool_efficiency_test: FENCED, additive KAT pinning the pool
770784
# EFFICIENCY / REAL-HASHRATE diagnostics arithmetic in pool_efficiency.hpp vs
771785
# the p2pool main.py status-loop oracle (stale_prop = (orphan+doa)/total;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// dgb NAUGHTY-PROPAGATION ancestor-punishment KAT.
2+
//
3+
// FENCED, additive (no production code touched this slice). Pins
4+
// src/impl/dgb/coin/naughty_propagation.hpp against the p2pool-dgb-scrypt
5+
// data.py:543-549 propagation rule:
6+
// if prev_share_hash and tracker.items[prev_share_hash].naughty:
7+
// self.naughty = 1 + tracker.items[prev_share_hash].naughty
8+
// if self.naughty > 6: self.naughty = 0
9+
//
10+
// Every expectation is hand-derived from the oracle Python (the +1-per-generation
11+
// step and the 6-generation reset clamp), NOT read from the code under test, so
12+
// the KAT is non-circular. The inline body at share_tracker.hpp:561-577 is NOT
13+
// yet rewired (delegation is the byte-identity follow-on). Pure unsigned
14+
// arithmetic -> links only GTest.
15+
// MUST appear in BOTH this dir CMakeLists.txt AND the build.yml --target
16+
// allowlist, or it becomes a #143 NOT_BUILT sentinel.
17+
18+
#include <impl/dgb/coin/naughty_propagation.hpp>
19+
20+
#include <cstdint>
21+
#include <optional>
22+
23+
#include <gtest/gtest.h>
24+
25+
using dgb::naughty_child_generation;
26+
using dgb::propagate_naughty_from_parent;
27+
28+
// --- bare child generation = 1 + parent, clamp >6 -> 0 (data.py:547-549) ----
29+
// Hand-derived table over every reachable parent generation. Parent naughty in
30+
// the live chain is always in [1,6] (it was itself produced by this same rule),
31+
// but we pin 7 too as a defensive over-the-clamp input.
32+
TEST(DgbNaughtyPropagation, ChildGenerationTable)
33+
{
34+
// parent gen 1 (the bad-reward share itself) -> child gen 2
35+
EXPECT_EQ(naughty_child_generation(1u), 2u);
36+
EXPECT_EQ(naughty_child_generation(2u), 3u);
37+
EXPECT_EQ(naughty_child_generation(3u), 4u);
38+
EXPECT_EQ(naughty_child_generation(4u), 5u);
39+
// gen 5 -> 6 is the LAST kept generation (6 is not > 6)
40+
EXPECT_EQ(naughty_child_generation(5u), 6u);
41+
// gen 6 -> would-be 7, clamps to 0: the 6th-generation descendant is forgiven
42+
EXPECT_EQ(naughty_child_generation(6u), 0u);
43+
// defensive: an out-of-range parent (7) also wraps to 0 (8 > 6)
44+
EXPECT_EQ(naughty_child_generation(7u), 0u);
45+
}
46+
47+
// --- the guarded propagation: nullopt when parent not naughty (data.py:543) --
48+
TEST(DgbNaughtyPropagation, NonNaughtyParentLeavesChildUntouched)
49+
{
50+
// parent.naughty == 0 is falsy in Python -> no assignment happens.
51+
EXPECT_FALSE(propagate_naughty_from_parent(0u).has_value());
52+
}
53+
54+
// --- guarded propagation matches the bare table for naughty parents ---------
55+
TEST(DgbNaughtyPropagation, NaughtyParentPropagates)
56+
{
57+
EXPECT_EQ(propagate_naughty_from_parent(1u), std::optional<std::uint32_t>(2u));
58+
EXPECT_EQ(propagate_naughty_from_parent(2u), std::optional<std::uint32_t>(3u));
59+
EXPECT_EQ(propagate_naughty_from_parent(3u), std::optional<std::uint32_t>(4u));
60+
EXPECT_EQ(propagate_naughty_from_parent(4u), std::optional<std::uint32_t>(5u));
61+
EXPECT_EQ(propagate_naughty_from_parent(5u), std::optional<std::uint32_t>(6u));
62+
// 6th-generation descendant forgiven (assigned 0, NOT left untouched)
63+
EXPECT_EQ(propagate_naughty_from_parent(6u), std::optional<std::uint32_t>(0u));
64+
}
65+
66+
// --- non-circular: a full 8-generation chain walk reproduces the oracle ------
67+
// Seed share is itself naughty (gen 1, excessive reward). Walk forward applying
68+
// the rule and compare against the hand-derived sequence the oracle produces:
69+
// 1 (seed, bad reward), then each child = clamp(1+parent):
70+
// 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 0(forgiven) -> 0(parent 0 = clean, untouched)
71+
TEST(DgbNaughtyPropagation, EightGenerationChainMatchesOracleSequence)
72+
{
73+
const std::uint32_t expected[8] = {1u, 2u, 3u, 4u, 5u, 6u, 0u, 0u};
74+
75+
std::uint32_t gen = 1u; // seed: excessive-reward share, naughty=1
76+
EXPECT_EQ(gen, expected[0]);
77+
for (int i = 1; i < 8; ++i) {
78+
auto next = propagate_naughty_from_parent(gen);
79+
if (next.has_value()) {
80+
gen = *next; // parent was naughty -> inherit/clamp
81+
} else {
82+
gen = 0u; // parent clean -> child stays at its default 0
83+
}
84+
EXPECT_EQ(gen, expected[i]) << "generation " << i;
85+
}
86+
}

0 commit comments

Comments
 (0)