|
29 | 29 |
|
30 | 30 | #include <impl/dash/coinbase_builder.hpp> // dash::coinbase::merkle_branches_raw, HexStr |
31 | 31 | #include <impl/dash/share_check.hpp> // dash::check_merkle_link |
| 32 | +#include <impl/dash/chain_admit.hpp> // dash::admit_chain_relative (accept-path seam) |
32 | 33 | #include <impl/dash/share_types.hpp> // dash::MerkleLink |
33 | 34 | #include <impl/dash/pplns.hpp> // dash::pplns::compute_payouts, dash::ShareChain, DashShare |
34 | 35 | #include <impl/dash/version_negotiation.hpp> // dash::version_negotiation::get_desired_version_counts/weights |
@@ -1132,6 +1133,70 @@ TEST(DashConformanceVersionWiring, InitPhaseComposedAndRejectsBeforeGate) { |
1132 | 1133 | } |
1133 | 1134 | } |
1134 | 1135 |
|
| 1136 | +// ── Accept-path SEAM: dash::admit_chain_relative (chain_admit.hpp) ─────────── |
| 1137 | +// The DashConformanceVersionWiring group above proves the gate primitive |
| 1138 | +// (verify_version_transition). These prove the FIRST src/ CONSUMER seam routes |
| 1139 | +// through to that gate: dash::admit_chain_relative — the single call the S8 node |
| 1140 | +// run-loop / launcher --run share-receive path makes after share_init_verify. |
| 1141 | +// Before chain_admit.hpp the gate had ZERO src/ consumers (test-only). These |
| 1142 | +// lock the consumer boundary; the 60%/95% floors stay pinned by the KATs above. |
| 1143 | +TEST(DashChainAdmitSeam, AdmitsSameVersionThroughSeam) { |
| 1144 | + SyntheticChain sc; |
| 1145 | + const uint160 A = miner_h160(0x16); |
| 1146 | + uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); |
| 1147 | + add_versioned(sc, 0x90, tip, 36, BITS_DIFF1, A); |
| 1148 | + EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL)); |
| 1149 | +} |
| 1150 | + |
| 1151 | +// Seam rejects a stale pre-v36 share once the lookbehind is >=95% weighted v36 |
| 1152 | +// (routes to the obsolescence arm of the gate). |
| 1153 | +TEST(DashChainAdmitSeam, RejectsStalePreV36ThroughSeam) { |
| 1154 | + SyntheticChain sc; |
| 1155 | + const uint160 A = miner_h160(0x17); |
| 1156 | + uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); |
| 1157 | + add_versioned(sc, 0x91, tip, 16, BITS_DIFF1, A); // stale v16 on v36 tip |
| 1158 | + try { |
| 1159 | + dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL); |
| 1160 | + FAIL() << "expected seam to reject stale pre-v36 share"; |
| 1161 | + } catch (const std::invalid_argument& e) { |
| 1162 | + EXPECT_NE(std::string(e.what()).find("too old"), std::string::npos); |
| 1163 | + } |
| 1164 | +} |
| 1165 | + |
| 1166 | +// Seam rejects an upgrade boundary lacking CHAIN_LENGTH history (routes to the |
| 1167 | +// successor-guard arm; no-history short-circuit throws "enough history"). |
| 1168 | +TEST(DashChainAdmitSeam, RejectsUpgradeWithoutHistoryThroughSeam) { |
| 1169 | + SyntheticChain sc; |
| 1170 | + const uint160 A = miner_h160(0x18); |
| 1171 | + uint256 tip = build_uniform(sc, 5, 16, BITS_DIFF1, A); // < CL ancestors |
| 1172 | + add_versioned(sc, 0x92, tip, 36, BITS_DIFF1, A); // v16 -> v36 upgrade |
| 1173 | + try { |
| 1174 | + dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL); |
| 1175 | + FAIL() << "expected seam to reject upgrade without enough history"; |
| 1176 | + } catch (const std::invalid_argument& e) { |
| 1177 | + EXPECT_NE(std::string(e.what()).find("enough history"), std::string::npos); |
| 1178 | + } |
| 1179 | +} |
| 1180 | + |
| 1181 | +// Seam admits an upgrade boundary whose tail window already carries the |
| 1182 | +// successor version (>=60% successor votes) — the gate's admit path. |
| 1183 | +TEST(DashChainAdmitSeam, AdmitsSuccessorMajorityThroughSeam) { |
| 1184 | + SyntheticChain sc; |
| 1185 | + const uint160 A = miner_h160(0x19); |
| 1186 | + uint256 tip = build_uniform(sc, 22, 36, BITS_DIFF1, A); // tail = v36 |
| 1187 | + uint256 prev = add_versioned(sc, 0x93, tip, 16, BITS_DIFF1, A); // lone v16 prev |
| 1188 | + add_versioned(sc, 0x94, prev, 36, BITS_DIFF1, A); // v16 -> v36 upgrade |
| 1189 | + EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL)); |
| 1190 | +} |
| 1191 | + |
| 1192 | +// Genesis / no-parent share is admitted unconditionally through the seam. |
| 1193 | +TEST(DashChainAdmitSeam, AdmitsGenesisThroughSeam) { |
| 1194 | + SyntheticChain sc; |
| 1195 | + const uint160 A = miner_h160(0x1a); |
| 1196 | + add_versioned(sc, 0x95, uint256(), 36, BITS_DIFF1, A); // null prev_hash |
| 1197 | + EXPECT_NO_THROW(dash::admit_chain_relative(*sc.pool.back(), sc.chain, CL)); |
| 1198 | +} |
| 1199 | + |
1135 | 1200 | // ── DIP4 special-tx coinbase payload (CCbTx) wire-encoding conformance ────── |
1136 | 1201 | // DASH coinbase transactions carry a DIP-0004 "special transaction" extra |
1137 | 1202 | // payload: the CCbTx. Its merkleRootMNList / merkleRootQuorums fields are what |
|
0 commit comments