|
1 | 1 | // dgb_auto_ratchet_sim_test: FENCED, additive rig-free SIM KAT that arms the |
2 | 2 | // DGB G2 staged-migration harness |
3 | | -// (scripts/dgb_g2_ratchet_staged_migration_harness.sh) C2/C3/C4 rows WITHOUT a |
4 | | -// Scrypt rig or a live digibyted parent -- the C++ counterpart of that |
| 3 | +// (scripts/dgb_g2_ratchet_staged_migration_harness.sh) C2/C3/C4/C5 rows WITHOUT |
| 4 | +// a Scrypt rig or a live digibyted parent -- the C++ counterpart of that |
5 | 5 | // harness's --sim-votes path. It exercises the AutoRatchet staged-gate LOGIC |
6 | 6 | // over hand-derived oracle votes: |
7 | 7 | // |
|
10 | 10 | // outrun it (mint-cannot-outrun-accept) -- the property that keeps a |
11 | 11 | // minted V36 boundary share from being rejected by every peer |
12 | 12 | // C4 ratchet state persists across restart (CONFIRMED survives reconstruct) |
| 13 | +// C5 SCRYPT-ONLY / 4-algos-by-continuity posture (project_v36_dgb_scrypt_only): |
| 14 | +// only Scrypt headers are validated; the other algos accept-by-continuity |
| 15 | +// (work-NEUTRAL) and so cannot move the 60%-by-work ratchet accept gate |
13 | 16 | // |
14 | 17 | // DGB ORACLE PIN (NOT the LTC v35 transition): DGB conforms to its OWN oracle |
15 | 18 | // frstrtr/p2pool-dgb-scrypt @22761e7, which mints share VERSION=35 with |
|
21 | 24 | // Consensus surface is NOT mutated. C3 pins the live 60%-by-WORK gate via a |
22 | 25 | // VERBATIM replica of the inline tail-guard expression in |
23 | 26 | // AutoRatchet::get_share_version (auto_ratchet.hpp), localising the gate the |
24 | | -// same non-circular way dgb_auto_ratchet_tail_guard_test does. Test-only / |
| 27 | +// same non-circular way dgb_auto_ratchet_tail_guard_test does. C5 pins the |
| 28 | +// multi-algo disposition against the consensus SSOT coin/dgb_block_algo.hpp |
| 29 | +// (itself guarded vs upstream DigiByte Core by algo_select_test). Test-only / |
25 | 30 | // dgb-tree-local. |
26 | 31 |
|
27 | 32 | #include <gtest/gtest.h> |
28 | 33 |
|
29 | 34 | #include <impl/dgb/auto_ratchet.hpp> // also pulls share_tracker.hpp + config_pool.hpp |
| 35 | +#include <impl/dgb/coin/dgb_block_algo.hpp> // dgb_header_disposition, is_scrypt_header |
30 | 36 | #include <core/uint256.hpp> // uint288 |
31 | 37 |
|
32 | 38 | #include <cstdint> |
|
38 | 44 |
|
39 | 45 | using dgb::AutoRatchet; |
40 | 46 | using dgb::RatchetState; |
| 47 | +using dgb::coin::DgbAlgo; |
| 48 | +using dgb::coin::HeaderDisposition; |
| 49 | +using dgb::coin::dgb_header_disposition; |
| 50 | +using dgb::coin::is_scrypt_header; |
41 | 51 |
|
42 | 52 | namespace { |
43 | 53 |
|
@@ -79,6 +89,28 @@ bool effective_activation(int votes, int total, |
79 | 89 | // V36, carrying `w_target` of `w_total` work. |
80 | 90 | struct Stage { int votes; int total; uint288 w_target; uint288 w_total; }; |
81 | 91 |
|
| 92 | +// One staged-migration block AS SEEN BY THE CROSSING: which algo mined it (DGB |
| 93 | +// nVersion algo bits), whether it votes V36, and how much PoW work it carries. |
| 94 | +// The G2 work-weighted accept gate credits work ONLY for Scrypt blocks; a |
| 95 | +// non-Scrypt block is accept-by-continuity == work-NEUTRAL (coin/header_chain.hpp |
| 96 | +// THIRD INVARIANT), so its work NEVER enters the ratchet tally however it votes. |
| 97 | +struct AlgoBlock { int32_t n_version; bool votes_v36; uint32_t work; }; |
| 98 | + |
| 99 | +// Work this block credits to the V36 accept tally: the Scrypt-only, |
| 100 | +// work-neutral-continuity posture localised for the sim. Mirrors |
| 101 | +// header_credits_work() forwarding to is_scrypt_header(). |
| 102 | +uint288 scrypt_v36_work_credit(const AlgoBlock& b) |
| 103 | +{ |
| 104 | + return (is_scrypt_header(b.n_version) && b.votes_v36) ? uint288(b.work) |
| 105 | + : uint288(0); |
| 106 | +} |
| 107 | +// Work this block credits to the gate denominator: again Scrypt-only, since a |
| 108 | +// continuity header widens neither numerator nor denominator of the tail guard. |
| 109 | +uint288 scrypt_total_work_credit(const AlgoBlock& b) |
| 110 | +{ |
| 111 | + return is_scrypt_header(b.n_version) ? uint288(b.work) : uint288(0); |
| 112 | +} |
| 113 | + |
82 | 114 | } // namespace |
83 | 115 |
|
84 | 116 | // --------------------------------------------------------------------------- |
@@ -226,3 +258,96 @@ TEST(DGB_AutoRatchetSim, C4_FreshNodeMintsBaselineOnBootstrap) |
226 | 258 | EXPECT_EQ(mint, DGB_BASE_VERSION); |
227 | 259 | EXPECT_EQ(vote, DGB_TARGET_VERSION); |
228 | 260 | } |
| 261 | + |
| 262 | +// --------------------------------------------------------------------------- |
| 263 | +// C5 — SCRYPT-ONLY POSTURE PIN (project_v36_dgb_scrypt_only). V36 c2pool-dgb |
| 264 | +// validates the SCRYPT path ONLY; the V36-deferred algos accept-by-continuity; |
| 265 | +// unknown algo bits reject. Mirrors the per-header disposition the harness C5 |
| 266 | +// row exercises against the consensus SSOT coin/dgb_block_algo.hpp (itself |
| 267 | +// guarded vs upstream DigiByte Core block.h by algo_select_test). |
| 268 | +// --------------------------------------------------------------------------- |
| 269 | +TEST(DGB_AutoRatchetSim, C5_ScryptOnlyPostureDisposition) |
| 270 | +{ |
| 271 | + using dgb::coin::DGB_BLOCK_VERSION_SCRYPT; |
| 272 | + using dgb::coin::DGB_BLOCK_VERSION_SHA256D; |
| 273 | + using dgb::coin::DGB_BLOCK_VERSION_GROESTL; |
| 274 | + using dgb::coin::DGB_BLOCK_VERSION_SKEIN; |
| 275 | + using dgb::coin::DGB_BLOCK_VERSION_QUBIT; |
| 276 | + using dgb::coin::DGB_BLOCK_VERSION_ODO; |
| 277 | + |
| 278 | + // Scrypt is the all-zero algo codepoint -> the ONLY validated PoW lane. |
| 279 | + EXPECT_TRUE(is_scrypt_header(DGB_BLOCK_VERSION_SCRYPT)); |
| 280 | + EXPECT_EQ(dgb_header_disposition(DGB_BLOCK_VERSION_SCRYPT), |
| 281 | + HeaderDisposition::VALIDATE_SCRYPT); |
| 282 | + |
| 283 | + // The four V36-deferred algos (SHA256d, Skein, Qubit, Odocrypt) and legacy |
| 284 | + // Groestl accept-by-continuity (work-neutral) -- NOT validate, NOT reject. |
| 285 | + for (int32_t v : { DGB_BLOCK_VERSION_SHA256D, DGB_BLOCK_VERSION_SKEIN, |
| 286 | + DGB_BLOCK_VERSION_QUBIT, DGB_BLOCK_VERSION_ODO, |
| 287 | + DGB_BLOCK_VERSION_GROESTL }) { |
| 288 | + EXPECT_FALSE(is_scrypt_header(v)); |
| 289 | + EXPECT_EQ(dgb_header_disposition(v), |
| 290 | + HeaderDisposition::ACCEPT_BY_CONTINUITY) << "version_bits=" << v; |
| 291 | + } |
| 292 | + |
| 293 | + // Algo nibbles NOT in the DGB map (1/3/5/7/9/15 << 8) reject. Note 7<<8 is |
| 294 | + // unknown: ALGO_ODO==7 in the id space but BLOCK_VERSION_ODO encodes 14<<8. |
| 295 | + for (int32_t sel : { 1, 3, 5, 7, 9, 15 }) { |
| 296 | + EXPECT_EQ(dgb_header_disposition(sel << 8), |
| 297 | + HeaderDisposition::REJECT) << "algo_nibble=" << sel; |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +// --------------------------------------------------------------------------- |
| 302 | +// C5/work-neutral — the load-bearing G2 intersection of the Scrypt-only |
| 303 | +// posture with the #288 accept gate: accept-by-continuity non-Scrypt blocks are |
| 304 | +// WORK-NEUTRAL, so they cannot move the 60%-by-work ratchet accept gate however |
| 305 | +// they vote. A crossing whose V36 vote MAJORITY (>=95% by head-count) is carried |
| 306 | +// by non-Scrypt algos credits ZERO V36 work; the Scrypt work tally still decides |
| 307 | +// the gate. This is the multi-algo analogue of C3_MintCannotOutrunAccept and is |
| 308 | +// what stops a SHA256d/Skein/Qubit/Odo majority from spoofing a Scrypt-sharechain |
| 309 | +// v35 -> v36 activation. |
| 310 | +// --------------------------------------------------------------------------- |
| 311 | +TEST(DGB_AutoRatchetSim, C5_ContinuityVotesCannotOutrunScryptWorkGate) |
| 312 | +{ |
| 313 | + using dgb::coin::DGB_BLOCK_VERSION_SCRYPT; |
| 314 | + using dgb::coin::DGB_BLOCK_VERSION_SHA256D; |
| 315 | + using dgb::coin::DGB_BLOCK_VERSION_SKEIN; |
| 316 | + using dgb::coin::DGB_BLOCK_VERSION_QUBIT; |
| 317 | + using dgb::coin::DGB_BLOCK_VERSION_ODO; |
| 318 | + |
| 319 | + std::vector<AlgoBlock> blocks; |
| 320 | + blocks.push_back({ DGB_BLOCK_VERSION_SCRYPT, /*votes_v36*/false, 1000 }); // lone Scrypt V35 |
| 321 | + const int32_t non_scrypt[4] = { DGB_BLOCK_VERSION_SHA256D, DGB_BLOCK_VERSION_SKEIN, |
| 322 | + DGB_BLOCK_VERSION_QUBIT, DGB_BLOCK_VERSION_ODO }; |
| 323 | + for (int i = 0; i < 19; ++i) |
| 324 | + blocks.push_back({ non_scrypt[i % 4], /*votes_v36*/true, 50 }); // non-Scrypt V36 voters |
| 325 | + |
| 326 | + uint288 w_target(0), w_total(0); |
| 327 | + int v36 = 0, total = 0; |
| 328 | + for (const auto& b : blocks) { |
| 329 | + w_target = w_target + scrypt_v36_work_credit(b); |
| 330 | + w_total = w_total + scrypt_total_work_credit(b); |
| 331 | + if (b.votes_v36) ++v36; |
| 332 | + ++total; |
| 333 | + } |
| 334 | + EXPECT_EQ(total, 20); |
| 335 | + EXPECT_EQ(v36, 19); |
| 336 | + EXPECT_TRUE(count_activates(v36, total)); // 19/20 = 95% -> count gate WOULD fire |
| 337 | + EXPECT_TRUE(w_target == uint288(0)); // but every V36 vote is continuity |
| 338 | + EXPECT_TRUE(w_total == uint288(1000)); // only the lone Scrypt block credits work |
| 339 | + EXPECT_FALSE(inline_tail_ok(w_target, w_total)); // work gate holds |
| 340 | + EXPECT_FALSE(effective_activation(v36, total, w_target, w_total)); // crossing blocked |
| 341 | + |
| 342 | + // Flip the lone Scrypt miner to V36: now 100% of the CREDITED (Scrypt) work |
| 343 | + // is V36 and the work gate passes -- only Scrypt votes ever move the gate. |
| 344 | + blocks[0].votes_v36 = true; |
| 345 | + uint288 w_t2(0), w_all2(0); |
| 346 | + for (const auto& b : blocks) { |
| 347 | + w_t2 = w_t2 + scrypt_v36_work_credit(b); |
| 348 | + w_all2 = w_all2 + scrypt_total_work_credit(b); |
| 349 | + } |
| 350 | + EXPECT_TRUE(w_t2 == uint288(1000)); |
| 351 | + EXPECT_TRUE(w_all2 == uint288(1000)); |
| 352 | + EXPECT_TRUE(inline_tail_ok(w_t2, w_all2)); // Scrypt work now 100% V36 -> gate passes |
| 353 | +} |
0 commit comments