Skip to content

Commit 72a520a

Browse files
authored
Merge pull request #369 from frstrtr/dgb/think-p6-desired-cutoff
dgb(Phase-B): think() Phase-6 desired-shares timestamp cutoff SSOT + KAT
2 parents 37207eb + ac9a5e9 commit 72a520a

4 files changed

Lines changed: 214 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 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 \
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 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 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 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 \
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 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 test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
220220
v37_test \
221221
-j$(nproc)
222222

src/impl/dgb/test/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,4 +632,19 @@ if (BUILD_TESTING AND GTest_FOUND)
632632
dgb_coin pool sharechain)
633633
gtest_add_tests(dgb_think_p1_desired_emit_test "" AUTO)
634634

635+
# dgb_think_p6_desired_cutoff_test: FENCED, additive KAT pinning the think()
636+
# Phase-6 desired-shares timestamp cutoff in think_p6_desired_cutoff.hpp vs
637+
# the p2pool data.py think() tail oracle (min(now,best_ts)-3600 / 24h no-best
638+
# fallback; inclusive keep predicate), plus a non-circular delegation anchor
639+
# vs the verbatim pre-delegation inline cutoff/keep expressions. Pure
640+
# arithmetic -> links only core. MUST also be in the build.yml --target
641+
# allowlist (#143 NOT_BUILT trap).
642+
add_executable(dgb_think_p6_desired_cutoff_test think_p6_desired_cutoff_test.cpp)
643+
target_link_libraries(dgb_think_p6_desired_cutoff_test PRIVATE
644+
GTest::gtest_main GTest::gtest
645+
core dgb
646+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
647+
dgb_coin pool sharechain)
648+
gtest_add_tests(dgb_think_p6_desired_cutoff_test "" AUTO)
649+
635650
endif()
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// dgb think() Phase-6 desired-shares TIMESTAMP CUTOFF conformance KAT.
2+
//
3+
// FENCED, additive (no production code touched this slice). Pins
4+
// src/impl/dgb/think_p6_desired_cutoff.hpp against the p2pool data.py think()
5+
// Phase-6 tail oracle for the two pure decisions:
6+
// timestamp_cutoff = min(now, best_ts) - 3600 if best valid
7+
// = now - 24*60*60 otherwise
8+
// keep(req) = req.max_timestamp >= timestamp_cutoff (inclusive)
9+
//
10+
// Expectations are HAND-DERIVED from the oracle formula, NOT read back from the
11+
// code under test — a conformance KAT that asks its subject for the answer
12+
// passes vacuously. Pure arithmetic: links only core (no chain standup). MUST
13+
// appear in BOTH this dir's CMakeLists.txt AND the build.yml --target allowlist,
14+
// or it becomes a #143 NOT_BUILT sentinel (compiled-out, silently "passing").
15+
16+
#include <impl/dgb/think_p6_desired_cutoff.hpp>
17+
18+
#include <gtest/gtest.h>
19+
20+
#include <cstdint>
21+
22+
namespace {
23+
24+
// A fixed "now" well above the 24h/1h offsets so no uint32 underflow occurs in
25+
// the hand-derived arithmetic below. (1700000000 ~= 2023-11-14 UTC.)
26+
constexpr uint32_t NOW = 1700000000u;
27+
28+
// ---- timestamp_cutoff: no valid best -> 24h fallback window
29+
TEST(ThinkP6DesiredCutoff, NoBestUses24hFallback)
30+
{
31+
// best_ts is ignored on the no-best branch.
32+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(false, 0, NOW),
33+
NOW - 86400u);
34+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(false, NOW + 50000u, NOW),
35+
NOW - 86400u);
36+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(false, 12345u, NOW),
37+
NOW - 86400u);
38+
}
39+
40+
// ---- timestamp_cutoff: valid best, best_ts BEHIND now -> best_ts - 3600
41+
TEST(ThinkP6DesiredCutoff, BestBehindNowUsesBestMinus3600)
42+
{
43+
const uint32_t best_ts = NOW - 7200u; // 2h behind local clock
44+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(true, best_ts, NOW),
45+
best_ts - 3600u); // min(now,best)=best
46+
}
47+
48+
// ---- timestamp_cutoff: valid best, best_ts AHEAD of now -> now - 3600
49+
// min() clamps a best share whose timestamp leads local clock so the cutoff
50+
// can never be pushed into the future.
51+
TEST(ThinkP6DesiredCutoff, BestAheadOfNowClampsToNowMinus3600)
52+
{
53+
const uint32_t best_ts = NOW + 10000u; // best leads local clock
54+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(true, best_ts, NOW),
55+
NOW - 3600u); // min(now,best)=now
56+
}
57+
58+
// ---- timestamp_cutoff: valid best, best_ts == now -> now - 3600 (boundary)
59+
TEST(ThinkP6DesiredCutoff, BestEqualsNowBoundary)
60+
{
61+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(true, NOW, NOW), NOW - 3600u);
62+
}
63+
64+
// ---- keep predicate: inclusive lower bound at the cutoff
65+
TEST(ThinkP6DesiredCutoff, KeepPredicateInclusive)
66+
{
67+
const uint32_t cutoff = NOW - 3600u;
68+
EXPECT_FALSE(dgb::think_p6_passes_cutoff(cutoff - 1u, cutoff)); // stale -> drop
69+
EXPECT_TRUE (dgb::think_p6_passes_cutoff(cutoff, cutoff)); // == cutoff -> keep
70+
EXPECT_TRUE (dgb::think_p6_passes_cutoff(cutoff + 1u, cutoff)); // fresh -> keep
71+
EXPECT_TRUE (dgb::think_p6_passes_cutoff(NOW, cutoff));
72+
EXPECT_FALSE(dgb::think_p6_passes_cutoff(0u, cutoff));
73+
}
74+
75+
// ---- Non-circular delegation proof ---------------------------------------
76+
// When share_tracker.hpp think() Phase 6 is rewired to CALL the SSOT functions
77+
// above, this anchors against drift. It does NOT stand up a ShareTracker;
78+
// instead it re-implements the EXACT pre-delegation inline expressions verbatim
79+
// (copied from the share_tracker.hpp think() body as it stood before this slice)
80+
// and asserts byte-identity to the SSOT across a dense input grid. Independent
81+
// code path => non-circular.
82+
83+
// Verbatim copy of the pre-delegation inline cutoff computation.
84+
// Original (best valid): std::min(static_cast<uint32_t>(now), best_ts) - 3600
85+
// (no best) : now - 24*60*60
86+
inline uint32_t inline_cutoff_verbatim(bool best_valid, uint32_t best_ts,
87+
uint32_t now)
88+
{
89+
uint32_t timestamp_cutoff;
90+
if (best_valid)
91+
timestamp_cutoff = std::min(static_cast<uint32_t>(now), best_ts) - 3600;
92+
else
93+
timestamp_cutoff = static_cast<uint32_t>(now) - 24 * 60 * 60;
94+
return timestamp_cutoff;
95+
}
96+
97+
// Verbatim copy of the pre-delegation inline keep test.
98+
// Original: if (d.max_timestamp >= timestamp_cutoff) keep;
99+
inline bool inline_keep_verbatim(uint32_t max_timestamp, uint32_t cutoff)
100+
{
101+
return max_timestamp >= cutoff;
102+
}
103+
104+
TEST(ThinkP6DesiredCutoff, DelegationMatchesPreDelegationInlineCutoff)
105+
{
106+
for (uint32_t now : {1700000000u, 1234567890u, 90000u}) {
107+
// no-best branch
108+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(false, 0u, now),
109+
inline_cutoff_verbatim(false, 0u, now))
110+
<< "no-best cutoff drift @ now=" << now;
111+
// best-valid branch across a window straddling `now`
112+
for (uint32_t off = 0; off <= 20000u; off += 250u) {
113+
const uint32_t behind = now - off;
114+
const uint32_t ahead = now + off;
115+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(true, behind, now),
116+
inline_cutoff_verbatim(true, behind, now))
117+
<< "best-behind cutoff drift @ now=" << now << " off=" << off;
118+
EXPECT_EQ(dgb::think_p6_timestamp_cutoff(true, ahead, now),
119+
inline_cutoff_verbatim(true, ahead, now))
120+
<< "best-ahead cutoff drift @ now=" << now << " off=" << off;
121+
}
122+
}
123+
}
124+
125+
TEST(ThinkP6DesiredCutoff, DelegationMatchesPreDelegationInlineKeep)
126+
{
127+
const uint32_t cutoff = 1700000000u - 3600u;
128+
for (int32_t delta = -5000; delta <= 5000; delta += 25) {
129+
const uint32_t ts = static_cast<uint32_t>(static_cast<int64_t>(cutoff) + delta);
130+
EXPECT_EQ(dgb::think_p6_passes_cutoff(ts, cutoff),
131+
inline_keep_verbatim(ts, cutoff))
132+
<< "keep drift @ delta=" << delta;
133+
}
134+
}
135+
136+
} // namespace
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#pragma once
2+
3+
// SSOT for the think() Phase-6 desired-shares TIMESTAMP CUTOFF — the two pure
4+
// decisions that govern which outstanding (desired) share requests survive the
5+
// final filter at the end of think():
6+
// (a) the cutoff timestamp itself, derived from the best share (or a 24h
7+
// fallback when there is no valid best), and
8+
// (b) the per-request keep/drop predicate against that cutoff.
9+
//
10+
// These are currently OPEN-CODED inline in share_tracker.hpp think() Phase 6
11+
// (the best-valid vs no-best branch computing timestamp_cutoff, plus the
12+
// `d.max_timestamp >= timestamp_cutoff` keep test in the desired loop). A silent
13+
// drift — wrong fallback window, wrong 3600s back-off, or a flipped comparison —
14+
// would change which shares a c2pool-dgb node keeps requesting during sync, with
15+
// NO compile error: too-loose hammers peers with unanswerable requests until they
16+
// drop us; too-tight starves the chain of legitimately-missing parents. Lifting
17+
// the arithmetic to a single header-only SSOT lets a KAT pin it exactly.
18+
//
19+
// Oracle: p2pool data.py:2360-2374 OkayTracker.think() tail:
20+
// if best is not None:
21+
// timestamp_cutoff = min(int(time.time()), best_share.timestamp) - 3600
22+
// else:
23+
// timestamp_cutoff = int(time.time()) - 24*60*60
24+
// return best, [(peer_addr, hash) for peer_addr, hash, ts, targ in desired
25+
// if ts >= timestamp_cutoff]
26+
//
27+
// Per-coin isolation: dgb/ only. Header-only, additive; this slice does NOT yet
28+
// rewire share_tracker.hpp (that is the byte-identity-fenced delegation
29+
// follow-on) — it pins the cutoff math as free functions so the KAT exercises
30+
// them with no ShareTracker/NodeImpl standup. Consensus-neutral: pure arithmetic,
31+
// no value semantics changed.
32+
33+
#include <algorithm>
34+
#include <cstdint>
35+
36+
namespace dgb {
37+
38+
// The Phase-6 desired-shares timestamp cutoff.
39+
// best_valid == true : min(now, best_ts) - 3600 (1h grace below the best
40+
// share timestamp; min() guards a best share whose timestamp is ahead of
41+
// local clock from pushing the cutoff into the future).
42+
// best_valid == false: now - 24*60*60 (24h fallback window when no
43+
// best share is resolved yet).
44+
// uint32_t arithmetic, byte-identical to the inline share_tracker.hpp formula.
45+
inline uint32_t think_p6_timestamp_cutoff(bool best_valid, uint32_t best_ts,
46+
uint32_t now)
47+
{
48+
if (best_valid)
49+
return std::min(now, best_ts) - 3600u;
50+
return now - 24u * 60u * 60u;
51+
}
52+
53+
// Per-desired-request keep predicate: a request whose max-available timestamp is
54+
// at or after the cutoff is retained (inclusive lower bound), matching the
55+
// p2pool list-comprehension guard `ts >= timestamp_cutoff`.
56+
inline bool think_p6_passes_cutoff(uint32_t max_timestamp, uint32_t cutoff)
57+
{
58+
return max_timestamp >= cutoff;
59+
}
60+
61+
} // namespace dgb

0 commit comments

Comments
 (0)