fork_block_tests.cpp raw

   1  // Copyright (c) 2025 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <test/fork_util.h>
   6  
   7  #include <chain.h>
   8  #include <chainparams.h>
   9  #include <consensus/merkle.h>
  10  #include <consensus/params.h>
  11  #include <consensus/validation.h>
  12  #include <consensus/delay.h>
  13  #include <pow.h>
  14  #include <pow_fork.h>
  15  #include <primitives/block.h>
  16  #include <script/interpreter.h>
  17  #include <script/script.h>
  18  #include <validation.h>
  19  
  20  #include <boost/test/unit_test.hpp>
  21  
  22  BOOST_FIXTURE_TEST_SUITE(fork_block_tests, ForkTestingSetup)
  23  
  24  // ---------------------------------------------------------------------------
  25  // Single-lane DAA: target after block
  26  // ---------------------------------------------------------------------------
  27  
  28  BOOST_AUTO_TEST_CASE(fork_block_daa_single_target)
  29  {
  30      const auto& p = ForkConsensus();
  31      const arith_uint256 seed = UintToArith256(p.powLimit);
  32  
  33      CBlockHeader hdr;
  34      hdr.nVersion = 0;
  35      hdr.nBits = seed.GetCompact();
  36      hdr.nTime = 1632;  // 632s after first block (near 600s target)
  37  
  38      CBlockIndex idx(hdr);
  39      InitForkDAAState(&idx);
  40      idx.nForkLastBlockTime = 1000;
  41  
  42      // Mine one block near target cadence (error ≈ 0).
  43      uint32_t nbits = CalculateForkTarget(&idx, &hdr, p);
  44  
  45      BOOST_CHECK_GT(nbits, 0u);
  46      BOOST_CHECK_GT(idx.nForkTarget, arith_uint256(0));
  47  }
  48  
  49  // ---------------------------------------------------------------------------
  50  // Witness discount removal
  51  // ---------------------------------------------------------------------------
  52  
  53  BOOST_AUTO_TEST_CASE(fork_block_witness_weight)
  54  {
  55      // Build a block with witness data and compare weight with/without
  56      // fork active flag.
  57      CMutableTransaction cb;
  58      cb.vin.resize(1);
  59      cb.vin[0].prevout.SetNull();
  60      cb.vout.resize(1);
  61      cb.vout[0].scriptPubKey = CScript() << OP_TRUE;
  62      cb.vout[0].nValue = 50 * COIN;
  63      cb.vin[0].scriptWitness.stack.push_back(std::vector<uint8_t>(32, 0x42));
  64  
  65      CBlock block;
  66      block.vtx.push_back(MakeTransactionRef(cb));
  67      block.nVersion = 0;
  68      block.nTime = 1000;
  69      block.hashMerkleRoot = BlockMerkleRoot(block);
  70  
  71      int64_t w_fork = GetBlockWeight(block, true);
  72      int64_t w_std  = GetBlockWeight(block, false);
  73      // With witness, fork weight should be SMALLER (raw size, no 3x discount).
  74      // Standard weight: stripped*3 + total.  Fork weight: just total.
  75      BOOST_CHECK_LE(w_fork, w_std);
  76  }
  77  
  78  // ---------------------------------------------------------------------------
  79  // P2BPCT tier rejection: script recognition
  80  // ---------------------------------------------------------------------------
  81  
  82  BOOST_AUTO_TEST_CASE(fork_block_p2bpct_script_recognition)
  83  {
  84      // P2BPCT output is witness v4 (OP_4) + 33-byte Pedersen commitment.
  85      CScript p2bpct;
  86      p2bpct << OP_4 << std::vector<uint8_t>(WITNESS_V4_BPCT_SIZE, 0x42);
  87  
  88      int witver; std::vector<uint8_t> witprog;
  89      BOOST_CHECK(p2bpct.IsWitnessProgram(witver, witprog));
  90      BOOST_CHECK_EQUAL(witver, 4);
  91      BOOST_CHECK_EQUAL(witprog.size(), size_t(WITNESS_V4_BPCT_SIZE));
  92  }
  93  
  94  // ---------------------------------------------------------------------------
  95  // Replay protection constants
  96  // ---------------------------------------------------------------------------
  97  
  98  BOOST_AUTO_TEST_CASE(fork_block_replay_constants)
  99  {
 100  }
 101  
 102  // ---------------------------------------------------------------------------
 103  // Fork reward calculation
 104  // ---------------------------------------------------------------------------
 105  
 106  BOOST_AUTO_TEST_CASE(fork_block_reward_calculation)
 107  {
 108      // Time-proportional reward: R(e) = R_full * e / 600.
 109      // At e = 600 the block pays the full pre-halving subsidy.
 110      CAmount r = GetForkBlockSubsidy(600, 0, 210000);
 111      BOOST_CHECK_EQUAL(r, 50 * COIN);
 112      // Early block: half the interval, half the reward.
 113      BOOST_CHECK_EQUAL(GetForkBlockSubsidy(300, 0, 210000), 25 * COIN);
 114      // Late block: uncapped catch-up.
 115      BOOST_CHECK_EQUAL(GetForkBlockSubsidy(1200, 0, 210000), 100 * COIN);
 116  
 117      // Post-halving: after 4 years of aggregate seconds.
 118      int64_t agg = 600LL * 210000 + 1;
 119      CAmount r_pre  = GetForkBlockSubsidy(600, 0, 210000);
 120      CAmount r_post = GetForkBlockSubsidy(600, agg, 210000);
 121      BOOST_CHECK_EQUAL(r_post, r_pre / 2);
 122  }
 123  
 124  // ---------------------------------------------------------------------------
 125  // Delay output coinbase parser
 126  // ---------------------------------------------------------------------------
 127  
 128  BOOST_AUTO_TEST_CASE(fork_block_delay_output_parser)
 129  {
 130      // Create a coinbase with the delay OP_RETURN and verify parsing.
 131      CMutableTransaction cb;
 132      cb.vin.resize(1);
 133      cb.vin[0].prevout.SetNull();
 134  
 135      // Delay output: OP_RETURN <"LD"> <8-byte LE value>
 136      CTxOut delay_out;
 137      delay_out.nValue = 0;
 138      std::vector<uint8_t> delay_magic = {DELAY_MAGIC_BYTE0, DELAY_MAGIC_BYTE1};
 139      std::vector<uint8_t> delay_bytes = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 140      delay_out.scriptPubKey << OP_RETURN << delay_magic << delay_bytes;
 141      cb.vout.push_back(delay_out);
 142  
 143      // Add a regular output after the delay commitment.
 144      cb.vout.push_back(CTxOut(50 * COIN, CScript() << OP_TRUE));
 145  
 146      CTransaction tx(cb);
 147      int idx = GetDelayOutputIndex(tx);
 148      BOOST_CHECK_GE(idx, 0);
 149      BOOST_CHECK_EQUAL(idx, 0);
 150      auto val = GetDelayOutputValue(tx.vout[idx]);
 151      BOOST_CHECK(val.has_value());
 152      BOOST_CHECK_EQUAL(*val, uint64_t(1));
 153  }
 154  
 155  // ---------------------------------------------------------------------------
 156  // Fork block weight: time-proportional payload, no fixed cap
 157  // ---------------------------------------------------------------------------
 158  
 159  BOOST_AUTO_TEST_CASE(fork_block_weight_limit)
 160  {
 161      // Fork blocks have no fixed weight cap (decision 5): the payload
 162      // limit is time-proportional.  The parent 4MW constant remains as
 163      // the base of the payload formula.
 164      BOOST_CHECK_EQUAL(MAX_BLOCK_WEIGHT, 4'000'000);
 165      BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(600, MAX_BLOCK_WEIGHT), 4'000'000);
 166      BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(300, MAX_BLOCK_WEIGHT), 2'000'000);
 167      BOOST_CHECK_EQUAL(GetForkPayloadWeightLimit(1200, MAX_BLOCK_WEIGHT), 8'000'000);
 168  }
 169  
 170  // ---------------------------------------------------------------------------
 171  BOOST_AUTO_TEST_CASE(fork_block_p2bpct_coinbase_exclusion)
 172  {
 173      // A P2BPCT output is witness v4 with a 32-byte program.
 174      CScript p2bpct;
 175      p2bpct << OP_4 << std::vector<uint8_t>(WITNESS_V4_BPCT_SIZE, 0x42);
 176  
 177      int witver; std::vector<uint8_t> witprog;
 178      BOOST_CHECK(p2bpct.IsWitnessProgram(witver, witprog));
 179      BOOST_CHECK_EQUAL(witver, 4);
 180      BOOST_CHECK_EQUAL(witprog.size(), WITNESS_V4_BPCT_SIZE);
 181  
 182      // A delay OP_RETURN output is NOT a witness program, so a coinbase
 183      // carrying the delay commitment + plain outputs is clean.
 184      CTxOut delay_out;
 185      delay_out.nValue = 0;
 186      std::vector<uint8_t> magic = {DELAY_MAGIC_BYTE0, DELAY_MAGIC_BYTE1};
 187      delay_out.scriptPubKey << OP_RETURN << magic << std::vector<uint8_t>(8, 0);
 188      BOOST_CHECK(!delay_out.scriptPubKey.IsWitnessProgram(witver, witprog));
 189  }
 190  
 191  // ---------------------------------------------------------------------------
 192  // Delay computation correctness (reference values computed with Python
 193  // bigints from the same construction)
 194  // ---------------------------------------------------------------------------
 195  
 196  BOOST_AUTO_TEST_CASE(fork_block_delay_computation)
 197  {
 198      // prev_hash = 00 01 02 ... 1f (in-memory bytes 00..1f)
 199      uint256 prev_hash;
 200      for (int i = 0; i < 32; i++) prev_hash.begin()[i] = (uint8_t)i;
 201  
 202      BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 1), uint64_t(0x0407618d7e9d02f7ULL));
 203      BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 10), uint64_t(0x026edfffac915932ULL));
 204      BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 100), uint64_t(0x0525b3de31e9a6d4ULL));
 205      BOOST_CHECK_EQUAL(ComputeDelay(prev_hash, 1024), uint64_t(0x01eb57199ada2befULL));
 206  }
 207  
 208  // ---------------------------------------------------------------------------
 209  // Activation-boundary epoch seeding: the fork continues the parent's
 210  // halving schedule instead of restarting at 50 BTC
 211  // ---------------------------------------------------------------------------
 212  
 213  BOOST_AUTO_TEST_CASE(fork_block_epoch_seeding)
 214  {
 215      // At the activation boundary, ConnectBlock seeds the aggregate
 216      // seconds from chain_height * 600.  At height 966501 (post-4th
 217      // halving) the full-interval subsidy must be 3.125 BTC, matching
 218      // the parent chain's schedule.
 219      const int halving_interval = 210000;
 220      int64_t agg_base = int64_t(966501) * 600;
 221      CAmount sub = GetForkBlockSubsidy(600, agg_base, halving_interval);
 222      BOOST_CHECK_EQUAL(sub, CAmount(312500000)); // 3.125 BTC
 223      // Early block at that height: half interval, half reward.
 224      BOOST_CHECK_EQUAL(GetForkBlockSubsidy(300, agg_base, halving_interval), CAmount(156250000));
 225      // Pre-activation restart (agg=0) would pay 50 BTC - the seed
 226      // prevents exactly this.
 227      BOOST_CHECK_EQUAL(GetForkBlockSubsidy(600, 0, halving_interval), 50 * COIN);
 228  }
 229  
 230  BOOST_AUTO_TEST_SUITE_END()
 231