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 dgb_naughty_propagation_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_get_height_and_last_endpoints_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 dgb_naughty_propagation_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_get_height_and_last_endpoints_test dgb_naughty_propagation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j$(nproc)

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

// SSOT for the DGB GET_HEIGHT_AND_LAST chain-walk endpoints -- the pure integer
// arithmetic that every consumer of forest.get_height_and_last() applies to the
// resolved (height, last) pair before walking the sharechain. forest returns
//
// (delta.height, delta.tail) [p2pool util/forest.py:171-173]
//
// where `last` (delta.tail) is the hash one past the deepest reachable ancestor,
// or null when the walk bottoms out at a genesis/rooted tail. Two facts about
// that pair drive consensus-bearing windowing across the DGB tree:
//
// 1. The ROOTED-TAIL invariant. A verified head is only fully rooted once it
// has >= REAL_CHAIN_LENGTH ancestors; below that it must still be hanging
// off an unrooted tail (last is None). p2pool asserts this at every window
// entry point:
// data.py:161 assert height >= net.REAL_CHAIN_LENGTH or last is None
// data.py:696 if height < CHAIN_LENGTH + 1 and last is not None: raise
//
// 2. The PPLNS / payout WINDOW DEPTH. The trailing payout walk is clamped to
// the configured chain length and is only live once at least one share is
// in range:
// depth = min(height, REAL_CHAIN_LENGTH) (redistribute.hpp:453)
// active = depth >= 1
// which mirrors the oracle PPLNS bound
// _pplns_max_shares = max(0, min(height, REAL_CHAIN_LENGTH) - 1)
// (see pplns_weight_walk.hpp:96 / p2pool get_chain(best, min(height, CL))).
//
// 3. The MONITOR min-height gate. The pool monitor's diagnostic cycle is a
// no-op until the chain is deep enough to be statistically meaningful:
// if (height < 10) return 0; (pool_monitor.hpp:98)
// Diagnostic-only, but pinned here so a silent drift of the floor is caught.
//
// A silent drift in the REAL_CHAIN_LENGTH clamp, the >=1 activation, or the
// rooted-tail invariant would re-window PPLNS payout with NO compile error --
// diverging operator-facing reward distribution from the p2pool-dgb-scrypt
// reference the V36 master-compat invariant pins.
//
// Oracle: p2pool-dgb-scrypt util/forest.py:171-173 (get_height_and_last) +
// data.py:160-161 (generate_transaction rooted-tail assert) +
// data.py:695-696 (attempt_verify rooted-tail raise) +
// networks/digibyte.py (REAL_CHAIN_LENGTH = 12*60*60//15 = 2880).
//
// Per-coin isolation: dgb/ only. Header-only, additive, free functions over the
// already-resolved (height, last) pair -- the get_delta_to_last skip-list walk
// stays in the forest. This slice does NOT rewire redistribute.hpp / pool_monitor.hpp
// / share_check.hpp -- that is the byte-identity delegation follow-on. The lifted
// bodies are verbatim copies of the inline guards (same int32_t height type,
// same min()/comparison), so the follow-on is provably value-identical.
// Consensus-neutral: pure arithmetic, no value semantics changed.

#include <algorithm>
#include <cstdint>

