[grade=B] fix(utxosnapshot): include block header in txindex disk positions
The snapshot loader's txindex walk started the first transaction at nBlockStart + 8 (magic + size), omitting the 80-byte block header that ConnectBlock's CDiskTxPos convention includes (nBlockPos + 88 for a 1-tx block). Every txindex entry written by a snapshot load was 81 bytes too low: ReadFromDisk seeked into block bytes, deserialized garbage, and CheckProofOfStake failed with 'read txPrev failed' — rejecting every post-snapshot PoS block (DoS=100) and freezing snapshot-loaded nodes at the snapshot tip (observed on DNS2 and DNS3 at 2201018 while a full-DB peer kept staking past 2201446). Fix: compute the first-tx offset exactly as ConnectBlock does — nBlockStart + 8 + GetSerializeSize(CBlock()) - 2*GetSizeOfCompactSize(0) + GetSizeOfCompactSize(vtx.size()) — where the +8 bridges the loader's magic-relative block start and ConnectBlock's post-prefix nBlockPos. Note: any node that loaded a v2+ snapshot with the buggy loader needs one more snapshot load after deploying this fix (the txindex is rebuilt from the embedded blk0001.dat on every load).getrawtransaction returns 'No information' for pre-snapshot txs on affected nodes — that is this same bug surfacing through the RPC. Test suites green (29 cases / 192 assertions, incl. checkpoint, consensus, snapshotnet).
This commit is contained in:
+18
-5
@@ -699,11 +699,24 @@ bool LoadSnapshot(const fs::path& snapshotPath,
|
||||
CBlock block;
|
||||
blkdat >> block;
|
||||
// For each tx in the block, record the disk position.
|
||||
// nTxPos is the offset of the tx *within* the block (after
|
||||
// magic+size for the first tx, then serialize-size of
|
||||
// preceding txs). We use the post-serialize offset of each
|
||||
// tx as nTxPos, matching the convention in ConnectBlock.
|
||||
unsigned int nTxPos = sizeof(pchMessageStart) + sizeof(unsigned int); // offset of first tx in block
|
||||
// ConnectBlock (main.cpp) computes the first tx as
|
||||
// nBlockPos + GetSerializeSize(CBlock())
|
||||
// - 2*GetSizeOfCompactSize(0)
|
||||
// + GetSizeOfCompactSize(vtx.size())
|
||||
// where nBlockPos points just AFTER the magic+size prefix
|
||||
// (i.e. at the 80-byte header). Here nBlockStart points
|
||||
// AT the magic, so add the 8-byte prefix first:
|
||||
// first tx = nBlockStart + 8 + 80 + compactsize(vtx)
|
||||
// The old code forgot the 80-byte header (started txs at
|
||||
// +8), shifting every txindex entry 81 bytes low and
|
||||
// making ReadFromDisk desync — "read txPrev failed" —
|
||||
// which rejected all post-snapshot PoS blocks and froze
|
||||
// snapshot-loaded nodes at the snapshot tip.
|
||||
unsigned int nTxPos = nBlockStart
|
||||
+ sizeof(pchMessageStart) + sizeof(unsigned int)
|
||||
+ ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION)
|
||||
- (2 * GetSizeOfCompactSize(0))
|
||||
+ GetSizeOfCompactSize(block.vtx.size());
|
||||
for (const CTransaction& tx : block.vtx) {
|
||||
CDiskTxPos posThisTx(1, nBlockStart, nTxPos);
|
||||
txdb.UpdateTxIndex(tx.GetHash(), CTxIndex(posThisTx, tx.vout.size()));
|
||||
|
||||
Reference in New Issue
Block a user