[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:
+1
-1
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user