Skip to content

Commit d290f75

Browse files
authored
Merge pull request #568 from frstrtr/dash/s8-version-gate-wiring
dash(s8): wire version-gate into a real accept entry (close enforcement hole)
2 parents 78ceb3e + 64820f5 commit d290f75

2 files changed

Lines changed: 64 additions & 11 deletions

File tree

src/impl/dash/share_check.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,4 +706,36 @@ inline void verify_version_transition(const DashShare& share, ChainT& chain,
706706
// obsolete) is permitted, matching btc validate_version_switch. No gate.
707707
}
708708

709+
710+
// === verify_share (Dash accept-path COMBINED entry) ==========================
711+
// The single entry a Dash node runs on every incoming share, mirroring
712+
// src/impl/btc/share_check.hpp::verify_share. Composes the two accept phases:
713+
// Phase 1 (init): share_init_verify -- PoW (X11), hash_link, merkle, target.
714+
// CPU-heavy, so a node offloads it to a thread pool (cf.
715+
// src/impl/dgb/node.cpp:356 two-phase split) and passes
716+
// verify_init=false here when Phase 1 already ran.
717+
// Phase 2 (chain-context gate): verify_version_transition -- the share-
718+
// VERSION boundary admit/reject gate. THIS closes the
719+
// enforcement hole: verify_version_transition was KAT-proven
720+
// in isolation but had ZERO accept-path consumers, so the v36
721+
// obsolescence (95% weighted) + successor (60% weighted)
722+
// guards never ran on a real admission path. verify_share is
723+
// that consumer; the wired KAT drives the 7 boundary cases
724+
// through HERE, not the orphan primitive.
725+
// Bucket-2 structural standardization: same compose shape as btc verify_share,
726+
// so the v37 unification is a clean migration, not a per-coin v36 dialect.
727+
// Returns the Phase-1 share hash (null when verify_init=false). The dashd-RPC
728+
// submitblock fallback is unaffected -- this gates SHARE admission, not block
729+
// submission.
730+
template <typename ChainT>
731+
inline uint256 verify_share(const DashShare& share, ChainT& chain,
732+
uint64_t chain_length, const core::CoinParams& params,
733+
bool verify_init = true, bool check_pow = true)
734+
{
735+
uint256 hash;
736+
if (verify_init)
737+
hash = share_init_verify(share, params, check_pow); // Phase 1
738+
verify_version_transition(share, chain, chain_length); // Phase 2 (wired gate)
739+
return hash;
740+
}
709741
} // namespace dash

test/test_dash_conformance.cpp

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,11 +1016,13 @@ TEST(DashConformanceVersionNeg, SwitchClassifier5CaseKat) {
10161016
successor_switch_allowed(clears, 36u)));
10171017
}
10181018

1019-
// === verify_version_transition: the WIRED accept-path gate (not the isolated
1020-
// primitives above). Proves dash::verify_version_transition composes the
1021-
// version_negotiation primitives into admit/reject enforcement at a version
1022-
// boundary -- the mint<->accept coupling that was previously ZERO-consumer on
1023-
// dash. CHAIN_LENGTH (CL) is small (20) so the [9/10..10/10] tail window is
1019+
// === dash::verify_share: the WIRED accept-path entry (not the isolated
1020+
// primitives above, and not the orphan verify_version_transition the S8-prep
1021+
// KAT used). Proves the version-boundary gate fires through the SAME combined
1022+
// entry a node calls -- closing the enforcement hole where the gate had ZERO
1023+
// accept-path consumers. verify_init=false because Phase 1 (X11 PoW/hash_link)
1024+
// runs in the node thread pool; these cases lock the Phase-2 chain-context
1025+
// WIRING. The init-composition case below proves Phase 1 is wired too. CHAIN_LENGTH (CL) is small (20) so the [9/10..10/10] tail window is
10241026
// reachable in a synthetic chain; the exact 60%/95% floors are pinned by the
10251027
// isolation KATs above, so these lock the WIRING, not the thresholds.
10261028
namespace {
@@ -1034,6 +1036,7 @@ uint256 build_uniform(SyntheticChain& sc, int count, uint64_t version,
10341036
return prev;
10351037
}
10361038
constexpr uint64_t CL = 20; // synthetic CHAIN_LENGTH for the gate window
1039+
core::CoinParams kVgParams; // accept-entry param (unused when verify_init=false)
10371040
} // namespace
10381041

10391042
// Same-version share is never a boundary -> always admitted (data.py:382).
@@ -1042,7 +1045,7 @@ TEST(DashConformanceVersionWiring, SameVersionAdmitted) {
10421045
const uint160 A = miner_h160(0x10);
10431046
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A);
10441047
add_versioned(sc, 0x80, tip, 36, BITS_DIFF1, A);
1045-
EXPECT_NO_THROW(dash::verify_version_transition(*sc.pool.back(), sc.chain, CL));
1048+
EXPECT_NO_THROW(dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false));
10461049
}
10471050

