Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,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 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_dc_proof_test dgb_aux_doge_bind_parsers_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_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 \
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 \
-j$(nproc)

- name: Run tests
Expand Down Expand Up @@ -216,7 +216,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 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_dc_proof_test dgb_aux_doge_bind_parsers_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_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 \
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 \
v37_test \
-j$(nproc)

Expand Down
75 changes: 75 additions & 0 deletions src/impl/dgb/coin/naughty_propagation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

// SSOT for DGB NAUGHTY-PROPAGATION ancestor punishment -- the pure-integer
// generation arithmetic that taints the descendants of a share which built (or
// would build) an invalid block. A share is marked naughty=1 when it carries an
// excessive block reward (data.py:535-536); thereafter EVERY descendant inherits
// an incrementing generation counter, and that counter is what get_emphemeral
// dispute / best-share selection reads to walk back off a naughty tail
// (data.py:608-611, best_descendent skips naughty children at data.py:812-813).
//
// The propagation rule is consensus-bearing: it decides which shares the pool
// will refuse to build on and which descendants it punishes, so a silent drift
// in the "+1 per generation" step or the "reset after 6 generations" clamp would
// re-rank sharechain tails with NO compile error, diverging operator-facing chain
// selection from the p2pool reference the V36 master-compat invariant pins.
//
// Oracle: p2pool-dgb-scrypt data.py:543-549, evaluated for every share at
// construction after the excessive-reward check:
//
// if self.share_data['previous_share_hash'] and \
// tracker.items[self.share_data['previous_share_hash']].naughty:
// print "naughty ancestor found %i generations ago" % ...
// self.naughty = 1 + tracker.items[self.share_data['previous_share_hash']].naughty
// if self.naughty > 6:
// self.naughty = 0
//
// Two observable facts pinned here:
// 1. The guard is "parent EXISTS and parent.naughty is truthy". Python treats
// naughty==0 as falsy, so a non-naughty parent leaves self.naughty UNTOUCHED
// (it keeps whatever the excessive-reward branch set -- 0 normally, 1 if this
// very share is itself a bad-reward share). Propagation NEVER zeroes an
// already-naughty share whose parent happens to be clean.
// 2. When the parent IS naughty (1..6), the child generation is parent+1, and
// a child that would reach generation 7 wraps back to 0 -- i.e. punishment
// spans exactly 6 generations (parent gen 1 -> ... -> gen 6 kept; the would-be
// gen-7 descendant is forgiven). "third and fourth generation" in the oracle
// comment is hyperbole; the code clamps at 6.
//
// Per-coin isolation: dgb/ only. Header-only, additive. This slice does NOT yet
// rewire share_tracker.hpp -- that is the byte-identity delegation follow-on. The
// inline body at share_tracker.hpp:561-577 computes the identical value
// (parent_idx->naughty + 1, then > 6 -> 0), so the follow-on is provably
// value-identical. Pure unsigned arithmetic -> no core link needed.

#include <cstdint>
#include <optional>

namespace dgb {

// Child naughty generation given a NAUGHTY parent (parent_naughty >= 1).
// Oracle data.py:547-549: self.naughty = 1 + parent.naughty; if > 6 -> 0.
// Precondition: caller has already established the parent is naughty; this is the
// bare arithmetic so a KAT can pin the +1 step and the 6-generation reset clamp.
inline std::uint32_t naughty_child_generation(std::uint32_t parent_naughty)
{
std::uint32_t child = 1u + parent_naughty; // data.py:547
if (child > 6u) // data.py:548
child = 0u; // data.py:549 -- reset after 6 generations
return child;
}

// Full propagation INCLUDING the oracle's "parent is naughty" guard
// (data.py:543). Returns the value to assign to the child's naughty counter, or
// std::nullopt when no propagation applies -- i.e. the parent is not naughty, in
// which case the oracle leaves self.naughty untouched (the caller must NOT
// overwrite it). This mirrors the inline guard at share_tracker.hpp:567
// (`parent_idx->naughty > 0`): assign only inside the if-branch.
inline std::optional<std::uint32_t> propagate_naughty_from_parent(std::uint32_t parent_naughty)
{
if (parent_naughty == 0u) // data.py:543 -- non-naughty parent is falsy
return std::nullopt; // leave child's naughty as-is
return naughty_child_generation(parent_naughty);
}

} // namespace dgb
14 changes: 14 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,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_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
# oracle (child = 1 + parent.naughty; reset to 0 after 6 generations; the
# "parent is naughty" guard leaves a clean-parent child untouched). The rule
# decides which sharechain tails the pool refuses to build on (best_descendent
# skips naughty children), so it is consensus-bearing. Pure unsigned
# arithmetic -> links only GTest. share_tracker.hpp:561-577 NOT yet rewired
# (delegation is the byte-identity follow-on). MUST also be in the build.yml
# --target allowlist (#143 NOT_BUILT trap).
add_executable(dgb_naughty_propagation_test naughty_propagation_test.cpp)
target_link_libraries(dgb_naughty_propagation_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_naughty_propagation_test "" AUTO)
# dgb_pool_efficiency_test: FENCED, additive KAT pinning the pool
# EFFICIENCY / REAL-HASHRATE diagnostics arithmetic in pool_efficiency.hpp vs
# the p2pool main.py status-loop oracle (stale_prop = (orphan+doa)/total;
Expand Down
86 changes: 86 additions & 0 deletions src/impl/dgb/test/naughty_propagation_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// dgb NAUGHTY-PROPAGATION ancestor-punishment KAT.
//
// FENCED, additive (no production code touched this slice). Pins
// src/impl/dgb/coin/naughty_propagation.hpp against the p2pool-dgb-scrypt
// data.py:543-549 propagation rule:
// if prev_share_hash and tracker.items[prev_share_hash].naughty:
// self.naughty = 1 + tracker.items[prev_share_hash].naughty
// if self.naughty > 6: self.naughty = 0
//
// Every expectation is hand-derived from the oracle Python (the +1-per-generation
// step and the 6-generation reset clamp), NOT read from the code under test, so
// the KAT is non-circular. The inline body at share_tracker.hpp:561-577 is NOT
// yet rewired (delegation is the byte-identity follow-on). Pure unsigned
// arithmetic -> links only GTest.
// MUST appear in BOTH this dir CMakeLists.txt AND the build.yml --target
// allowlist, or it becomes a #143 NOT_BUILT sentinel.

