|
| 1 | +// dgb_template_builder_test — guards the dgb::coin::build_work_template SSOT |
| 2 | +// (Stage 4c extraction). The work-template assembly was lifted out of |
| 3 | +// DGBWorkSource::get_current_work_template() into coin/template_builder.hpp so |
| 4 | +// the stratum work source and the embedded path emit ONE template object and |
| 5 | +// cannot diverge. These tests pin the truthfulness invariants the assembly |
| 6 | +// must hold: the Scrypt lane is pinned, the consensus coinbasevalue passes |
| 7 | +// through verbatim, mintime tracks median-time-past (0 on an empty chain), |
| 8 | +// transactions[] stays empty, bits is never emitted, and previousblockhash is |
| 9 | +// a truthful conditional (present only when a real tip hash is supplied). |
| 10 | +// |
| 11 | +// Pure-shaping function -> guard-weight test (no chain/mempool fixture). |
| 12 | + |
| 13 | +#include <cstdint> |
| 14 | +#include <limits> |
| 15 | +#include <optional> |
| 16 | +#include <string> |
| 17 | + |
| 18 | +#include <gtest/gtest.h> |
| 19 | +#include <nlohmann/json.hpp> |
| 20 | + |
| 21 | +#include <impl/dgb/coin/template_builder.hpp> |
| 22 | +#include <impl/dgb/coin/dgb_block_algo.hpp> |
| 23 | + |
| 24 | +using dgb::coin::WorkTemplateInputs; |
| 25 | +using dgb::coin::build_work_template; |
| 26 | + |
| 27 | +namespace { |
| 28 | + |
| 29 | +// A fully-populated set of inputs mirroring a mid-chain template build. |
| 30 | +WorkTemplateInputs make_inputs() |
| 31 | +{ |
| 32 | + WorkTemplateInputs in; |
| 33 | + in.next_height = 17722; |
| 34 | + in.coinbasevalue = 625000000ULL; // arbitrary post-resolve reward |
| 35 | + in.median_time_past = 1750000000; // a concrete MTP |
| 36 | + in.curtime = 1750000075; // MTP + ~one block period |
| 37 | + in.previousblockhash = |
| 38 | + "00000000000000000a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778"; |
| 39 | + return in; |
| 40 | +} |
| 41 | + |
| 42 | +} // namespace |
| 43 | + |
| 44 | +// version MUST be BIP9 base OR'd with the all-zero Scrypt algo nibble. A DGB |
| 45 | +// template that is not Scrypt-pinned would be a block this V36 binary never mines. |
| 46 | +TEST(DgbTemplateBuilder, ScryptLanePinnedVersion) |
| 47 | +{ |
| 48 | + auto t = build_work_template(make_inputs()); |
| 49 | + static constexpr uint32_t BIP9_BASE = 0x20000000u; |
| 50 | + const uint32_t expect = |
| 51 | + BIP9_BASE | static_cast<uint32_t>(dgb::coin::DGB_BLOCK_VERSION_SCRYPT); |
| 52 | + EXPECT_EQ(t.at("version").get<uint32_t>(), expect); |
| 53 | + EXPECT_EQ(expect, 0x20000000u); // Scrypt == all-zero codepoint |
| 54 | + // The version nibble must classify back to Scrypt through the SSOT. |
| 55 | + EXPECT_TRUE(dgb::coin::is_scrypt_header(t.at("version").get<int32_t>())); |
| 56 | +} |
| 57 | + |
| 58 | +// The consensus-bearing reward is resolved upstream (#207 SSOT) and must pass |
| 59 | +// through the builder byte-for-byte — the builder never recomputes or scales it. |
| 60 | +TEST(DgbTemplateBuilder, CoinbaseValuePassesThroughVerbatim) |
| 61 | +{ |
| 62 | + auto in = make_inputs(); |
| 63 | + in.coinbasevalue = 0xCAFEBABEDEADBEEFULL; |
| 64 | + auto t = build_work_template(in); |
| 65 | + EXPECT_EQ(t.at("coinbasevalue").get<uint64_t>(), 0xCAFEBABEDEADBEEFULL); |
| 66 | + EXPECT_EQ(t.at("height").get<uint32_t>(), in.next_height); |
| 67 | +} |
| 68 | + |
| 69 | +// mintime = median_time_past + 1 (DGB Core's nTime > MTP lower bound). |
| 70 | +TEST(DgbTemplateBuilder, MintimeIsMtpPlusOne) |
| 71 | +{ |
| 72 | + auto in = make_inputs(); |
| 73 | + in.median_time_past = 1750000000; |
| 74 | + auto t = build_work_template(in); |
| 75 | + EXPECT_EQ(t.at("mintime").get<int64_t>(), 1750000001); |
| 76 | + EXPECT_EQ(t.at("curtime").get<int64_t>(), in.curtime); |
| 77 | +} |
| 78 | + |
| 79 | +// Empty chain reports MTP == INT64_MIN (unconstrained) -> mintime emits 0, |
| 80 | +// never INT64_MIN+1 (which would underflow-adjacent and be meaningless to GBT). |
| 81 | +TEST(DgbTemplateBuilder, EmptyChainMintimeIsZero) |
| 82 | +{ |
| 83 | + auto in = make_inputs(); |
| 84 | + in.median_time_past = std::numeric_limits<int64_t>::min(); |
| 85 | + auto t = build_work_template(in); |
| 86 | + EXPECT_EQ(t.at("mintime").get<int64_t>(), 0); |
| 87 | +} |
| 88 | + |
| 89 | +// transactions[] is always an empty array (embedded tx selection unwired); |
| 90 | +// bits is NEVER emitted (MultiShield V4 next-target is V37 — a Scrypt-only |
| 91 | +// walk would be a known-wrong difficulty). |
| 92 | +TEST(DgbTemplateBuilder, EmptyTransactionsAndNoBits) |
| 93 | +{ |
| 94 | + auto t = build_work_template(make_inputs()); |
| 95 | + ASSERT_TRUE(t.at("transactions").is_array()); |
| 96 | + EXPECT_TRUE(t.at("transactions").empty()); |
| 97 | + EXPECT_FALSE(t.contains("bits")); |
| 98 | +} |
| 99 | + |
| 100 | +// previousblockhash is a truthful conditional: emitted verbatim when a real |
| 101 | +// tip hash is supplied, omitted entirely when absent (never a fabricated id). |
| 102 | +TEST(DgbTemplateBuilder, PreviousblockhashConditionalEmit) |
| 103 | +{ |
| 104 | + auto with = build_work_template(make_inputs()); |
| 105 | + ASSERT_TRUE(with.contains("previousblockhash")); |
| 106 | + EXPECT_EQ(with.at("previousblockhash").get<std::string>(), |
| 107 | + make_inputs().previousblockhash.value()); |
| 108 | + |
| 109 | + auto in = make_inputs(); |
| 110 | + in.previousblockhash = std::nullopt; |
| 111 | + auto without = build_work_template(in); |
| 112 | + EXPECT_FALSE(without.contains("previousblockhash")); |
| 113 | +} |
| 114 | + |
| 115 | +// Divergence guard: identical inputs -> byte-identical template. This is the |
| 116 | +// whole point of the SSOT — the work source and embedded path cannot diverge. |
| 117 | +TEST(DgbTemplateBuilder, DeterministicForIdenticalInputs) |
| 118 | +{ |
| 119 | + EXPECT_EQ(build_work_template(make_inputs()).dump(), |
| 120 | + build_work_template(make_inputs()).dump()); |
| 121 | +} |
0 commit comments