10481051
// Upgrade boundary with fewer than CHAIN_LENGTH ancestors -> rejected
@@ -1053,7 +1056,7 @@ TEST(DashConformanceVersionWiring, UpgradeWithoutHistoryRejected) {
10531056
uint256 tip = build_uniform(sc, 5, 16, BITS_DIFF1, A); // < CL ancestors
10541057
add_versioned(sc, 0x81, tip, 36, BITS_DIFF1, A); // v16 -> v36 upgrade
10551058
try {
1056-
dash::verify_version_transition(*sc.pool.back(), sc.chain, CL);
1059+
dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false);
10571060
FAIL() << "expected throw on upgrade without enough history";
10581061
} catch (const std::invalid_argument& e) {
10591062
EXPECT_NE(std::string(e.what()).find("enough history"), std::string::npos);
@@ -1068,7 +1071,7 @@ TEST(DashConformanceVersionWiring, UpgradeWithSuccessorMajorityAdmitted) {
10681071
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); // tail window = v36
10691072
uint256 prev = add_versioned(sc, 0x82, tip, 16, BITS_DIFF1, A); // a lone v16 prev
10701073
add_versioned(sc, 0x83, prev, 36, BITS_DIFF1, A); // v16 -> v36 upgrade
1071-
EXPECT_NO_THROW(dash::verify_version_transition(*sc.pool.back(), sc.chain, CL));
1074+
EXPECT_NO_THROW(dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false));
10721075
}
10731076

10741077
// Upgrade boundary, history present, but the tail window has NO successor votes
@@ -1079,7 +1082,7 @@ TEST(DashConformanceVersionWiring, UpgradeWithoutSuccessorVotesRejected) {
10791082
uint256 tip = build_uniform(sc, 22, 16, BITS_DIFF1, A); // tail window = all v16
10801083
add_versioned(sc, 0x84, tip, 36, BITS_DIFF1, A); // v16 -> v36 upgrade
10811084
try {
1082-
dash::verify_version_transition(*sc.pool.back(), sc.chain, CL);
1085+
dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false);
10831086
FAIL() << "expected throw on upgrade without successor votes";
10841087
} catch (const std::invalid_argument& e) {
10851088
EXPECT_NE(std::string(e.what()).find("hash power upgraded"),
@@ -1096,7 +1099,7 @@ TEST(DashConformanceVersionWiring, StalePreV36ShareRejected) {
10961099
uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); // lookbehind = all v36
10971100
add_versioned(sc, 0x85, tip, 16, BITS_DIFF1, A); // stale v16 on v36 tip
10981101
try {
1099-
dash::verify_version_transition(*sc.pool.back(), sc.chain, CL);
1102+
dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false);
11001103
FAIL() << "expected throw on stale pre-v36 share";
11011104
} catch (const std::invalid_argument& e) {
11021105
EXPECT_NE(std::string(e.what()).find("too old"), std::string::npos);
@@ -1108,7 +1111,25 @@ TEST(DashConformanceVersionWiring, GenesisShareAdmitted) {
11081111
SyntheticChain sc;
11091112
const uint160 A = miner_h160(0x15);
11101113
add_versioned(sc, 0x86, uint256(), 36, BITS_DIFF1, A); // null prev_hash
1111-
EXPECT_NO_THROW(dash::verify_version_transition(*sc.pool.back(), sc.chain, CL));
1114+
EXPECT_NO_THROW(dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/false));
1115+
}
1116+
1117+
// Phase-1 composition: with verify_init=true the combined entry runs
1118+
// share_init_verify FIRST, so a structurally invalid share (empty coinbase) is
1119+
// rejected by verify_share BEFORE the version gate is ever consulted. Proves
1120+
// Phase 1 is wired into the accept entry, not bypassed.
1121+
TEST(DashConformanceVersionWiring, InitPhaseComposedAndRejectsBeforeGate) {
1122+
SyntheticChain sc;
1123+
const uint160 A = miner_h160(0x16);
1124+
add_versioned(sc, 0x87, uint256(), 36, BITS_DIFF1, A); // genesis: gate is a no-op
1125+
// Synthetic shares carry an empty coinbase -> share_init_verify rejects on
1126+
// the size guard. verify_init=true must surface that, proving init runs.
1127+
try {
1128+
dash::verify_share(*sc.pool.back(), sc.chain, CL, kVgParams, /*verify_init=*/true);
1129+
FAIL() << "expected Phase-1 init to reject the empty-coinbase share";
1130+
} catch (const std::invalid_argument& e) {
1131+
EXPECT_NE(std::string(e.what()).find("coinbase"), std::string::npos);
1132+
}
11121133
}
11131134

11141135
// ── DIP4 special-tx coinbase payload (CCbTx) wire-encoding conformance ──────

0 commit comments

Comments
 (0)