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_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 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 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_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 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 test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j$(nproc)

Expand Down
96 changes: 96 additions & 0 deletions src/impl/dgb/coin/binomial_conf_interval.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#pragma once
// ---------------------------------------------------------------------------
// dgb::coin::binomial_conf_interval -- FENCED, additive SSOT lift of the
// p2pool util/math.py:133 binomial_conf_interval (Wilson score interval) and
// its ierf/erf helpers. This is the stale-rate / dead-share CONFIDENCE-INTERVAL
// display arithmetic the status loop renders (main.py:406/425/426 via
// format_binomial_conf). DIAGNOSTICS ONLY -- it touches no PoW, share format,
// sharechain rule, PPLNS math, or block submission, so the p2pool-merged-v36
// surface is NONE. No call site is rewired by this slice (the byte-identity
// delegation, if ever wanted, is a separate follow-on); this header is the
// dormant SSOT + its KAT.
//
// FAITHFULNESS NOTE: the oracle does NOT use libm erf. It ships its own
// Abramowitz & Stegun 7.1.26 polynomial approximation (util/math.py:100) and
// ierf Newton-iterates ON THAT approximation (10 steps from 0, true-erf
// derivative). Porting std::erf instead would shift ierf and break byte-parity
// with the oracle, so erf_as7126 below reproduces the oracle polynomial exactly.
//
// Per-coin isolation: src/impl/dgb/ only. Pure header (<cmath>/<array>) -- no
// core/share/tracker translation unit pulled in.
// ---------------------------------------------------------------------------

#include <array>
#include <cmath>
#include <algorithm>

