triedb/pathdb: fix trienode history gap when newly enabled on existing node

When enabling trienode history (--history.trienode=0) on a node that
already has state at block N, repairHistory() returns a fatal error:
"gap between state [#N] and trienode history [#0]"

This happens because the newly created trienode freezer has 0 entries
while state is at block N. The gap check on line 427 treats this as a
data corruption error, but the function's own comments document that
it should "detect and resolve such gaps" for the optional trienode
history.

Fix by detecting the newly-enabled case (empty freezer + non-zero
stateID) and skipping both the gap check and the head truncation.
An empty freezer with head=0 would also fail truncateFromHead because
ohead(0) < nhead(stateID) is outside the valid range.

The trienode history will begin accumulating naturally from the
current state forward, which is the expected behavior documented
in the v1.17.0 release notes.

Fixes ethereum/go-ethereum#33907
This commit is contained in:
YQ AltLayer 2026-03-01 12:20:50 +00:00
parent 723aae2b4e
commit eabbd68c19

View file

@ -419,17 +419,27 @@ func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint
if stateID > head {
return nil, nil, fmt.Errorf("gap between state [#%d] and state history [#%d]", stateID, head)
}
// Track whether trienode history is newly enabled (empty freezer on an
// existing node). In that case, skip gap checks and truncation — history
// will begin accumulating from the current state forward.
trienodeNewlyEnabled := false
if trienodes != nil {
th, err := trienodes.Ancients()
if err != nil {
return nil, nil, err
}
if stateID > th {
return nil, nil, fmt.Errorf("gap between state [#%d] and trienode history [#%d]", stateID, th)
}
if th != head {
log.Info("Histories are not aligned with each other", "state", head, "trienode", th)
head = min(head, th)
if th == 0 && stateID > 0 {
// Trienode history is newly enabled on an existing node.
log.Info("Trienode history newly enabled, will start from current state", "state", stateID)
trienodeNewlyEnabled = true
} else {
if stateID > th {
return nil, nil, fmt.Errorf("gap between state [#%d] and trienode history [#%d]", stateID, th)
}
if th != head {
log.Info("Histories are not aligned with each other", "state", head, "trienode", th)
head = min(head, th)
}
}
}
head = min(head, stateID)
@ -449,6 +459,11 @@ func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint
}
}
truncate(states, typeStateHistory, head)
truncate(trienodes, typeTrienodeHistory, head)
// Only truncate trienode history if it already has data. A newly enabled
// (empty) trienode freezer would cause truncateFromHead to error because
// ohead(0) < nhead(stateID) falls outside the valid range.
if !trienodeNewlyEnabled {
truncate(trienodes, typeTrienodeHistory, head)
}
return states, trienodes, nil
}