namespace dgb {

// Minimum sharechain depth before the pool monitor's diagnostic cycle runs.
// p2pool/c2pool pool_monitor.hpp:98 -- diagnostic floor, not consensus.
static constexpr int32_t MONITOR_MIN_HEIGHT = 10;

// ROOTED-TAIL invariant (data.py:161, data.py:696). A resolved head must either
// have at least `real_chain_length` ancestors, or still be hanging off an
// unrooted tail (last is null). Returns true when the invariant holds.
// p2pool: height >= net.REAL_CHAIN_LENGTH or last is None
inline bool rooted_tail_invariant_holds(int32_t height,
bool last_is_null,
int32_t real_chain_length)
{
return height >= real_chain_length || last_is_null;
}

// attempt_verify entry guard (data.py:695-696): a share below CHAIN_LENGTH+1
// height that is NOT on an unrooted tail is malformed and must be rejected.
// Returns true when the share is acceptable for verification at this depth.
// p2pool: if height < CHAIN_LENGTH + 1 and last is not None: raise
inline bool verify_depth_ok(int32_t height, bool last_is_null, int32_t chain_length)
{
return height >= chain_length + 1 || last_is_null;
}

// PPLNS / payout window depth (redistribute.hpp:453). Trailing payout walk is
// clamped to the configured real chain length.
// c2pool: depth = min(height, real_chain_length())
inline int32_t pplns_window_depth(int32_t height, int32_t real_chain_length)
{
return std::min(height, real_chain_length);
}

// PPLNS window activation (redistribute.hpp:454). The payout window is only live
// once at least one share is in range.
// c2pool: if (depth < 1) return; -> active iff depth >= 1
inline bool pplns_window_active(int32_t depth)
{
return depth >= 1;
}

// p2pool PPLNS share-count bound (pplns_weight_walk.hpp:96): the number of
// shares actually contributing to PPLNS weight, one less than the window depth
// (the head itself is excluded), floored at zero.
// p2pool: _pplns_max_shares = max(0, min(height, REAL_CHAIN_LENGTH) - 1)
inline int32_t pplns_max_shares(int32_t height, int32_t real_chain_length)
{
return std::max(0, pplns_window_depth(height, real_chain_length) - 1);
}

// Pool-monitor diagnostic gate (pool_monitor.hpp:98).
// c2pool: if (height < 10) return 0; -> runs iff height >= 10
inline bool monitor_cycle_runs(int32_t height)
{
return height >= MONITOR_MIN_HEIGHT;
}

} // namespace dgb
15 changes: 15 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,21 @@ if (BUILD_TESTING AND GTest_FOUND)
target_link_libraries(dgb_naughty_propagation_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_naughty_propagation_test "" AUTO)
# dgb_get_height_and_last_endpoints_test: FENCED, additive KAT pinning the
# get_height_and_last chain-walk WINDOW arithmetic in
# coin/get_height_and_last_endpoints.hpp vs the p2pool-dgb-scrypt oracle:
# forest.py:171-173 (height,last), data.py:161 / data.py:696 rooted-tail
# invariant, PPLNS window depth = min(height, REAL_CHAIN_LENGTH) /
# _pplns_max_shares = max(0, depth-1), pool_monitor.hpp:98 height>=10 gate.
# redistribute.hpp / pool_monitor.hpp 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_get_height_and_last_endpoints_test get_height_and_last_endpoints_test.cpp)
target_link_libraries(dgb_get_height_and_last_endpoints_test PRIVATE
GTest::gtest_main GTest::gtest
core
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
gtest_add_tests(dgb_get_height_and_last_endpoints_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
114 changes: 114 additions & 0 deletions src/impl/dgb/test/get_height_and_last_endpoints_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// dgb get_height_and_last endpoints — chain-walk window arithmetic KAT.
//
// FENCED, additive (no production code touched this slice). Pins
// src/impl/dgb/coin/get_height_and_last_endpoints.hpp against the
// p2pool-dgb-scrypt oracle that governs every consumer of
// forest.get_height_and_last():
// util/forest.py:171-173 get_height_and_last -> (delta.height, delta.tail)
// data.py:160-161 assert height >= net.REAL_CHAIN_LENGTH or last is None
// data.py:695-696 if height < CHAIN_LENGTH + 1 and last is not None: raise
// pplns window depth = min(height, REAL_CHAIN_LENGTH); _pplns_max_shares = max(0, depth-1)
// pool_monitor.hpp:98 if (height < 10) return 0;
//
// Every expectation is hand-derived from the oracle formula and the DGB net
// constants (REAL_CHAIN_LENGTH = 12*60*60//15 = 2880 mainnet / 400 testnet,
// MONITOR_MIN_HEIGHT = 10), NOT read from the code under test. This header lifts
// only the integer window guards over the already-resolved (height, last) pair;
// the get_delta_to_last skip-list walk stays in the forest. redistribute.hpp /
// pool_monitor.hpp / share_check.hpp are NOT rewired (delegation is the
// byte-identity follow-on). 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/get_height_and_last_endpoints.hpp>

#include <gtest/gtest.h>

using namespace dgb;

static constexpr int32_t CL_MAIN = 2880; // REAL_CHAIN_LENGTH mainnet
static constexpr int32_t CL_TEST = 400; // REAL_CHAIN_LENGTH testnet

// --- ROOTED-TAIL invariant: height >= REAL_CHAIN_LENGTH or last is None -----
TEST(DgbGhalEndpoints, RootedTailInvariant) {
// Fully rooted: deep chain with a concrete tail -> holds regardless of last.
EXPECT_TRUE(rooted_tail_invariant_holds(CL_MAIN, /*last_is_null=*/false, CL_MAIN));
EXPECT_TRUE(rooted_tail_invariant_holds(CL_MAIN + 1, false, CL_MAIN));
// Shallow but unrooted (last == None) -> holds.
EXPECT_TRUE(rooted_tail_invariant_holds(5, /*last_is_null=*/true, CL_MAIN));
EXPECT_TRUE(rooted_tail_invariant_holds(0, true, CL_MAIN));
// Shallow AND rooted (last present) -> VIOLATION (the oracle assert fires).
EXPECT_FALSE(rooted_tail_invariant_holds(5, /*last_is_null=*/false, CL_MAIN));
EXPECT_FALSE(rooted_tail_invariant_holds(CL_MAIN - 1, false, CL_MAIN));
// Boundary: exactly REAL_CHAIN_LENGTH is rooted (>=).
EXPECT_TRUE(rooted_tail_invariant_holds(CL_TEST, false, CL_TEST));
EXPECT_FALSE(rooted_tail_invariant_holds(CL_TEST - 1, false, CL_TEST));
}

// --- attempt_verify entry guard: height >= CL+1 or last is None -------------
TEST(DgbGhalEndpoints, VerifyDepthGuard) {
// data.py:696 raises when (height < CHAIN_LENGTH+1) AND (last is not None).
// ok == NOT(raise).
EXPECT_FALSE(verify_depth_ok(CL_MAIN, /*last_is_null=*/false, CL_MAIN)); // CL < CL+1, rooted -> raise
EXPECT_TRUE(verify_depth_ok(CL_MAIN + 1, false, CL_MAIN)); // height == CL+1 -> ok
EXPECT_TRUE(verify_depth_ok(3, /*last_is_null=*/true, CL_MAIN)); // unrooted -> ok at any depth
EXPECT_FALSE(verify_depth_ok(3, false, CL_MAIN)); // shallow + rooted -> raise
// Boundary at CHAIN_LENGTH+1 exactly (testnet).
EXPECT_TRUE(verify_depth_ok(CL_TEST + 1, false, CL_TEST));
EXPECT_FALSE(verify_depth_ok(CL_TEST, false, CL_TEST));
}

// --- PPLNS window depth = min(height, REAL_CHAIN_LENGTH) ---------------------
TEST(DgbGhalEndpoints, PplnsWindowDepthClamps) {
EXPECT_EQ(pplns_window_depth(100, CL_MAIN), 100); // below clamp -> identity
EXPECT_EQ(pplns_window_depth(CL_MAIN, CL_MAIN), CL_MAIN); // at clamp
EXPECT_EQ(pplns_window_depth(CL_MAIN + 5000, CL_MAIN), CL_MAIN); // above -> clamped
EXPECT_EQ(pplns_window_depth(0, CL_MAIN), 0);
EXPECT_EQ(pplns_window_depth(450, CL_TEST), CL_TEST); // testnet clamp 400
EXPECT_EQ(pplns_window_depth(399, CL_TEST), 399);
}

// --- PPLNS window activation = depth >= 1 -----------------------------------
TEST(DgbGhalEndpoints, PplnsWindowActivation) {
EXPECT_FALSE(pplns_window_active(0)); // redistribute.hpp:454 early-return
EXPECT_TRUE(pplns_window_active(1));
EXPECT_TRUE(pplns_window_active(CL_MAIN));
EXPECT_FALSE(pplns_window_active(-3)); // defensive: never active below 1
}

// --- p2pool _pplns_max_shares = max(0, min(height, REAL_CHAIN_LENGTH) - 1) ---
TEST(DgbGhalEndpoints, PplnsMaxSharesMatchesOracle) {
EXPECT_EQ(pplns_max_shares(0, CL_MAIN), 0); // max(0, 0-1) = 0
EXPECT_EQ(pplns_max_shares(1, CL_MAIN), 0); // max(0, 1-1) = 0
EXPECT_EQ(pplns_max_shares(2, CL_MAIN), 1); // max(0, 2-1) = 1
EXPECT_EQ(pplns_max_shares(100, CL_MAIN), 99);
EXPECT_EQ(pplns_max_shares(CL_MAIN, CL_MAIN), CL_MAIN - 1); // 2879
EXPECT_EQ(pplns_max_shares(CL_MAIN + 1000, CL_MAIN), CL_MAIN - 1); // clamped then -1
EXPECT_EQ(pplns_max_shares(CL_TEST, CL_TEST), CL_TEST - 1); // 399
}

// --- pool-monitor diagnostic gate: height >= 10 -----------------------------
TEST(DgbGhalEndpoints, MonitorCycleGate) {
EXPECT_FALSE(monitor_cycle_runs(0));
EXPECT_FALSE(monitor_cycle_runs(9)); // pool_monitor.hpp:98 boundary -> no-op
EXPECT_TRUE(monitor_cycle_runs(10)); // exactly the floor runs
EXPECT_TRUE(monitor_cycle_runs(2880));
}

// --- non-circular cross-check: window depth, max_shares, and activation are
// mutually consistent across the whole 0..2*CL range (derived purely from
// the oracle min/clamp identities, no code-under-test constants). ---------
TEST(DgbGhalEndpoints, WindowConsistencyNonCircular) {
for (int32_t h = 0; h <= 2 * CL_MAIN; ++h) {
int32_t depth = pplns_window_depth(h, CL_MAIN);
// depth never exceeds the clamp, never below 0
ASSERT_LE(depth, CL_MAIN);
ASSERT_GE(depth, 0);
// expected clamp recomputed independently
int32_t expect_depth = (h < CL_MAIN) ? h : CL_MAIN;
ASSERT_EQ(depth, expect_depth);
// max_shares is exactly depth-1 floored at 0
ASSERT_EQ(pplns_max_shares(h, CL_MAIN), (expect_depth > 0 ? expect_depth - 1 : 0));
// active iff there is at least one share in range
ASSERT_EQ(pplns_window_active(depth), expect_depth >= 1);
}
}
Loading