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 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_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_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_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_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 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_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_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_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j$(nproc)

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

// SSOT for the DGB TAIL-HASHRATE-SCORE chain-walk endpoints -- the pure
// arithmetic core of ShareTracker::score() (the per-tail "approximate lower
// bound on the chain's hashrate" used to rank candidate sharechain tails).
// Given a verified head share it resolves a window
//
// end_point = get_nth_parent_hash(share_hash, CHAIN_LENGTH * 15 / 16)
//
// walks the trailing CHAIN_LENGTH/16 shares to find the most-recent resolvable
// parent block, then scores
//
// score = get_delta(share_hash, end_point).work / (block_span * BLOCK_PERIOD)
//
// This score decides best_tail selection (data.py:767-772) and therefore which
// sharechain the pool extends -- a silent drift in the 15/16 endpoint offset, the
// /16 tail length, the short-chain guard, or the span clamp would re-rank 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:843-855 ShareTracker.score(share_hash,
// block_rel_height_func):
// head_height = self.verified.get_height(share_hash)
// if head_height < self.net.CHAIN_LENGTH:
// return head_height, None
// end_point = self.verified.get_nth_parent_hash(
// share_hash, self.net.CHAIN_LENGTH*15//16)
// block_height = max(block_rel_height_func(share.header['previous_block'])
// for share in self.verified.get_chain(end_point, self.net.CHAIN_LENGTH//16))
// return self.net.CHAIN_LENGTH, \
// self.verified.get_delta(share_hash, end_point).work \
// / ((0 - block_height + 1) * self.net.PARENT.BLOCK_PERIOD)
//
// The chain-walk halves -- locating end_point via get_nth_parent_hash, walking
// get_chain, and summing the work delta via get_delta -- stay inside ShareTracker
// (they need the skip-list / TrackerView). This header captures ONLY the integer
// endpoint offsets, the short-chain guard, the unresolvable-block fallback, and
// the final span-clamp+divide as free functions over already-resolved inputs, so
// a KAT can pin the arithmetic with no NodeImpl / ShareTracker standup.
//
// Confirmation convention: p2pool's block_rel_height_func returns a relative
// height (0 = tip, negative = behind) and scores over (0 - block_height + 1) *
// BLOCK_PERIOD. c2pool's block_rel_height_func returns a CONFIRMATION count
// (1 = tip, 4 = three-deep), so the equivalent span is block_height *
// BLOCK_PERIOD: oracle (0 - 0 + 1) = 1 == c2pool 1 (tip), oracle (0 - -3 + 1) = 4
// == c2pool 4. The mapping is exact; this SSOT works in the c2pool confirmation
// convention to match the inline body verbatim.
//
// 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
// lifted bodies are verbatim copies of the inline arithmetic (same int32_t span
// type, same uint288 divide), so the follow-on is provably value-identical.
// Consensus-neutral: pure arithmetic, no value semantics changed.

#include <algorithm>
#include <cstdint>

#include <core/uint256.hpp> // uint288

namespace dgb {

// Distance to the window end-point: get_nth_parent_hash(share_hash, this).
// Oracle: self.net.CHAIN_LENGTH*15//16 (Python floor division == C++ / on
// non-negative operands). CHAIN_LENGTH is a positive net constant.
inline int32_t score_endpoint_offset(int32_t chain_length)
{
return (chain_length * 15) / 16;
}

// Number of trailing shares walked from end_point to find the most-recent
// resolvable parent block. Oracle: get_chain(end_point, self.net.CHAIN_LENGTH//16).
// The inline body additionally clamps to the end_point's accumulated height so
// the walk never over-reads past chain end (get_chain stops there anyway, but the
// clamp makes the <= 0 short-circuit below exact). end_point_acc_height is
// verified.get_acc_height(end_point).
inline int32_t score_tail_walk_count(int32_t chain_length, int32_t end_point_acc_height)
{
return std::min(chain_length / 16, end_point_acc_height);
}

// Short-chain guard: when the head's accumulated verified height is below a full
// CHAIN_LENGTH the score is undefined (oracle returns (head_height, None)). The
// caller returns {head_acc_height, 0} in that case.
inline bool score_head_too_short(int32_t head_acc_height, int32_t chain_length)
{
return head_acc_height < chain_length;
}

// Resolved block span in the c2pool confirmation convention. When NO parent block
// in the trailing window resolves (block_rel_height_func returned <= 0 for all, or
// the window was empty), p2pool would score work / (1e9 * BLOCK_PERIOD) -- tiny but
// non-zero so the higher-work chain still wins and selection does not oscillate.
// c2pool matches with a very large confirmation count (1,000,000) producing the
// same tiny-but-positive score. raw_block_height is the max confirmation count
// found; resolved is false when no block resolved.
inline int32_t score_resolved_block_span(bool resolved, int32_t raw_block_height)
{
if (!resolved || raw_block_height <= 0)
return 1000000;
return raw_block_height;
}

// Score denominator: block_span * BLOCK_PERIOD, clamped strictly positive so the
// divide is well-defined. Oracle BLOCK_PERIOD = self.net.PARENT.BLOCK_PERIOD
// (config_coin.hpp = 75s). block_period is the make_coin_params-populated value.
inline int32_t score_time_span(int32_t block_span, int32_t block_period)
{
int32_t time_span = block_span * block_period;
if (time_span <= 0)
time_span = 1;
return time_span;
}

// Final hashrate score: cumulative-work delta over the window divided by the
// clamped time span. total_work = verified.get_delta_work(share_hash, end_point).
inline uint288 score_value(const uint288& total_work, int32_t time_span)
{
return total_work / static_cast<uint32_t>(time_span);
}

} // namespace dgb
16 changes: 16 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,22 @@ if (BUILD_TESTING AND GTest_FOUND)
target_link_libraries(dgb_expected_time_to_block_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_expected_time_to_block_test "" AUTO)

