// Copyright 2025 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see id mappings are left in the database and wait // for overwriting. return int(ohead - nhead), nil } // truncateFromTail removes excess elements from the end of the freezer based // on the given parameters. It returns the number of items that were removed. func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (int, error) { ohead, err := store.Ancients() if err != nil { return 0, err } otail, err := store.Tail() if err != nil { return 0, err } // Ensure that the truncation target falls within the valid range. if otail > ntail || ntail > ohead { return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errTailTruncationOutOfRange, typ, otail, ohead, ntail) } // Short circuit if nothing to truncate. if otail == ntail { return 0, nil } otail, err = store.TruncateTail(ntail) if err != nil { return 0, err } // Associated root->id mappings are left in the database. return int(ntail - otail), nil } // purgeHistory resets the history and also purges the associated index data. func purgeHistory(store ethdb.ResettableAncientStore, disk ethdb.KeyValueStore, typ historyType) { if store == nil { return } frozen, err := store.Ancients() if err != nil { log.Crit("Failed to retrieve head of history", "type", typ, "err", err) } if frozen == 0 { return } // Purge all state history indexing data first batch := disk.NewBatch() if typ == typeStateHistory { rawdb.DeleteStateHistoryIndexMetadata(batch) rawdb.DeleteStateHistoryIndexes(batch) } else { rawdb.DeleteTrienodeHistoryIndexMetadata(batch) rawdb.DeleteTrienodeHistoryIndexes(batch) } if err := batch.Write(); err != nil { log.Crit("Failed to purge history index", "type", typ, "err", err) } if err := store.Reset(); err != nil { log.Crit("Failed to reset history", "type", typ, "err", err) } log.Info("Truncated extraneous history", "type", typ) } // syncHistory explicitly sync the provided history stores. func syncHistory(stores ...ethdb.AncientWriter) error { for _, store := range stores { if store == nil { continue } if err := store.SyncAncient(); err != nil { return err } } return nil } // repairHistory truncates any leftover history objects in either the state // history or the trienode history, which may occur due to an unclean shutdown // or other unexpected events. // // Additionally, this mechanism ensures that the state history and trienode // history remain aligned. Since the trienode history is optional and not // required by regular users, a gap between the trienode history and the // persistent state may appear if the trienode history was disabled during the // previous run. This process detects and resolves such gaps, preventing // unexpected panics. func repairHistory(db ethdb.Database, isVerkle bool, readOnly bool, stateID uint64, enableTrienode bool) (ethdb.ResettableAncientStore, ethdb.ResettableAncientStore, error) { ancient, err := db.AncientDatadir() if err != nil { // TODO error out if ancient store is disabled. A tons of unit tests // disable the ancient store thus the error here will immediately fail // all of them. Fix the tests first. return nil, nil, nil } // State history is mandatory as it is the key component that ensures // resilience to deep reorgs. states, err := rawdb.NewStateFreezer(ancient, isVerkle, readOnly) if err != nil { log.Crit("Failed to open state history freezer", "err", err) } // Trienode history is optional and only required for building archive // node with state proofs. var trienodes ethdb.ResettableAncientStore if enableTrienode { trienodes, err = rawdb.NewTrienodeFreezer(ancient, isVerkle, readOnly) if err != nil { log.Crit("Failed to open trienode history freezer", "err", err) } } // Reset the both histories if the trie database is not initialized yet. // This action is necessary because these histories are not expected // to exist without an initialized trie database. if stateID == 0 { purgeHistory(states, db, typeStateHistory) purgeHistory(trienodes, db, typeTrienodeHistory) return states, trienodes, nil } // Truncate excessive history entries in either the state history or // the trienode history, ensuring both histories remain aligned with // the state. shead, err := states.Ancients() if err != nil { return nil, nil, err } if stateID > shead { // Gap is not permitted in the state history return nil, nil, fmt.Errorf("gap between state [#%d] and state history [#%d]", stateID, shead) } truncTo := min(shead, stateID) if trienodes != nil { thead, err := trienodes.Ancients() if err != nil { return nil, nil, err } if stateID <= thead { truncTo = min(truncTo, thead) } else { if thead == 0 { _, err = trienodes.TruncateTail(stateID) if err != nil { return nil, nil, err } log.Warn("Initialized trienode history") } else { return nil, nil, fmt.Errorf("gap between state [#%d] and trienode history [#%d]", stateID, thead) } } } // Truncate the extra history elements above in freezer in case it's not // aligned with the state. It might happen after an unclean shutdown. truncate := func(store ethdb.AncientStore, typ historyType, nhead uint64) { if store == nil { return } pruned, err := truncateFromHead(store, typ, nhead) if err != nil { log.Crit("Failed to truncate extra histories", "typ", typ, "err", err) } if pruned != 0 { log.Warn("Truncated extra histories", "typ", typ, "number", pruned) } } truncate(states, typeStateHistory, truncTo) truncate(trienodes, typeTrienodeHistory, truncTo) return states, trienodes, nil }