// 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 0 && descList[i].max > limit; i-- { // The previous block has the elements that exceed the limit, // therefore the current block can be entirely dropped. if descList[i-1].max >= limit { descList = descList[:i] } } // Take the last block for appending new elements lastDesc := descList[len(descList)-1] indexBlock := readStateIndexBlock(state, db, lastDesc.id) // Construct the writer for the last block. All elements in this block // that exceed the limit will be truncated. bw, err := newBlockWriter(indexBlock, lastDesc, limit, bitmapSize != 0) if err != nil { return nil, err } return &indexWriter{ descList: descList, lastID: bw.last(), bw: bw, state: state, db: db, bitmapSize: bitmapSize, }, nil } // append adds the new element into the index writer. func (w *indexWriter) append(id uint64, ext []uint16) error { if id <= w.lastID { return fmt.Errorf("append element out of order, last: %d, this: %d", w.lastID, id) } if w.bw.estimateFull(ext) { if err := w.rotate(); err != nil { return err } } if err := w.bw.append(id, ext); err != nil { return err } w.lastID = id return nil } // rotate creates a new index block for storing index records from scratch // and caches the current full index block for finalization. func (w *indexWriter) rotate() error { var ( err error desc = newIndexBlockDesc(w.bw.desc.id+1, w.bitmapSize) ) w.frozen = append(w.frozen, w.bw) w.bw, err = newBlockWriter(nil, desc, 0 /* useless if the block is empty */, w.bitmapSize != 0) if err != nil { return err } w.descList = append(w.descList, desc) return nil } // finish finalizes all the frozen index block writers along with the live one // if it's not empty, committing the index block data and the index meta into // the supplied batch. // // This function is safe to be called multiple times. func (w *indexWriter) finish(batch ethdb.Batch) { var ( writers = append(w.frozen, w.bw) descList = w.descList ) // The live index block writer might be empty if the entire index write // is created from scratch, remove it from committing. if w.bw.empty() { writers = writers[:len(writers)-1] descList = descList[:len(descList)-1] } if len(writers) == 0 { return // nothing to commit } for _, bw := range writers { writeStateIndexBlock(w.state, batch, bw.desc.id, bw.finish()) } w.frozen = nil // release all the frozen writers size := indexBlockDescSize + w.bitmapSize buf := make([]byte, 0, size*len(descList)) for _, desc := range descList { buf = append(buf, desc.encode()...) } writeStateIndex(w.state, batch, buf) } // indexDeleter is responsible for deleting index data for a specific state. type indexDeleter struct { descList []*indexBlockDesc // The list of index block descriptions bw *blockWriter // The live index block writer dropped []uint32 // The list of index block id waiting for deleting lastID uint64 // The ID of the latest tracked history state stateIdent // The identifier of the state being indexed bitmapSize int // The size of optional extension bitmap db ethdb.KeyValueReader } // newIndexDeleter constructs the index deleter for the specified state. func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64, bitmapSize int) (*indexDeleter, error) { blob := readStateIndex(state, db) if len(blob) == 0 { // TODO(rjl493456442) we can probably return an error here, // deleter with no data is meaningless. desc := newIndexBlockDesc(0, bitmapSize) bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */, bitmapSize != 0) return &indexDeleter{ descList: []*indexBlockDesc{desc}, bw: bw, state: state, bitmapSize: bitmapSize, db: db, }, nil } descList, err := parseIndex(blob, bitmapSize) if err != nil { return nil, err } // Trim trailing blocks whose elements all exceed the limit. for i := len(descList) - 1; i > 0 && descList[i].max > limit; i-- { // The previous block has the elements that exceed the limit, // therefore the current block can be entirely dropped. if descList[i-1].max >= limit { descList = descList[:i] } } // Take the block for deleting element from lastDesc := descList[len(descList)-1] indexBlock := readStateIndexBlock(state, db, lastDesc.id) // Construct the writer for the last block. All elements in this block // that exceed the limit will be truncated. bw, err := newBlockWriter(indexBlock, lastDesc, limit, bitmapSize != 0) if err != nil { return nil, err } return &indexDeleter{ descList: descList, lastID: bw.last(), bw: bw, state: state, bitmapSize: bitmapSize, db: db, }, nil } // empty returns whether the state index is empty. func (d *indexDeleter) empty() bool { return d.bw.empty() && len(d.descList) == 1 } // pop removes the last written element from the index writer. func (d *indexDeleter) pop(id uint64) error { if id == 0 { return errors.New("zero history ID is not valid") } if id != d.lastID { return fmt.Errorf("pop element out of order, last: %d, this: %d", d.lastID, id) } if err := d.bw.pop(id); err != nil { return err } if !d.bw.empty() { d.lastID = d.bw.desc.max return nil } // Discarding the last block writer if it becomes empty by popping an element d.dropped = append(d.dropped, d.descList[len(d.descList)-1].id) // Reset the entire index writer if it becomes empty after popping an element if d.empty() { d.lastID = 0 return nil } d.descList = d.descList[:len(d.descList)-1] // Open the previous block writer for deleting lastDesc := d.descList[len(d.descList)-1] indexBlock := readStateIndexBlock(d.state, d.db, lastDesc.id) bw, err := newBlockWriter(indexBlock, lastDesc, lastDesc.max, d.bitmapSize != 0) if err != nil { return err } d.bw = bw d.lastID = bw.desc.max return nil } // finish deletes the empty index blocks and updates the index meta. // // This function is safe to be called multiple times. func (d *indexDeleter) finish(batch ethdb.Batch) { for _, id := range d.dropped { deleteStateIndexBlock(d.state, batch, id) } d.dropped = nil // Flush the content of last block writer, regardless it's dirty or not if !d.bw.empty() { writeStateIndexBlock(d.state, batch, d.bw.desc.id, d.bw.finish()) } // Flush the index metadata into the supplied batch if d.empty() { deleteStateIndex(d.state, batch) } else { size := indexBlockDescSize + d.bitmapSize buf := make([]byte, 0, size*len(d.descList)) for _, desc := range d.descList { buf = append(buf, desc.encode()...) } writeStateIndex(d.state, batch, buf) } } // readStateIndex retrieves the index metadata for the given state identifier. // This function is shared by accounts and storage slots. func readStateIndex(ident stateIdent, db ethdb.KeyValueReader) []byte { switch ident.typ { case typeAccount: return rawdb.ReadAccountHistoryIndex(db, ident.addressHash) case typeStorage: return rawdb.ReadStorageHistoryIndex(db, ident.addressHash, ident.storageHash) case typeTrienode: return rawdb.ReadTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } } // writeStateIndex writes the provided index metadata into database with the // given state identifier. This function is shared by accounts and storage slots. func writeStateIndex(ident stateIdent, db ethdb.KeyValueWriter, data []byte) { switch ident.typ { case typeAccount: rawdb.WriteAccountHistoryIndex(db, ident.addressHash, data) case typeStorage: rawdb.WriteStorageHistoryIndex(db, ident.addressHash, ident.storageHash, data) case typeTrienode: rawdb.WriteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path), data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } } // deleteStateIndex removes the index metadata for the given state identifier. // This function is shared by accounts and storage slots. func deleteStateIndex(ident stateIdent, db ethdb.KeyValueWriter) { switch ident.typ { case typeAccount: rawdb.DeleteAccountHistoryIndex(db, ident.addressHash) case typeStorage: rawdb.DeleteStorageHistoryIndex(db, ident.addressHash, ident.storageHash) case typeTrienode: rawdb.DeleteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } } // readStateIndexBlock retrieves the index block for the given state identifier // and block ID. This function is shared by accounts and storage slots. func readStateIndexBlock(ident stateIdent, db ethdb.KeyValueReader, id uint32) []byte { switch ident.typ { case typeAccount: return rawdb.ReadAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: return rawdb.ReadStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) case typeTrienode: return rawdb.ReadTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } } // writeStateIndexBlock writes the provided index block into database with the // given state identifier. This function is shared by accounts and storage slots. func writeStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32, data []byte) { switch ident.typ { case typeAccount: rawdb.WriteAccountHistoryIndexBlock(db, ident.addressHash, id, data) case typeStorage: rawdb.WriteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id, data) case typeTrienode: rawdb.WriteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id, data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } } // deleteStateIndexBlock removes the index block from database with the given // state identifier. This function is shared by accounts and storage slots. func deleteStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32) { switch ident.typ { case typeAccount: rawdb.DeleteAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: rawdb.DeleteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) case typeTrienode: rawdb.DeleteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } }