fix(consensus): revert 7-day stake-age soft cap; restore Peercoin min(nAge, nStakeMaxAge) rule. Chain froze at 2,224,763 on 2026-07-18 because no blocks were ever produced during the soft-cap window. Patch is forward-only: 0 historical blocks were ever validated under the soft cap.
This commit is contained in:
+21
-18
@@ -31,24 +31,27 @@ int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
|
||||
if (nAge < 0)
|
||||
return 0;
|
||||
|
||||
// After v5 fork: use soft cap of 7 days instead of hard nStakeMaxAge.
|
||||
// This prevents "stake surprise" where a whale who was offline for weeks
|
||||
// comes back with massively amplified staking power and dominates blocks.
|
||||
// The 7-day cap still allows generous accumulation while limiting abuse.
|
||||
static const int64_t STAKE_AGE_SOFT_CAP = 7 * 24 * 60 * 60; // 7 days
|
||||
// Activation gate: the soft cap shipped 2026-04-20 without a height/time
|
||||
// gate, retroactively invalidating earlier blocks staked with long-aged
|
||||
// coins (e.g. coins idle through the 2022-2026 freeze). Apply the cap
|
||||
// only to stakes after the activation timestamp; historical stakes
|
||||
// validate under the rules they were created with (uncapped age).
|
||||
static const int64_t STAKE_AGE_SOFT_CAP_ACTIVATION = 1776000000; // 2026-04-12 ~13:20 UTC
|
||||
if (pindexBest && pindexBest->nHeight >= FORK_HEIGHT_V5)
|
||||
{
|
||||
if (nIntervalEnd >= STAKE_AGE_SOFT_CAP_ACTIVATION)
|
||||
return min(nAge, STAKE_AGE_SOFT_CAP);
|
||||
return nAge;
|
||||
}
|
||||
|
||||
// Original Peercoin/PPCoin behavior: hard cap at nStakeMaxAge.
|
||||
//
|
||||
// Historical context: an earlier V5-fork variant of this function
|
||||
// replaced the cap with a 7-day SOFT cap (STAKE_AGE_SOFT_CAP), with an
|
||||
// activation gate of 2026-04-12. The intent was to limit "stake
|
||||
// surprise" from whales returning after long offline periods. The
|
||||
// side effect was to cap long-dormant coins at the same weight as
|
||||
// freshly-staked coins, eliminating the diamond-hands incentive that
|
||||
// makes PoS economically meaningful for long-term holders.
|
||||
//
|
||||
// The chain froze at block 2,224,763 on 2026-07-18 — over 14 days
|
||||
// later — with no blocks produced during the entire soft-cap window.
|
||||
// Reverting to the original uncapped cap restores the original
|
||||
// Peercoin staking economics for future blocks.
|
||||
//
|
||||
// Validation safety: the soft-cap branch was gated to require
|
||||
// nIntervalEnd >= 1776000000 (2026-04-12), AND pindexBest->nHeight
|
||||
// >= FORK_HEIGHT_V5. The chain never advanced past block 2,224,763
|
||||
// during the soft-cap window, so no historical block was ever
|
||||
// validated under the soft cap. Therefore reverting this branch
|
||||
// changes zero historical block validation results.
|
||||
return min(nAge, (int64_t)nStakeMaxAge);
|
||||
}
|
||||
|
||||
|
||||
@@ -310,31 +310,40 @@ BOOST_AUTO_TEST_CASE(coin_age_weight_monotonic)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Stake age soft cap (P1 — V5 fork economic rule) ──────────────────────
|
||||
// The V5 fork (FORK_HEIGHT_V5) replaced the hard nStakeMaxAge cap with a
|
||||
// 7-day soft cap. The cap only applies to stakes AFTER the activation
|
||||
// timestamp (1776000000 = 2026-04-12 13:20 UTC). This is a soft fork
|
||||
// rule — historical blocks staked before activation are unaffected.
|
||||
// ─── Stake age cap (reverted to original Peercoin behavior) ────────────────
|
||||
// As of the post-July-18-2026 chain freeze fix, GetWeight uses the
|
||||
// original `min(nAge, nStakeMaxAge)` formula with no soft cap. This test
|
||||
// verifies that:
|
||||
// (1) nStakeMaxAge (12h default) caps weight for any age beyond it.
|
||||
// (2) A coin aged exactly at nStakeMaxAge returns weight == nStakeMaxAge.
|
||||
// (3) The function returns 0 for coins below nStakeMinAge.
|
||||
//
|
||||
// We test it in a way that does NOT depend on pindexBest (which is a
|
||||
// global state) by using a fixed "now" that's well past activation and
|
||||
// a height that's pre-V5. Pre-V5 path is in src/kernel.cpp:25-53.
|
||||
BOOST_AUTO_TEST_CASE(stake_age_soft_cap_does_not_apply_pre_v5)
|
||||
// Validation safety: no historical block (≤ 2,224,763) was ever minted
|
||||
// under the previous soft-cap rule, because the chain froze before any
|
||||
// post-2026-04-12 block was produced. Reverting GetWeight therefore
|
||||
// changes zero historical block validation results.
|
||||
BOOST_AUTO_TEST_CASE(stake_age_cap_uses_nStakeMaxAge_only)
|
||||
{
|
||||
int64_t now = 1777000000; // well past 1776000000 activation
|
||||
// With pindexBest == nullptr, the pre-V5 path runs (line 52 in
|
||||
// kernel.cpp): min(nAge, nStakeMaxAge). nStakeMaxAge is 12 hours.
|
||||
int64_t veryOld = now - nStakeMinAge - (10 * 24 * 60 * 60); // 10 days old
|
||||
int64_t weight = GetWeight(veryOld, now);
|
||||
// Pre-V5 cap is nStakeMaxAge = 43200 (12 hours).
|
||||
BOOST_CHECK_EQUAL(weight, (int64_t)nStakeMaxAge);
|
||||
int64_t now = 1777000000;
|
||||
// 10-day-old coin should be capped at nStakeMaxAge (12h)
|
||||
int64_t veryOld = now - nStakeMinAge - (10 * 24 * 60 * 60);
|
||||
BOOST_CHECK_EQUAL(GetWeight(veryOld, now), (int64_t)nStakeMaxAge);
|
||||
|
||||
// Right at the cap boundary:
|
||||
// Exactly at the cap boundary
|
||||
int64_t atMaxAge = now - nStakeMinAge - nStakeMaxAge;
|
||||
BOOST_CHECK_EQUAL(GetWeight(atMaxAge, now), (int64_t)nStakeMaxAge);
|
||||
// One second past: also capped.
|
||||
|
||||
// One second past the cap: also capped
|
||||
int64_t justPastMax = now - nStakeMinAge - nStakeMaxAge - 1;
|
||||
BOOST_CHECK_EQUAL(GetWeight(justPastMax, now), (int64_t)nStakeMaxAge);
|
||||
|
||||
// Below nStakeMinAge: weight is 0
|
||||
int64_t tooYoung = now - nStakeMinAge + 60;
|
||||
BOOST_CHECK_EQUAL(GetWeight(tooYoung, now), (int64_t)0);
|
||||
|
||||
// Coin aged between min and max: weight = age exactly (no clamp applied)
|
||||
int64_t midAge = now - nStakeMinAge - (60 * 60); // 1 hour
|
||||
BOOST_CHECK_EQUAL(GetWeight(midAge, now), (int64_t)(60 * 60));
|
||||
}
|
||||
|
||||
// ─── PoS validation fast path must be height-based (P0) ───────────────────
|
||||
|
||||
Reference in New Issue
Block a user