// 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 { r.cacher.block.Add(key, blob) } } br, err = newBlockReader(blob) if err != nil { return 0, err } r.readers[desc.id] = br } // The supplied ID is not greater than block.max, ensuring that an element // satisfying the condition can be found. return br.readGreaterThan(id) } // indexWriter is responsible for writing index data for a specific state (either // an account or a storage slot). The state index follows a two-layer structure: // the first layer consists of a list of fixed-size metadata, each linked to a // second-layer block. The index data (monotonically increasing list of state // history ids) is stored in these second-layer index blocks, which are size // limited. type indexWriter struct { descList []*indexBlockDesc // The list of index block descriptions bw *blockWriter // The live index block writer frozen []*blockWriter // The finalized index block writers, waiting for flush lastID uint64 // The ID of the latest tracked history state stateIdent db ethdb.KeyValueReader cacher *historyCacher } // newIndexWriter constructs the index writer for the specified state. func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, cacher *historyCacher) (*indexWriter, error) { var blob []byte key := state.CacheKey() if cacher != nil && cacher.index.Contains(key) { blob, _ = cacher.index.Get(key) } else { if state.account { blob = rawdb.ReadAccountHistoryIndex(db, state.addressHash) } else { blob = rawdb.ReadStorageHistoryIndex(db, state.addressHash, state.storageHash) } if cacher != nil { cacher.index.Add(key, blob) } } if len(blob) == 0 { desc := newIndexBlockDesc(0) bw, _ := newBlockWriter(nil, desc) return &indexWriter{ descList: []*indexBlockDesc{desc}, bw: bw, state: state, db: db, cacher: cacher, }, nil } descList, err := parseIndex(blob) if err != nil { return nil, err } var ( indexBlock []byte lastDesc = descList[len(descList)-1] ) key = fmt.Sprintf("%s:%d", state.CacheKey(), lastDesc.id) if cacher != nil && cacher.block.Contains(key) { indexBlock, _ = cacher.block.Get(key) } else { if state.account { indexBlock = rawdb.ReadAccountHistoryIndexBlock(db, state.addressHash, lastDesc.id) } else { indexBlock = rawdb.ReadStorageHistoryIndexBlock(db, state.addressHash, state.storageHash, lastDesc.id) } if cacher != nil { cacher.block.Add(key, indexBlock) } } bw, err := newBlockWriter(indexBlock, lastDesc) if err != nil { return nil, err } return &indexWriter{ descList: descList, lastID: lastDesc.max, bw: bw, state: state, db: db, cacher: cacher, }, nil } // append adds the new element into the index writer. func (w *indexWriter) append(id uint64) error { if id <= w.lastID { return fmt.Errorf("append element out of order, last: %d, this: %d", w.lastID, id) } if w.bw.full() { if err := w.rotate(); err != nil { return err } } if err := w.bw.append(id); 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.frozen = append(w.frozen, w.bw) w.bw, err = newBlockWriter(nil, desc) 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 { buf := bw.finish() if w.cacher != nil { if key := fmt.Sprintf("%s:%d", w.state.CacheKey(), bw.desc.id); w.cacher.block.Contains(key) { w.cacher.block.Add(key, buf) } } if w.state.account { rawdb.WriteAccountHistoryIndexBlock(batch, w.state.addressHash, bw.desc.id, buf) } else { rawdb.WriteStorageHistoryIndexBlock(batch, w.state.addressHash, w.state.storageHash, bw.desc.id, buf) } } w.frozen = nil // release all the frozen writers buf := make([]byte, 0, indexBlockDescSize*len(descList)) for _, desc := range descList { buf = append(buf, desc.encode()...) } if w.cacher != nil { if key := w.state.CacheKey(); w.cacher.index.Contains(key) { w.cacher.index.Add(key, buf) } } if w.state.account { rawdb.WriteAccountHistoryIndex(batch, w.state.addressHash, buf) } else { rawdb.WriteStorageHistoryIndex(batch, w.state.addressHash, w.state.storageHash, 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 db ethdb.KeyValueReader cacher *historyCacher } // newIndexDeleter constructs the index deleter for the specified state. func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, cacher *historyCacher) (*indexDeleter, error) { var blob []byte key := state.CacheKey() if cacher != nil && cacher.index.Contains(key) { blob, _ = cacher.index.Get(key) } else { if state.account { blob = rawdb.ReadAccountHistoryIndex(db, state.addressHash) } else { blob = rawdb.ReadStorageHistoryIndex(db, state.addressHash, state.storageHash) } if cacher != nil { cacher.index.Add(key, blob) } } if len(blob) == 0 { // TODO(rjl493456442) we can probably return an error here, // deleter with no data is meaningless. desc := newIndexBlockDesc(0) bw, _ := newBlockWriter(nil, desc) return &indexDeleter{ descList: []*indexBlockDesc{desc}, bw: bw, state: state, db: db, cacher: cacher, }, nil } descList, err := parseIndex(blob) if err != nil { return nil, err } var ( indexBlock []byte lastDesc = descList[len(descList)-1] ) key = fmt.Sprintf("%s:%d", state.CacheKey(), lastDesc.id) if cacher != nil && cacher.block.Contains(key) { indexBlock, _ = cacher.block.Get(key) } else { if state.account { indexBlock = rawdb.ReadAccountHistoryIndexBlock(db, state.addressHash, lastDesc.id) } else { indexBlock = rawdb.ReadStorageHistoryIndexBlock(db, state.addressHash, state.storageHash, lastDesc.id) } if cacher != nil { cacher.block.Add(key, indexBlock) } } bw, err := newBlockWriter(indexBlock, lastDesc) if err != nil { return nil, err } return &indexDeleter{ descList: descList, lastID: lastDesc.max, bw: bw, state: state, db: db, cacher: cacher, }, nil } // empty returns an flag indicating 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 var ( indexBlock []byte lastDesc = d.descList[len(d.descList)-1] ) if d.state.account { indexBlock = rawdb.ReadAccountHistoryIndexBlock(d.db, d.state.addressHash, lastDesc.id) } else { indexBlock = rawdb.ReadStorageHistoryIndexBlock(d.db, d.state.addressHash, d.state.storageHash, lastDesc.id) } bw, err := newBlockWriter(indexBlock, lastDesc) 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 { if d.cacher != nil { if key := fmt.Sprintf("%s:%d", d.state.CacheKey(), id); d.cacher.block.Contains(key) { d.cacher.block.Remove(key) } } if d.state.account { rawdb.DeleteAccountHistoryIndexBlock(batch, d.state.addressHash, id) } else { rawdb.DeleteStorageHistoryIndexBlock(batch, d.state.addressHash, d.state.storageHash, id) } } d.dropped = nil // Flush the content of last block writer, regardless it's dirty or not if !d.bw.empty() { buf := d.bw.finish() if d.cacher != nil { if key := fmt.Sprintf("%s:%d", d.state.CacheKey(), d.bw.desc.id); d.cacher.block.Contains(key) { d.cacher.block.Add(key, buf) } } if d.state.account { rawdb.WriteAccountHistoryIndexBlock(batch, d.state.addressHash, d.bw.desc.id, buf) } else { rawdb.WriteStorageHistoryIndexBlock(batch, d.state.addressHash, d.state.storageHash, d.bw.desc.id, buf) } } // Flush the index metadata into the supplied batch if d.empty() { if d.cacher != nil { if key := d.state.CacheKey(); d.cacher.index.Contains(key) { d.cacher.index.Remove(key) } } if d.state.account { rawdb.DeleteAccountHistoryIndex(batch, d.state.addressHash) } else { rawdb.DeleteStorageHistoryIndex(batch, d.state.addressHash, d.state.storageHash) } } else { buf := make([]byte, 0, indexBlockDescSize*len(d.descList)) for _, desc := range d.descList { buf = append(buf, desc.encode()...) } if d.cacher != nil { if key := d.state.CacheKey(); d.cacher.index.Contains(key) { d.cacher.index.Add(key, buf) } } if d.state.account { rawdb.WriteAccountHistoryIndex(batch, d.state.addressHash, buf) } else { rawdb.WriteStorageHistoryIndex(batch, d.state.addressHash, d.state.storageHash, buf) } } }