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