// 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 lastID { return 0, fmt.Errorf("index reader is stale, limit: %d, last-state-id: %d", r.limit, lastID) } // Try to find the element which is greater than the specified target res, err := r.reader.readGreaterThan(id) if err != nil { return 0, err } // Short circuit if the element is found within the current index if res != math.MaxUint64 { return res, nil } // The element was not found, and no additional histories have been indexed. // Return a not-found result. if r.limit == lastID { return res, nil } // Refresh the index reader and give another attempt indexed := rawdb.ReadLastStateHistoryIndex(r.db) if indexed == nil || *indexed < lastID { return 0, errors.New("state history hasn't been indexed yet") } if err := r.reader.refresh(); err != nil { return 0, err } r.limit = *indexed return r.reader.readGreaterThan(id) } // historyReader is the structure to access historic state data. type historyReader struct { disk ethdb.KeyValueReader freezer ethdb.AncientReader readers map[string]*indexReaderWithLimitTag } // newHistoryReader constructs the history reader with the supplied db. func newHistoryReader(disk ethdb.KeyValueReader, freezer ethdb.AncientReader) *historyReader { return &historyReader{ disk: disk, freezer: freezer, readers: make(map[string]*indexReaderWithLimitTag), } } // readAccountMetadata resolves the account metadata within the specified // state history. func (r *historyReader) readAccountMetadata(address common.Address, historyID uint64) ([]byte, error) { blob := rawdb.ReadStateAccountIndex(r.freezer, historyID) if len(blob)%accountIndexSize != 0 { return nil, fmt.Errorf("account index is corrupted, historyID: %d", historyID) } n := len(blob) / accountIndexSize pos := sort.Search(n, func(i int) bool { h := blob[accountIndexSize*i : accountIndexSize*i+common.HashLength] return bytes.Compare(h, address.Bytes()) >= 0 }) if pos == n { return nil, fmt.Errorf("account %#x is not found", address) } offset := accountIndexSize * pos if address != common.BytesToAddress(blob[offset:offset+common.AddressLength]) { return nil, fmt.Errorf("account %#x is not found", address) } return blob[offset : accountIndexSize*(pos+1)], nil } // readStorageMetadata resolves the storage slot metadata within the specified // state history. func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) { // TODO(rj493456442) optimize it with partial read blob := rawdb.ReadStateStorageIndex(r.freezer, historyID) if len(blob)%slotIndexSize != 0 { return nil, fmt.Errorf("storage indices is corrupted, historyID: %d", historyID) } if slotIndexSize*(slotOffset+slotNumber) > len(blob) { return nil, errors.New("out of slice") } subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)] // TODO(rj493456442) get rid of the metadata resolution var ( m meta target common.Hash ) blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID) if err := m.decode(blob); err != nil { return nil, err } if m.version == stateHistoryV0 { target = storageHash } else { target = storageKey } pos := sort.Search(slotNumber, func(i int) bool { slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength] return bytes.Compare(slotID, target.Bytes()) >= 0 }) if pos == slotNumber { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } offset := slotIndexSize * pos if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } return subSlice[offset : slotIndexSize*(pos+1)], nil } // readAccount retrieves the account data from the specified state history. func (r *historyReader) readAccount(address common.Address, historyID uint64) ([]byte, error) { metadata, err := r.readAccountMetadata(address, historyID) if err != nil { return nil, err } length := int(metadata[common.AddressLength]) // one byte for account data length offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset // TODO(rj493456442) optimize it with partial read data := rawdb.ReadStateAccountHistory(r.freezer, historyID) if len(data) < length+offset { return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d", address, historyID) } return data[offset : offset+length], nil } // readStorage retrieves the storage slot data from the specified state history. func (r *historyReader) readStorage(address common.Address, storageKey common.Hash, storageHash common.Hash, historyID uint64) ([]byte, error) { metadata, err := r.readAccountMetadata(address, historyID) if err != nil { return nil, err } // slotIndexOffset: // The offset of storage indices associated with the specified account. // slotIndexNumber: // The number of storage indices associated with the specified account. slotIndexOffset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+5 : common.AddressLength+9])) slotIndexNumber := int(binary.BigEndian.Uint32(metadata[common.AddressLength+9 : common.AddressLength+13])) slotMetadata, err := r.readStorageMetadata(storageKey, storageHash, historyID, slotIndexOffset, slotIndexNumber) if err != nil { return nil, err } length := int(slotMetadata[common.HashLength]) // one byte for slot data length offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset // TODO(rj493456442) optimize it with partial read data := rawdb.ReadStateStorageHistory(r.freezer, historyID) if len(data) < offset+length { return nil, errors.New("corrupted storage data") } return data[offset : offset+length], nil } // read retrieves the state element data associated with the stateID. // stateID: represents the ID of the state of the specified version; // lastID: represents the ID of the latest/newest state history; // latestValue: represents the state value at the current disk layer with ID == lastID; func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint64, latestValue []byte) ([]byte, error) { tail, err := r.freezer.Tail() if err != nil { return nil, err } // stateID == tail is allowed, as the first history object preserved // is tail+1 if stateID < tail { return nil, errors.New("historical state has been pruned") } lastIndexedID := rawdb.ReadLastStateHistoryIndex(r.disk) // To serve the request, all state histories from stateID+1 to lastID // must be indexed if lastIndexedID == nil || *lastIndexedID < lastID { indexed := "null" if lastIndexedID != nil { indexed = fmt.Sprintf("%d", *lastIndexedID) } return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed) } // Construct the index reader to locate the corresponding history for // state retrieval ir, ok := r.readers[state.String()] if !ok { ir, err = newIndexReaderWithLimitTag(r.disk, state.stateIdent) if err != nil { return nil, err } r.readers[state.String()] = ir } historyID, err := ir.readGreaterThan(stateID, lastID) if err != nil { return nil, err } // The state was not found in the state histories, as it has not been modified // since stateID. Use the data from the associated disk layer instead. if historyID == math.MaxUint64 { return latestValue, nil } if state.account { return r.readAccount(state.address, historyID) } return r.readStorage(state.address, state.storageKey, state.storageHash, historyID) }