namespace dgb {
namespace coin {

// Oracle erf -- Abramowitz & Stegun formula 7.1.26 (util/math.py:100).
// erf(-x) = -erf(x) handled via sign capture, exactly as the oracle.
inline double erf_as7126(double x)
{
int sign = 1;
if (x < 0) sign = -1;
x = std::fabs(x);

const double a1 = 0.254829592;
const double a2 = -0.284496736;
const double a3 = 1.421413741;
const double a4 = -1.453152027;
const double a5 = 1.061405429;
const double p = 0.3275911;

const double t = 1.0 / (1.0 + p * x);
const double y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1)
* t * std::exp(-x * x);
return sign * y;
}

// Oracle ierf (util/math.py:130) -- Newton root-find of erf(x) - z, 10 steps
// from guess 0, no bounds. find_root step: guess -= (erf(guess)-z)/erf'(guess),
// with erf'(x) = 2 e^{-x^2} / sqrt(pi) (the TRUE derivative, matching the
// oracle's analytic dy term even though erf itself is the approximation above).
inline double ierf(double z)
{
double guess = 0.0;
for (int i = 0; i < 10; ++i) {
const double dy = 2.0 * std::exp(-guess * guess) / std::sqrt(M_PI);
const double y_over_dy = (erf_as7126(guess) - z) / dy;
const double prev = guess;
guess = guess - y_over_dy;
if (guess == prev) break;
}
return guess;
}

// Wilson score interval (util/math.py:133), n > 0 path. Returns {left, right},
// each clipped to [0,1], with the interval EXTENDED to include the point
// estimate p = x/n (oracle add_to_range, util/math.py:48). PRECONDITION n > 0:
// the oracle's n == 0 branch seeds a random.random() placeholder window of
// width `conf` -- a non-deterministic display sentinel handled by the caller,
// deliberately NOT reproduced here (a pure function cannot mirror it).
inline std::array<double, 2> binomial_conf_interval(double x, double n,
double conf = 0.95)
{
const double z = std::sqrt(2.0) * ierf(conf);
const double p = x / n;
const double topa = p + z * z / 2.0 / n;
const double topb = z * std::sqrt(p * (1.0 - p) / n + z * z / 4.0 / (n * n));
const double bottom = 1.0 + z * z / n;

double lo = (topa - topb) / bottom;
double hi = (topa + topb) / bottom;

// add_to_range(p, [lo, hi]) = (min(lo, p), max(hi, p)): pull the endpoints
// out to always bracket the point estimate before clipping.
lo = std::min(lo, p);
hi = std::max(hi, p);

const auto clip01 = [](double v) { return std::max(0.0, std::min(1.0, v)); };
return { clip01(lo), clip01(hi) };
}

} // namespace coin
} // namespace dgb
12 changes: 12 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,18 @@ if (BUILD_TESTING AND GTest_FOUND)
GTest::gtest_main GTest::gtest
core c2pool_hashrate c2pool_merged_mining c2pool_payout c2pool_storage)
gtest_add_tests(dgb_pool_attempts_per_second_test "" AUTO)
# dgb_binomial_conf_interval_test: FENCED, additive KAT pinning the pure
# Wilson-score confidence-interval arithmetic of p2pool util/math.py:133
# binomial_conf_interval (+ ierf/erf A&S-7.1.26 helpers) lifted into
# coin/binomial_conf_interval.hpp. DIAGNOSTICS-ONLY stale-rate display math
# (main.py format_binomial_conf), no consensus surface; golden endpoints are
# non-circular Python-oracle output. Pure header (<cmath>/<array>) -> links
# only GTest. No call site rewired. MUST also be in the build.yml --target
# allowlist (#143 NOT_BUILT trap).
add_executable(dgb_binomial_conf_interval_test binomial_conf_interval_test.cpp)
target_link_libraries(dgb_binomial_conf_interval_test PRIVATE
GTest::gtest_main GTest::gtest)
gtest_add_tests(dgb_binomial_conf_interval_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
91 changes: 91 additions & 0 deletions src/impl/dgb/test/binomial_conf_interval_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// ---------------------------------------------------------------------------
// dgb_binomial_conf_interval_test -- FENCED, additive KAT pinning the pure
// arithmetic core of the p2pool util/math.py binomial_conf_interval (Wilson
// score interval) lifted into coin/binomial_conf_interval.hpp. The expected
// interval endpoints are NON-CIRCULAR golden values captured by running the
// actual Python oracle (util/math.py:133, with its A&S-7.1.26 erf + 10-step
// Newton ierf) on frstrtr/p2pool-dgb-scrypt -- they are NOT recomputed from
// the C++ port. Diagnostics-only display math; no consensus surface.
// ---------------------------------------------------------------------------

#include <gtest/gtest.h>
#include "../coin/binomial_conf_interval.hpp"

using dgb::coin::binomial_conf_interval;
using dgb::coin::ierf;

// Tight tolerance: a faithful double-precision port of the same operations in
// the same order should match the oracle to well under 1e-9.
static constexpr double kEps = 1e-9;

// z = sqrt(2)*ierf(0.95) must reproduce the canonical 1.95996 two-sided 95%
// normal quantile -- a non-circular cross-check the ierf/erf port is correct.
TEST(BinomialConfInterval, IerfZScore95MatchesNormalQuantile)
{
const double z = std::sqrt(2.0) * ierf(0.95);
EXPECT_NEAR(z, 1.9599628032746732, kEps);
}

TEST(BinomialConfInterval, IerfZScore6826MatchesOneSigma)
{
const double z = std::sqrt(2.0) * ierf(0.6826);
EXPECT_NEAR(z, 0.9998151342295566, kEps);
}

// --- Golden interval endpoints straight from the Python oracle --------------
TEST(BinomialConfInterval, Oracle_5_of_100_95)
{
auto r = binomial_conf_interval(5.0, 100.0, 0.95);
EXPECT_NEAR(r[0], 0.021543689574590505, kEps);
EXPECT_NEAR(r[1], 0.111750420163752220, kEps);
}

// Zero successes: lower endpoint clips to exactly 0, point estimate p=0 is
// inside the (add_to_range-extended) interval.
TEST(BinomialConfInterval, Oracle_0_of_100_95_ClipsLowerToZero)
{
auto r = binomial_conf_interval(0.0, 100.0, 0.95);
EXPECT_DOUBLE_EQ(r[0], 0.0);
EXPECT_NEAR(r[1], 0.036993455264825250, kEps);
}

// Symmetric case p=0.5: interval centred on 0.5.
TEST(BinomialConfInterval, Oracle_50_of_100_95_Symmetric)
{
auto r = binomial_conf_interval(50.0, 100.0, 0.95);
EXPECT_NEAR(r[0], 0.403831586182331560, kEps);
EXPECT_NEAR(r[1], 0.596168413817668400, kEps);
// Symmetry about 0.5 is the structural invariant of p=0.5.
EXPECT_NEAR((r[0] + r[1]) / 2.0, 0.5, kEps);
}

// Different confidence level (0.6826 ~ 1 sigma) exercises a distinct ierf root.
TEST(BinomialConfInterval, Oracle_10_of_50_6826)
{
auto r = binomial_conf_interval(10.0, 50.0, 0.6826);
EXPECT_NEAR(r[0], 0.149571297975416520, kEps);
EXPECT_NEAR(r[1], 0.262189143777230850, kEps);
}

// Small-rate, large-n tail.
TEST(BinomialConfInterval, Oracle_3_of_1000_95)
{
auto r = binomial_conf_interval(3.0, 1000.0, 0.95);
EXPECT_NEAR(r[0], 0.001020784487127052, kEps);
EXPECT_NEAR(r[1], 0.008783008879983123, kEps);
}

// --- Structural invariants (non-golden, hold for every valid input) ---------
TEST(BinomialConfInterval, BracketsPointEstimateAndStaysInUnit)
{
for (double x : {0.0, 1.0, 7.0, 33.0, 99.0}) {
const double n = 100.0;
auto r = binomial_conf_interval(x, n, 0.95);
const double p = x / n;
EXPECT_GE(p + kEps, r[0]) << "left must not exceed point estimate, x=" << x;
EXPECT_LE(p - kEps, r[1]) << "right must not be below point estimate, x=" << x;
EXPECT_GE(r[0], 0.0);
EXPECT_LE(r[1], 1.0);
EXPECT_LE(r[0], r[1]);
}
}
Loading