Skip to content

Commit c2007d9

Browse files
committed
dash: wire version-transition gate into a src/ accept seam (chain_admit.hpp)
The dash version-transition gate (verify_version_transition) was fully KAT-proven but had ZERO src/ consumers — its only callers lived in test/test_dash_conformance.cpp, so the mint<->accept coupling was dead in prod (btc/dgb carry it live via their node.cpp accept paths). Dash has no accept orchestrator yet, so the gate had nowhere to be called from. Add src/impl/dash/chain_admit.hpp — the first src/ consumer seam: - admit_chain_relative(): step-2-only chain-relative version gate, decoupled from the CPU-bound structural verify (the dgb node.cpp parallel-phase pattern), ready for the S8 node run-loop to call. - admit_share(): canonical single-call accept composing share_init_verify (step 1, structural + X11 PoW) then admit_chain_relative (step 2). Header-only, additive, dash-fenced. Behavioural floors (60% weighted successor / 95% weighted v36 obsolescence) stay pinned by the existing version_negotiation + DashConformanceVersionWiring KATs; 5 new DashChainAdmitSeam cases lock the consumer boundary. 11/11 green Linux x86_64. Live run-loop call site rides the S8 node.cpp slice.
1 parent 7f02492 commit c2007d9

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

src/impl/dash/chain_admit.hpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma once
2+
3+
// Dash chain-relative share admission seam.
4+
//
5+
// THE PROBLEM THIS CLOSES: dash::verify_version_transition (share_check.hpp) is
6+
// the mint<->accept version-coupling gate, fully KAT-proven in isolation — but
7+
// before this header it had ZERO src/ consumers (the only callers lived in
8+
// test/test_dash_conformance.cpp). btc/dgb carry this coupling LIVE: dgb's
9+
// src/impl/dgb/node.cpp runs share_init_verify() in a parallel phase and then
10+
// applies the chain-relative gates serially as each share is admitted to the
11+
// tracker. Dash had no accept orchestrator at all, so the gate was dead in
12+
// prod. This header is the FIRST src/ consumer: the single, named seam the S8
13+
// dash node run-loop / launcher --run share-receive path calls per incoming
14+
// share. The behavioural thresholds (60% weighted successor / 95% weighted v36
15+
// obsolescence) are pinned by the version_negotiation isolation KATs and the
16+
// DashConformanceVersionWiring wired-path KATs; this seam locks the WIRING
17+
// boundary the node accept path consumes, not the thresholds.
18+
//
19+
// Oracle: ref/p2pool-dash/p2pool/data.py — Share.__init__() (step 1, structural
20+
// + PoW) followed by tracker admission running Share.check() (step 2, the
21+
// chain-relative version guard). Mirrors the cross-coin standard so the v37
22+
// unification is a clean migration, not a per-coin v36 dialect.
23+
24+
#include "share.hpp"
25+
#include "share_check.hpp" // share_init_verify (step 1) + verify_version_transition (step 2)
26+
27+
#include <core/coin_params.hpp>
28+
#include <core/uint256.hpp>
29+
30+
namespace dash
31+
{
32+
33+
// admit_chain_relative — STEP 2 ONLY: the chain-relative version-transition
34+
// gate, decoupled from the CPU-bound structural verify. This is the seam the
35+
// node accept path runs AFTER share_init_verify has been computed (typically in
36+
// a parallel scrypt/X11 phase off the io_context — the dgb node.cpp pattern),
37+
// so the chain admission never re-runs PoW. Throws std::invalid_argument on a
38+
// disallowed version switch; returns normally when the share is admissible.
39+
//
40+
// Extension point: additional chain-relative admission gates (timestamp
41+
// monotonicity, abswork progression, far_share_hash anchoring) compose HERE as
42+
// they are conformance-proven, keeping the node accept path a single call.
43+
template <typename ChainT>
44+
inline void admit_chain_relative(const DashShare& share, ChainT& chain,
45+
uint64_t chain_length)
46+
{
47+
verify_version_transition(share, chain, chain_length);
48+
}
49+
50+
// admit_share — the canonical per-incoming-share admission a Dash node runs in
51+
// its accept path, composing BOTH steps in oracle order:
52+
// step 1 share_init_verify(...) structural + X11 PoW, coin-param-relative
53+
// step 2 admit_chain_relative(...) version mint<->accept coupling, chain-relative
54+
// Returns the verified share hash (the value share_init_verify computes); throws
55+
// std::invalid_argument on any rejected share. Use this single-call form on the
56+
// serial accept path; use share_init_verify + admit_chain_relative separately
57+
// when the structural verify is hoisted into a parallel phase.
58+
template <typename ChainT>
59+
inline uint256 admit_share(const DashShare& share, ChainT& chain,
60+
const core::CoinParams& params, uint64_t chain_length,
61+
bool check_pow = true)
62+
{
63+
uint256 hash = share_init_verify(share, params, check_pow);
64+
admit_chain_relative(share, chain, chain_length);
65+
return hash;
66+
}
67+
68+
} // namespace dash

