pow_fork.cpp raw

   1  // Copyright (c) 2026 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 <pow_fork.h>
   6  
   7  #include <chain.h>
   8  #include <consensus/params.h>
   9  #include <primitives/block.h>
  10  
  11  #include <algorithm>
  12  #include <vector>
  13  
  14  static constexpr int64_t PID_SCALE = 1000000;
  15  static constexpr int64_t DEN_MIN = 100000;
  16  static constexpr int64_t DEN_MAX = 10000000;
  17  static constexpr double   ALPHA = 0.05;       // EMA smoothing factor
  18  
  19  /** Advance the DAA state one block: pure function of the previous state
  20   *  and the block's stamp. */
  21  static ForkDAAState AdvanceDAA(const ForkDAAState& prev, int64_t block_time,
  22                                 const Consensus::Params& p)
  23  {
  24      ForkDAAState next;
  25      const int64_t interval = p.nForkIntervalTarget;
  26  
  27      int64_t actual;
  28      if (prev.nForkLastBlockTime == 0) {
  29          actual = interval;  // first fork block - assume the target interval
  30      } else {
  31          actual = block_time - prev.nForkLastBlockTime;
  32      }
  33  
  34      const int64_t error = interval - actual;
  35      const int64_t avg = int64_t(prev.nForkAvgError * (1.0 - ALPHA) + error * ALPHA);
  36      const int64_t ki = error > 0 ? p.nForkKiUp : p.nForkKiDown;
  37      const int64_t correction = (ki * avg + p.nForkKp * error) / PID_SCALE;
  38  
  39      int64_t denom = PID_SCALE + correction;
  40      denom = std::clamp(denom, DEN_MIN, DEN_MAX);
  41  
  42      next.nForkTarget = prev.nForkTarget * PID_SCALE / denom;
  43      next.nForkAvgError = avg;
  44      next.nForkLastBlockTime = block_time;
  45      return next;
  46  }
  47  
  48  /** Recompute and memoize the fork DAA state (target/avg/lasttime) plus the
  49   *  aggregate-seconds counter for fork-era index entries whose state is
  50   *  missing.  Headers-first sync validates difficulty before blocks are
  51   *  connected, and the state must be a deterministic pure function of the
  52   *  header chain - zero-filled state must never be mistaken for "first fork
  53   *  block" (that made restart and fresh sync compute a different validity
  54   *  function than uptime nodes). */
  55  static void EnsureForkDAAState(CBlockIndex* pindex, const Consensus::Params& p)
  56  {
  57      if (pindex->nForkTarget != arith_uint256(0)) return;
  58  
  59      std::vector<CBlockIndex*> missing;
  60      for (CBlockIndex* w = pindex;
  61           w && w->nForkTarget == arith_uint256(0) && w->pprev && IsForkActive(w->pprev, p);
  62           w = w->pprev) {
  63          missing.push_back(w);
  64      }
  65      for (auto it = missing.rbegin(); it != missing.rend(); ++it) {
  66          CBlockIndex* cur = *it;
  67          const CBlockIndex* pp = cur->pprev;
  68          ForkDAAState prev_state;
  69          if (pp->nForkTarget == arith_uint256(0)) {
  70              // Activation boundary: seed from the parent chain's difficulty.
  71              prev_state.nForkTarget.SetCompact(pp->nBits);
  72          } else {
  73              prev_state.nForkTarget = pp->nForkTarget;
  74              prev_state.nForkAvgError = pp->nForkAvgError;
  75              prev_state.nForkLastBlockTime = pp->nForkLastBlockTime;
  76          }
  77          const ForkDAAState next = AdvanceDAA(prev_state, cur->nTime, p);
  78          cur->nForkTarget = next.nForkTarget;
  79          cur->nForkAvgError = next.nForkAvgError;
  80          cur->nForkLastBlockTime = next.nForkLastBlockTime;
  81  
  82          int64_t e = pp->nForkLastBlockTime != 0
  83              ? cur->nTime - pp->nForkLastBlockTime
  84              : cur->nTime - pp->GetBlockTime();  // first block: actual interval
  85          if (e < 1) e = 1;
  86          const int64_t agg_base = IsForkActive(pp->pprev, p)
  87              ? pp->nForkAggregateSeconds
  88              : int64_t(cur->nHeight) * 600;
  89          cur->nForkAggregateSeconds = agg_base + e;
  90      }
  91  }
  92  
  93  uint32_t CalculateForkTarget(const CBlockIndex* pindexPrev,
  94                                 const CBlockHeader* pblock,
  95                                 const Consensus::Params& p,
  96                                 ForkDAAState* out)
  97  {
  98      // The DAA state of the previous fork block must exist before use:
  99      // recompute it from the activation point when missing (headers-only
 100      // sync, or an index written before the state was persisted).
 101      EnsureForkDAAState(const_cast<CBlockIndex*>(pindexPrev), p);
 102  
 103      ForkDAAState state;
 104      state.nForkTarget = pindexPrev->nForkTarget;
 105      state.nForkAvgError = pindexPrev->nForkAvgError;
 106      state.nForkLastBlockTime = pindexPrev->nForkLastBlockTime;
 107      if (state.nForkTarget == arith_uint256(0)) {
 108          // Activation block: seed from the parent chain's difficulty.
 109          state.nForkTarget.SetCompact(pindexPrev->nBits);
 110      }
 111  
 112      ForkDAAState next = AdvanceDAA(state, pblock->nTime, p);
 113      if (out) *out = next;
 114      return next.nForkTarget.GetCompact();
 115  }
 116  
 117  void InitForkDAAState(CBlockIndex* pindex)
 118  {
 119      // Seed from this block's own difficulty (the parent chain's target at
 120      // activation), so the fork continues without a difficulty reset.
 121      pindex->nForkTarget.SetCompact(pindex->nBits);
 122      pindex->nForkAvgError      = 0;
 123      pindex->nForkLastBlockTime = 0;
 124      pindex->nForkAggregateSeconds = 0;
 125  }
 126  
 127  arith_uint256 GetForkTarget(const CBlockIndex* pindex)
 128  {
 129      return pindex->nForkTarget;
 130  }
 131