# dgb_tail_score_endpoints_test: FENCED, additive KAT pinning the
# TAIL-HASHRATE-SCORE chain-walk endpoint arithmetic in
# coin/tail_score_endpoints.hpp vs the p2pool-dgb-scrypt data.py:843-855
# ShareTracker.score() oracle (end-point offset CHAIN_LENGTH*15//16, trailing
# walk CHAIN_LENGTH//16, short-chain guard, unresolvable-block 1e6 fallback,
# work/(block_span*BLOCK_PERIOD) divide). Pure header (uint288) -> links core.
# share_tracker.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_tail_score_endpoints_test tail_score_endpoints_test.cpp)
target_link_libraries(dgb_tail_score_endpoints_test PRIVATE
GTest::gtest_main GTest::gtest
core
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
gtest_add_tests(dgb_tail_score_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
106 changes: 106 additions & 0 deletions src/impl/dgb/test/tail_score_endpoints_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// dgb tail-hashrate-score endpoints — chain-walk endpoint arithmetic KAT.
//
// FENCED, additive (no production code touched this slice). Pins
// src/impl/dgb/coin/tail_score_endpoints.hpp against the p2pool-dgb-scrypt
// data.py:843-855 ShareTracker.score() oracle:
// head_height = self.verified.get_height(share_hash)
// if head_height < self.net.CHAIN_LENGTH: return head_height, None
// end_point = self.verified.get_nth_parent_hash(share_hash, CHAIN_LENGTH*15//16)
// block_height = max(block_rel_height_func(...) for share in
// self.verified.get_chain(end_point, CHAIN_LENGTH//16))
// return CHAIN_LENGTH, get_delta(share_hash, end_point).work
// / ((0 - block_height + 1) * PARENT.BLOCK_PERIOD)
//
// Every expectation is hand-derived from the oracle formula and the DGB net
// constants (CHAIN_LENGTH = 2880 mainnet / 400 testnet, PARENT.BLOCK_PERIOD =
// 75s), NOT read from the code under test. The chain-walk halves (get_nth_parent_hash,
// get_chain, get_delta) stay in ShareTracker; this header lifts only the integer
// endpoint offsets, the short-chain guard, the unresolvable-block fallback, and
// the span-clamp+divide. share_tracker.hpp is NOT rewired (delegation is the
// byte-identity follow-on). Pure header (uint288) -> links core only.
// 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/tail_score_endpoints.hpp>

#include <core/uint256.hpp> // uint288

#include <gtest/gtest.h>

// --- end-point offset = CHAIN_LENGTH*15//16 (Python floor div) --------------
TEST(DgbTailScoreEndpoints, EndpointOffsetMatchesOracle) {
EXPECT_EQ(dgb::score_endpoint_offset(2880), 2700); // 2880*15/16 = 43200/16
EXPECT_EQ(dgb::score_endpoint_offset(400), 375); // testnet: 400*15/16 = 6000/16
EXPECT_EQ(dgb::score_endpoint_offset(16), 15); // 16*15/16 = 15
EXPECT_EQ(dgb::score_endpoint_offset(32), 30); // 480/16
EXPECT_EQ(dgb::score_endpoint_offset(0), 0);
}

// --- trailing-walk length = min(CHAIN_LENGTH//16, end_point_acc_height) -----
TEST(DgbTailScoreEndpoints, TailWalkCountMatchesOracleAndClamps) {
EXPECT_EQ(dgb::score_tail_walk_count(2880, 5000), 180); // min(180, 5000)
EXPECT_EQ(dgb::score_tail_walk_count(2880, 100), 100); // clamp to acc-height
EXPECT_EQ(dgb::score_tail_walk_count(2880, 180), 180); // exact boundary
EXPECT_EQ(dgb::score_tail_walk_count(400, 5000), 25); // testnet 400/16
EXPECT_EQ(dgb::score_tail_walk_count(2880, 0), 0); // <=0 -> caller early-returns
}

// --- short-chain guard: head_height < CHAIN_LENGTH -> score undefined -------
TEST(DgbTailScoreEndpoints, ShortChainGuardMatchesOracle) {
EXPECT_TRUE (dgb::score_head_too_short(2879, 2880)); // one short of a full chain
EXPECT_FALSE(dgb::score_head_too_short(2880, 2880)); // exactly full -> scored
EXPECT_FALSE(dgb::score_head_too_short(5000, 2880)); // long -> scored
EXPECT_TRUE (dgb::score_head_too_short(399, 400)); // testnet short
EXPECT_FALSE(dgb::score_head_too_short(400, 400)); // testnet full
}

// --- unresolvable-block fallback: 1e6 confirmations (tiny but non-zero) -----
TEST(DgbTailScoreEndpoints, UnresolvableBlockFallbackMatchesOracle) {
EXPECT_EQ(dgb::score_resolved_block_span(true, 4), 4); // resolved, 3-deep
EXPECT_EQ(dgb::score_resolved_block_span(true, 1), 1); // resolved, tip
EXPECT_EQ(dgb::score_resolved_block_span(false, 4), 1000000); // window had none
EXPECT_EQ(dgb::score_resolved_block_span(true, 0), 1000000); // resolved <= 0
EXPECT_EQ(dgb::score_resolved_block_span(true, -3), 1000000); // negative -> fallback
}

// --- time span = block_span * BLOCK_PERIOD, clamped strictly positive -------
TEST(DgbTailScoreEndpoints, TimeSpanMatchesOracleAndClamps) {
EXPECT_EQ(dgb::score_time_span(1, 75), 75); // tip: 1*75
EXPECT_EQ(dgb::score_time_span(4, 75), 300); // 4-deep: 4*75
EXPECT_EQ(dgb::score_time_span(1000000, 75), 75000000); // fallback span
EXPECT_EQ(dgb::score_time_span(0, 75), 1); // clamp <=0
EXPECT_EQ(dgb::score_time_span(1, 0), 1); // zero period -> clamp
}

// --- final score = work / time_span (integer divide) -----------------------
TEST(DgbTailScoreEndpoints, ScoreValueMatchesOracle) {
EXPECT_EQ(dgb::score_value(uint288(300), 75), uint288(4)); // 300/75
EXPECT_EQ(dgb::score_value(uint288(900), 300), uint288(3)); // 900/300
EXPECT_EQ(dgb::score_value(uint288(0), 75), uint288(0)); // no work
EXPECT_EQ(dgb::score_value(uint288(1000000000ULL), 75000000), uint288(13)); // 1e9/75e6
}

// --- non-circular composite: full oracle scoring, hand-derived literals -----
// Case A (resolved, 4-deep tip block): CHAIN_LENGTH=2880, BLOCK_PERIOD=75,
// work=900000, block_height=4 (resolved).
// not short (2880>=2880); span=4; time_span=4*75=300; score=900000/300=3000.
TEST(DgbTailScoreEndpoints, CompositeResolvedScoreHandDerived) {
ASSERT_FALSE(dgb::score_head_too_short(2880, 2880));
int32_t span = dgb::score_resolved_block_span(true, 4); // 4
int32_t time_span = dgb::score_time_span(span, 75); // 300
uint288 score = dgb::score_value(uint288(900000), time_span);
EXPECT_EQ(span, 4);
EXPECT_EQ(time_span, 300);
EXPECT_EQ(score, uint288(3000)); // 900000 / 300, hand-computed
}

// Case B (unresolvable window): work=750000000, no block resolves.
// span=1000000; time_span=1000000*75=75000000; score=750000000/75000000=10.
TEST(DgbTailScoreEndpoints, CompositeUnresolvableScoreHandDerived) {
int32_t span = dgb::score_resolved_block_span(false, 0); // 1000000
int32_t time_span = dgb::score_time_span(span, 75); // 75000000
uint288 score = dgb::score_value(uint288(750000000ULL), time_span);
EXPECT_EQ(span, 1000000);
EXPECT_EQ(time_span, 75000000);
EXPECT_EQ(score, uint288(10)); // tiny but non-zero, hand-computed
}
Loading