#include <impl/dgb/coin/naughty_propagation.hpp>

#include <cstdint>
#include <optional>

#include <gtest/gtest.h>

using dgb::naughty_child_generation;
using dgb::propagate_naughty_from_parent;

// --- bare child generation = 1 + parent, clamp >6 -> 0 (data.py:547-549) ----
// Hand-derived table over every reachable parent generation. Parent naughty in
// the live chain is always in [1,6] (it was itself produced by this same rule),
// but we pin 7 too as a defensive over-the-clamp input.
TEST(DgbNaughtyPropagation, ChildGenerationTable)
{
// parent gen 1 (the bad-reward share itself) -> child gen 2
EXPECT_EQ(naughty_child_generation(1u), 2u);
EXPECT_EQ(naughty_child_generation(2u), 3u);
EXPECT_EQ(naughty_child_generation(3u), 4u);
EXPECT_EQ(naughty_child_generation(4u), 5u);
// gen 5 -> 6 is the LAST kept generation (6 is not > 6)
EXPECT_EQ(naughty_child_generation(5u), 6u);
// gen 6 -> would-be 7, clamps to 0: the 6th-generation descendant is forgiven
EXPECT_EQ(naughty_child_generation(6u), 0u);
// defensive: an out-of-range parent (7) also wraps to 0 (8 > 6)
EXPECT_EQ(naughty_child_generation(7u), 0u);
}

// --- the guarded propagation: nullopt when parent not naughty (data.py:543) --
TEST(DgbNaughtyPropagation, NonNaughtyParentLeavesChildUntouched)
{
// parent.naughty == 0 is falsy in Python -> no assignment happens.
EXPECT_FALSE(propagate_naughty_from_parent(0u).has_value());
}

// --- guarded propagation matches the bare table for naughty parents ---------
TEST(DgbNaughtyPropagation, NaughtyParentPropagates)
{
EXPECT_EQ(propagate_naughty_from_parent(1u), std::optional<std::uint32_t>(2u));
EXPECT_EQ(propagate_naughty_from_parent(2u), std::optional<std::uint32_t>(3u));
EXPECT_EQ(propagate_naughty_from_parent(3u), std::optional<std::uint32_t>(4u));
EXPECT_EQ(propagate_naughty_from_parent(4u), std::optional<std::uint32_t>(5u));
EXPECT_EQ(propagate_naughty_from_parent(5u), std::optional<std::uint32_t>(6u));
// 6th-generation descendant forgiven (assigned 0, NOT left untouched)
EXPECT_EQ(propagate_naughty_from_parent(6u), std::optional<std::uint32_t>(0u));
}

// --- non-circular: a full 8-generation chain walk reproduces the oracle ------
// Seed share is itself naughty (gen 1, excessive reward). Walk forward applying
// the rule and compare against the hand-derived sequence the oracle produces:
// 1 (seed, bad reward), then each child = clamp(1+parent):
// 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 0(forgiven) -> 0(parent 0 = clean, untouched)
TEST(DgbNaughtyPropagation, EightGenerationChainMatchesOracleSequence)
{
const std::uint32_t expected[8] = {1u, 2u, 3u, 4u, 5u, 6u, 0u, 0u};

std::uint32_t gen = 1u; // seed: excessive-reward share, naughty=1
EXPECT_EQ(gen, expected[0]);
for (int i = 1; i < 8; ++i) {
auto next = propagate_naughty_from_parent(gen);
if (next.has_value()) {
gen = *next; // parent was naughty -> inherit/clamp
} else {
gen = 0u; // parent clean -> child stays at its default 0
}
EXPECT_EQ(gen, expected[i]) << "generation " << i;
}
}
Loading