From eabbd68c19a8c02fdcec6d725b1a63426d7fd15c Mon Sep 17 00:00:00 2001 From: YQ AltLayer Date: Sun, 1 Mar 2026 12:20:50 +0000 Subject: [PATCH] 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 --- triedb/pathdb/history.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index 820c3c03bf..b010ad19e0 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -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 }