test/test_dash_conformance.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include <impl/dash/coinbase_builder.hpp> // dash::coinbase::merkle_branches_raw, HexStr
3131
#include <impl/dash/share_check.hpp> // dash::check_merkle_link
32+
#include <impl/dash/chain_admit.hpp> // dash::admit_chain_relative (accept-path seam)
3233
#include <impl/dash/share_types.hpp> // dash::MerkleLink
3334
#include <impl/dash/pplns.hpp> // dash::pplns::compute_payouts, dash::ShareChain, DashShare
3435
#include <impl/dash/version_negotiation.hpp> // dash::version_negotiation::get_desired_version_counts/weights
@@ -1111,6 +1112,70 @@ TEST(DashConformanceVersionWiring, GenesisShareAdmitted) {
11111112
EXPECT_NO_THROW(dash::verify_version_transition(*sc.pool.back(), sc.chain, CL));
11121113
}
11131114

1115+
// ── Accept-path SEAM: dash::admit_chain_relative (chain_admit.hpp) ───────────
1116+
// The DashConformanceVersionWiring group above proves the gate primitive
1117+
// (verify_version_transition). These prove the FIRST src/ CONSUMER seam routes
1118+
// through to that gate: dash::admit_chain_relative — the single call the S8 node
1119+
// run-loop / launcher --run share-receive path makes after share_init_verify.
1120+
// Before chain_admit.hpp the gate had ZERO src/ consumers (test-only). These
1121+
// lock the consumer boundary; the 60%/95% floors stay pinned by the KATs above.
1122+
TEST(DashChainAdmitSeam, AdmitsSameVersionThroughSeam) {
1123+
SyntheticChain sc;
1124+
const uint160 A = miner_h160(0x16);
1125+
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A);
1126+
add_versioned(sc, 0x90, tip, 36, BITS_DIFF1, A);
1127+
EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL));
1128+
}
1129+
1130+
// Seam rejects a stale pre-v36 share once the lookbehind is >=95% weighted v36
1131+
// (routes to the obsolescence arm of the gate).
1132+
TEST(DashChainAdmitSeam, RejectsStalePreV36ThroughSeam) {
1133+
SyntheticChain sc;
1134+
const uint160 A = miner_h160(0x17);
1135+
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A);
1136+
add_versioned(sc, 0x91, tip, 16, BITS_DIFF1, A); // stale v16 on v36 tip
1137+
try {
1138+
dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL);
1139+
FAIL() << "expected seam to reject stale pre-v36 share";
1140+
} catch (const std::invalid_argument& e) {
1141+
EXPECT_NE(std::string(e.what()).find("too old"), std::string::npos);
1142+
}
1143+
}
1144+
1145+
// Seam rejects an upgrade boundary lacking CHAIN_LENGTH history (routes to the
1146+
// successor-guard arm; no-history short-circuit throws "enough history").
1147+
TEST(DashChainAdmitSeam, RejectsUpgradeWithoutHistoryThroughSeam) {
1148+
SyntheticChain sc;
1149+
const uint160 A = miner_h160(0x18);
1150+
uint256 tip = build_uniform(sc, 5, 16, BITS_DIFF1, A); // < CL ancestors
1151+
add_versioned(sc, 0x92, tip, 36, BITS_DIFF1, A); // v16 -> v36 upgrade
1152+
try {
1153+
dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL);
1154+
FAIL() << "expected seam to reject upgrade without enough history";
1155+
} catch (const std::invalid_argument& e) {
1156+
EXPECT_NE(std::string(e.what()).find("enough history"), std::string::npos);
1157+
}
1158+
}
1159+
1160+
// Seam admits an upgrade boundary whose tail window already carries the
1161+
// successor version (>=60% successor votes) — the gate's admit path.
1162+
TEST(DashChainAdmitSeam, AdmitsSuccessorMajorityThroughSeam) {
1163+
SyntheticChain sc;
1164+
const uint160 A = miner_h160(0x19);
1165+
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); // tail = v36
1166+
uint256 prev = add_versioned(sc, 0x93, tip, 16, BITS_DIFF1, A); // lone v16 prev
1167+
add_versioned(sc, 0x94, prev, 36, BITS_DIFF1, A); // v16 -> v36 upgrade
1168+
EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL));
1169+
}
1170+
1171+
// Genesis / no-parent share is admitted unconditionally through the seam.
1172+
TEST(DashChainAdmitSeam, AdmitsGenesisThroughSeam) {
1173+
SyntheticChain sc;
1174+
const uint160 A = miner_h160(0x1a);
1175+
add_versioned(sc, 0x95, uint256(), 36, BITS_DIFF1, A); // null prev_hash
1176+
EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL));
1177+
}
1178+
11141179
// ── DIP4 special-tx coinbase payload (CCbTx) wire-encoding conformance ──────
11151180
// DASH coinbase transactions carry a DIP-0004 "special transaction" extra
11161181
// payload: the CCbTx. Its merkleRootMNList / merkleRootQuorums fields are what

0 commit comments

Comments
 (0)