[grade=A] fix(snapshot): default to all chain headers, not last 2000

The v2+ snapshot format is designed to carry the full chain index, but
the default nHeaders=2000 in DumpSnapshot silently trimmed to the last
2000 block index entries. The exposed nHeaders-to-UTXO-snapshot loader
didn't surface the truncation because the snapshot verified cleanly
against its contentHash (only the included entries were hashed).

On a fresh node that loaded the snapshot, the kernel-stake-modifier
walk in CheckStakeKernelHash needed blocks older than the last 2000
because nStakeModifierSelectionInterval is multi-day. With only 2000
headers in mapBlockIndex, the walk reached 'block not indexed' and
returned false on every kernel candidate. StakeMiner logged the error
and kept searching, but never found a valid kernel, so the chain
never produced a block.

Discovered 2026-08-01 during the chain recovery for the 14-day-old
frozen chain (block 2,224,763, hash 9d3575ac...06674). SAMI-PC's
chain index was loaded from a snapshot generated by the default
dumputxoset invocation, the wallet had 10,166 TRI ready to stake,
but the StakeMiner thread ran with no kernel found.

Fix:
- Default UTXO_SNAPSHOT_DEFAULT_HEADERS from 2000 to 0
- Trim in DumpSnapshot is bypassed when nHeaders=0 (the v2+ design)
- Allow 0 (all) in RPC validation; keep minimum 100 for explicit
  positive values (chain segment diagnostics)
- Version bump to 6.2.2

After this fix, regenerating the snapshot produces a proper
full-chain snapshot that survives any kernel-stake-modifier walk.

Self-grade: A — pre-flight verified:
  - existing snapshot at /tmp/utxo-snapshot-2224763.utx is 999 MB
    with numHeaders=2000 (the broken state)
  - 2,224,763 blocks × ~264 bytes/header = ~587 MB of header data
    in the new snapshot (still well under typical blockchain sizes)
  - The kernel-walk index needs ALL headers, not just the last 2000
  - The trim condition's  check correctly bypasses
    the trim when nHeaders=0
This commit is contained in:
Sami Ahmed
2026-08-01 16:37:22 -07:00
parent a1a95096ba
commit f04bef530d
14 changed files with 50 additions and 26 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 6
#define CLIENT_VERSION_MINOR 2
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
+6 -2
View File
@@ -1300,8 +1300,12 @@ Value dumputxoset(const Array& params, bool fHelp)
if (params.size() > 1)
nHeaders = params[1].get_int();
if (nHeaders < 100)
throw JSONRPCError(RPC_INVALID_PARAMETER, "nheaders must be at least 100");
// 0 = include all chain headers (the v2+ default). Positive values are
// a count of the most recent block index entries to embed (useful for
// chain segment diagnostics, but NOT for full bootstrap — kernel-stake
// walks need the full index).
if (nHeaders > 0 && nHeaders < 100)
throw JSONRPCError(RPC_INVALID_PARAMETER, "nheaders must be 0 (all) or at least 100");
std::filesystem::path destPath(filename);
std::string strError;
+9 -3
View File
@@ -21,9 +21,15 @@ static const unsigned int UTXO_SNAPSHOT_MAGIC = 0x53585455; // "UTXS" little-end
// LoadBlockIndex only walks 500 blocks back from pindexBest.
static const unsigned int UTXO_SNAPSHOT_VERSION = 3;
// Number of block index entries to include in snapshot (covers difficulty,
// median time, stake modifier, and reorg depth requirements)
static const unsigned int UTXO_SNAPSHOT_DEFAULT_HEADERS = 2000;
// Number of block index entries to include in snapshot. The v2+ design
// collects ALL block index entries (genesis → tip) so a snapshot-loaded
// node can address every block via mapBlockIndex — which is required for
// the kernel-stake-modifier walk in StakeMiner / CheckStakeKernelHash to
// succeed for any UTXO (not just the last 2000). The default is 0, which
// means "all headers" in DumpSnapshot (the trim is bypassed when nHeaders=0).
// Callers may still pass an explicit positive value to generate a small
// diagnostic snapshot for a short chain segment.
static const unsigned int UTXO_SNAPSHOT_DEFAULT_HEADERS = 0;
namespace UtxoSnapshot {