go-ethereum/triedb/pathdb/history_trienode.go
alex017 cb97c48cb6
Some checks failed
/ Linux Build (arm) (push) Has been cancelled
/ Keeper Build (push) Has been cancelled
/ Windows Build (push) Has been cancelled
/ Linux Build (push) Has been cancelled
/ Docker Image (push) Has been cancelled
triedb/pathdb: preallocate slices in decodeRestartTrailer (#33715)
Preallocate capacity for `keyOffsets` and `valOffsets` slices in
`decodeRestartTrailer` since the exact size (`nRestarts`) is known
upfront.

---------

Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
2026-01-30 21:14:15 +08:00

854 lines
30 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
package pathdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"iter"
"maps"
"math"
"slices"
"sort"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// Each trie node history entry consists of three parts (stored in three freezer
// tables according):
//
// # Header
// The header records metadata, including:
//
// - the history version (1 byte)
// - the parent state root (32 bytes)
// - the current state root (32 bytes)
// - block number (8 bytes)
//
// - a lexicographically sorted list of trie IDs
// - the corresponding offsets into the key and value sections for each trie
// data chunk. The offsets refer to the end position of each chunk, with
// the assumption that the key and value sections for the first data chunk
// start at offset 0.
//
// Although some fields (e.g., parent state root, block number) are duplicated
// between the state history and the trienode history, these two histories
// operate independently. To ensure each remains self-contained and self-descriptive,
// we have chosen to maintain these duplicate fields.
//
// # Key section
// The key section stores trie node keys (paths) in a compressed format.
// It also contains relative offsets into the value section for locating
// the corresponding trie node data. These offsets are relative to the
// beginning of the trie data chunk, the chunk's base offset must be added
// to obtain the absolute position in the value section.
//
// # Value section
// The value section is a concatenated byte stream of all trie node data.
// Each trie node can be retrieved using the offset and length specified
// by its index entry.
//
// Header section:
//
// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------|
// | metadata | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | ... | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) |
// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------|
//
//
// Key section:
//
// + restart point + restart point (depends on restart interval)
// / /
// +---------------+---------------+---------------+---------------+---------+
// | node entry 1 | node entry 2 | ... | node entry n | trailer |
// +---------------+---------------+---------------+---------------+---------+
// \ /
// +---- restart block ------+
//
// node entry:
//
// +---- key len ----+
// / \
// +-------+---------+-----------+---------+-----------------------+-----------------------+
// | shared (varint) | not shared (varint) | value length (varlen) | unshared key (varlen) |
// +-----------------+---------------------+-----------------------+-----------------------+
//
// trailer:
//
// +---- 4-bytes ----+ +---- 4-bytes ----+
// / \ / \
// +----------------------+------------------------+-----+--------------------------+
// | restart_1 key offset | restart_1 value offset | ... | restart number (4-bytes) |
// +----------------------+------------------------+-----+--------------------------+
//
// Note: Both the key offset and the value offset are relative to the beginning
// of the trie data chunk. The chunk's base offset must be added to obtain the
// absolute position in the value section.
//
// Value section:
//
// +--------------+--------------+-------+---------------+
// | node data 1 | node data 2 | ... | node data n |
// +--------------+--------------+-------+---------------+
//
// NOTE: All fixed-length integer are big-endian.
const (
trienodeHistoryV0 = uint8(0) // initial version of node history structure
trienodeHistoryVersion = trienodeHistoryV0 // the default node history version
trienodeMetadataSize = 1 + 2*common.HashLength + 8 // the size of metadata in the history
trienodeTrieHeaderSize = 8 + common.HashLength // the size of a single trie header in history
trienodeDataBlockRestartLen = 16 // The restart interval length of trie node block
)
// trienodeMetadata describes the meta data of trienode history.
type trienodeMetadata struct {
version uint8 // version tag of history object
parent common.Hash // prev-state root before the state transition
root common.Hash // post-state root after the state transition
block uint64 // associated block number
}
// trienodeHistory represents a set of trie node changes resulting from a state
// transition across the main account trie and all associated storage tries.
type trienodeHistory struct {
meta *trienodeMetadata // Metadata of the history
owners []common.Hash // List of trie identifier sorted lexicographically
nodeList map[common.Hash][]string // Set of node paths sorted lexicographically
nodes map[common.Hash]map[string][]byte // Set of original value of trie nodes before state transition
}
// newTrienodeHistory constructs a trienode history with the provided trie nodes.
func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, nodes map[common.Hash]map[string][]byte) *trienodeHistory {
nodeList := make(map[common.Hash][]string, len(nodes))
for owner, subset := range nodes {
keys := make(sort.StringSlice, 0, len(subset))
for k := range subset {
keys = append(keys, k)
}
keys.Sort()
nodeList[owner] = keys
}
return &trienodeHistory{
meta: &trienodeMetadata{
version: trienodeHistoryVersion,
parent: parent,
root: root,
block: block,
},
owners: slices.SortedFunc(maps.Keys(nodes), common.Hash.Cmp),
nodeList: nodeList,
nodes: nodes,
}
}
// typ implements the history interface, returning the historical data type held.
func (h *trienodeHistory) typ() historyType {
return typeTrienodeHistory
}
// forEach implements the history interface, returning an iterator to traverse the
// state entries in the history.
func (h *trienodeHistory) forEach() iter.Seq[indexElem] {
return func(yield func(indexElem) bool) {
for _, owner := range h.owners {
var (
scheme *indexScheme
paths = h.nodeList[owner]
indexes = make(map[string]map[uint16]struct{})
)
if owner == (common.Hash{}) {
scheme = accountIndexScheme
} else {
scheme = storageIndexScheme
}
for _, leaf := range findLeafPaths(paths) {
chunks, ids := scheme.splitPath(leaf)
for i := 0; i < len(chunks); i++ {
if _, exists := indexes[chunks[i]]; !exists {
indexes[chunks[i]] = make(map[uint16]struct{})
}
indexes[chunks[i]][ids[i]] = struct{}{}
}
}
for chunk, ids := range indexes {
elem := trienodeIndexElem{
owner: owner,
path: chunk,
data: slices.Collect(maps.Keys(ids)),
}
if !yield(elem) {
return
}
}
}
}
}
// encode serializes the contained trie nodes into bytes.
func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
var (
buf = make([]byte, 64)
headerSection bytes.Buffer
keySection bytes.Buffer
valueSection bytes.Buffer
)
binary.Write(&headerSection, binary.BigEndian, h.meta.version) // 1 byte
headerSection.Write(h.meta.parent.Bytes()) // 32 bytes
headerSection.Write(h.meta.root.Bytes()) // 32 bytes
binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte
for _, owner := range h.owners {
// Fill the key section with node index
var (
prevKey []byte
restarts []uint32
prefixLen int
internalKeyOffset uint32 // key offset within the trie data internally
internalValOffset uint32 // value offset within the trie data internally
)
for i, path := range h.nodeList[owner] {
key := []byte(path)
// Track the internal key and value offsets at the beginning of the
// restart section. The absolute offsets within the key and value
// sections should first include the offset of the trie chunk itself
// stored in the header section.
if i%trienodeDataBlockRestartLen == 0 {
restarts = append(restarts, internalKeyOffset)
restarts = append(restarts, internalValOffset)
prefixLen = 0
} else {
prefixLen = commonPrefixLen(prevKey, key)
}
value := h.nodes[owner][path]
// key section
n := binary.PutUvarint(buf[0:], uint64(prefixLen)) // key length shared (varint)
n += binary.PutUvarint(buf[n:], uint64(len(key)-prefixLen)) // key length not shared (varint)
n += binary.PutUvarint(buf[n:], uint64(len(value))) // value length (varint)
if _, err := keySection.Write(buf[:n]); err != nil {
return nil, nil, nil, err
}
// unshared key
if _, err := keySection.Write(key[prefixLen:]); err != nil {
return nil, nil, nil, err
}
n += len(key) - prefixLen
prevKey = key
// value section
if _, err := valueSection.Write(value); err != nil {
return nil, nil, nil, err
}
internalKeyOffset += uint32(n)
internalValOffset += uint32(len(value))
}
// Encode trailer, the number of restart sections is len(restarts))/2,
// as we track the offsets of both key and value sections.
var trailer []byte
for _, number := range append(restarts, uint32(len(restarts))/2) {
binary.BigEndian.PutUint32(buf[:4], number)
trailer = append(trailer, buf[:4]...)
}
if _, err := keySection.Write(trailer); err != nil {
return nil, nil, nil, err
}
// Fill the header section with the offsets of the key and value sections.
// Note that key/value offsets are intentionally recorded *after* encoding
// into their respective sections, so each offset refers to an end position.
// For n trie chunks, n offset pairs are sufficient to uniquely locate each
// chunk's data. For example, [0, offset_0] defines the range of trie chunk 0,
// while [offset_{n-2}, offset_{n-1}] defines the range of trie chunk n-1.
headerSection.Write(owner.Bytes()) // 32 bytes
binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes
binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes
}
return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil
}
// decodeHeader resolves the metadata from the header section. An error
// should be returned if the header section is corrupted.
func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []uint32, error) {
if len(data) < trienodeMetadataSize {
return nil, nil, nil, nil, fmt.Errorf("trienode history is too small, index size: %d", len(data))
}
version := data[0]
if version != trienodeHistoryVersion {
return nil, nil, nil, nil, fmt.Errorf("unregonized trienode history version: %d", version)
}
parent := common.BytesToHash(data[1 : common.HashLength+1]) // 32 bytes
root := common.BytesToHash(data[common.HashLength+1 : common.HashLength*2+1]) // 32 bytes
block := binary.BigEndian.Uint64(data[common.HashLength*2+1 : trienodeMetadataSize]) // 8 bytes
size := len(data) - trienodeMetadataSize
if size%trienodeTrieHeaderSize != 0 {
return nil, nil, nil, nil, fmt.Errorf("truncated trienode history data, size %d", len(data))
}
count := size / trienodeTrieHeaderSize
var (
owners = make([]common.Hash, 0, count)
keyOffsets = make([]uint32, 0, count)
valOffsets = make([]uint32, 0, count)
)
for i := range count {
n := trienodeMetadataSize + trienodeTrieHeaderSize*i
owner := common.BytesToHash(data[n : n+common.HashLength])
if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 {
return nil, nil, nil, nil, fmt.Errorf("trienode owners are out of order, prev: %v, cur: %v", owners[i-1], owner)
}
owners = append(owners, owner)
// Decode the offset to the key section
keyOffset := binary.BigEndian.Uint32(data[n+common.HashLength : n+common.HashLength+4])
if i != 0 && keyOffset <= keyOffsets[i-1] {
return nil, nil, nil, nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset)
}
keyOffsets = append(keyOffsets, keyOffset)
// Decode the offset into the value section. Note that identical value offsets
// are valid if the node values in the last trie chunk are all zero (e.g., after
// a trie deletion).
valOffset := binary.BigEndian.Uint32(data[n+common.HashLength+4 : n+common.HashLength+8])
if i != 0 && valOffset < valOffsets[i-1] {
return nil, nil, nil, nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset)
}
valOffsets = append(valOffsets, valOffset)
}
return &trienodeMetadata{
version: version,
parent: parent,
root: root,
block: block,
}, owners, keyOffsets, valOffsets, nil
}
// decodeKeyEntry resolves a single entry from the key section starting from
// the specified offset.
func decodeKeyEntry(keySection []byte, offset int) (uint64, uint64, []byte, int, error) {
var byteRead int
// Resolve the length of shared key
nShared, nn := binary.Uvarint(keySection[offset:]) // key length shared (varint)
if nn <= 0 {
return 0, 0, nil, 0, fmt.Errorf("corrupted varint encoding for nShared at offset %d", offset)
}
byteRead += nn
// Resolve the length of unshared key
nUnshared, nn := binary.Uvarint(keySection[offset+byteRead:]) // key length not shared (varint)
if nn <= 0 {
return 0, 0, nil, 0, fmt.Errorf("corrupted varint encoding for nUnshared at offset %d", offset+byteRead)
}
byteRead += nn
// Resolve the length of value
nValue, nn := binary.Uvarint(keySection[offset+byteRead:]) // value length (varint)
if nn <= 0 {
return 0, 0, nil, 0, fmt.Errorf("corrupted varint encoding for nValue at offset %d", offset+byteRead)
}
byteRead += nn
// Validate that the values can fit in an int to prevent overflow on 32-bit systems
if nShared > uint64(math.MaxUint32) || nUnshared > uint64(math.MaxUint32) || nValue > uint64(math.MaxUint32) {
return 0, 0, nil, 0, errors.New("key/value size too large")
}
// Resolve the unshared key
if offset+byteRead+int(nUnshared) > len(keySection) {
return 0, 0, nil, 0, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, offset+byteRead, len(keySection))
}
unsharedKey := keySection[offset+byteRead : offset+byteRead+int(nUnshared)]
byteRead += int(nUnshared)
return nShared, nValue, unsharedKey, byteRead, nil
}
// decodeRestartTrailer resolves all the offsets recorded at the trailer.
func decodeRestartTrailer(keySection []byte) ([]uint32, []uint32, int, error) {
// Decode the number of restart section
if len(keySection) < 4 {
return nil, nil, 0, fmt.Errorf("key section too short, size: %d", len(keySection))
}
nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:])
// Decode the trailer
var (
keyOffsets = make([]uint32, 0, int(nRestarts))
valOffsets = make([]uint32, 0, int(nRestarts))
)
if len(keySection) < int(8*nRestarts)+4 {
return nil, nil, 0, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
}
for i := range int(nRestarts) {
o := len(keySection) - 4 - (int(nRestarts)-i)*8
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
if i != 0 && keyOffset <= keyOffsets[i-1] {
return nil, nil, 0, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset)
}
keyOffsets = append(keyOffsets, keyOffset)
// Same value offset is allowed just in case all the trie nodes in the last
// section have zero-size value.
valOffset := binary.BigEndian.Uint32(keySection[o+4 : o+8])
if i != 0 && valOffset < valOffsets[i-1] {
return nil, nil, 0, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset)
}
valOffsets = append(valOffsets, valOffset)
}
keyLimit := len(keySection) - 4 - int(nRestarts)*8 // End of key data
return keyOffsets, valOffsets, keyLimit, nil
}
// decodeRestartSection resolves all entries in a restart section. The keyData
// contains the encoded keys for the section.
//
// onValue is the callback function being invoked for each resolved entry. The
// start and limit are the offsets within the restart section, the base value
// offset of the restart section itself should be added by the caller itself.
// What's more, this function should return `aborted == true` if the entry
// resolution should be terminated.
func decodeRestartSection(keyData []byte, onValue func(key []byte, start int, limit int) (bool, error)) error {
var (
prevKey []byte
items int
keyOff int // the key offset within the single trie data
valOff int // the value offset within the single trie data
)
// Decode data
for keyOff < len(keyData) {
nShared, nValue, unsharedKey, nn, err := decodeKeyEntry(keyData, keyOff)
if err != nil {
return err
}
keyOff += nn
// Assemble the full key
var key []byte
if items%trienodeDataBlockRestartLen == 0 {
if nShared != 0 {
return fmt.Errorf("unexpected non-zero shared key prefix: %d", nShared)
}
key = unsharedKey
} else {
if int(nShared) > len(prevKey) {
return fmt.Errorf("unexpected shared key prefix: %d, prefix key length: %d", nShared, len(prevKey))
}
key = make([]byte, int(nShared)+len(unsharedKey))
copy(key[:nShared], prevKey[:nShared])
copy(key[nShared:], unsharedKey)
}
if items != 0 && bytes.Compare(prevKey, key) >= 0 {
return fmt.Errorf("trienode paths are out of order, prev: %v, cur: %v", prevKey, key)
}
prevKey = key
valEnd := valOff + int(nValue)
abort, err := onValue(key, valOff, valEnd)
if err != nil {
return err
}
if abort {
return nil
}
valOff = valEnd
items++
}
if keyOff != len(keyData) {
return fmt.Errorf("excessive key data after decoding, offset: %d, size: %d", keyOff, len(keyData))
}
return nil
}
// onValue is the callback function being invoked for each resolved entry. The
// start and limit are the offsets within this trie chunk, the base value
// offset of the trie chunk itself should be added by the caller itself.
func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) error {
keyOffsets, valOffsets, keyLimit, err := decodeRestartTrailer(keySection)
if err != nil {
return err
}
for i := 0; i < len(keyOffsets); i++ {
var keyData []byte
if i == len(keyOffsets)-1 {
keyData = keySection[keyOffsets[i]:keyLimit]
} else {
keyData = keySection[keyOffsets[i]:keyOffsets[i+1]]
}
err := decodeRestartSection(keyData, func(key []byte, start int, limit int) (bool, error) {
valStart := int(valOffsets[i]) + start
valLimit := int(valOffsets[i]) + limit
// Possible in tests
if onValue == nil {
return false, nil
}
if err := onValue(key, valStart, valLimit); err != nil {
return false, err
}
return false, nil // abort=false
})
if err != nil {
return err
}
}
return nil
}
func decodeSingleWithValue(keySection []byte, valueSection []byte) ([]string, map[string][]byte, error) {
var (
offset int
estimated = len(keySection) / 8
nodes = make(map[string][]byte, estimated)
paths = make([]string, 0, estimated)
)
err := decodeSingle(keySection, func(key []byte, start int, limit int) error {
if start != offset {
return fmt.Errorf("gapped value section offset: %d, want: %d", start, offset)
}
// start == limit is allowed for zero-value trie node (e.g., non-existent node)
if start > limit {
return fmt.Errorf("invalid value offsets, start: %d, limit: %d", start, limit)
}
if start > len(valueSection) || limit > len(valueSection) {
return fmt.Errorf("value section out of range: start: %d, limit: %d, size: %d", start, limit, len(valueSection))
}
strkey := string(key)
paths = append(paths, strkey)
nodes[strkey] = valueSection[start:limit]
offset = limit
return nil
})
if err != nil {
return nil, nil, err
}
if offset != len(valueSection) {
return nil, nil, fmt.Errorf("excessive value data after decoding, offset: %d, size: %d", offset, len(valueSection))
}
return paths, nodes, nil
}
// decode deserializes the contained trie nodes from the provided bytes.
func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection []byte) error {
metadata, owners, keyOffsets, valueOffsets, err := decodeHeader(header)
if err != nil {
return err
}
h.meta = metadata
h.owners = owners
h.nodeList = make(map[common.Hash][]string)
h.nodes = make(map[common.Hash]map[string][]byte)
for i := range len(owners) {
// Resolve the boundary of the key section, each offset referring
// to the end position of this trie chunk.
var keyStart, keyLimit uint32
if i != 0 {
keyStart = keyOffsets[i-1]
}
keyLimit = keyOffsets[i]
if int(keyStart) > len(keySection) || int(keyLimit) > len(keySection) {
return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection))
}
// Resolve the boundary of the value section, each offset referring
// to the end position of this trie chunk.
var valStart, valLimit uint32
if i != 0 {
valStart = valueOffsets[i-1]
}
valLimit = valueOffsets[i]
if int(valStart) > len(valueSection) || int(valLimit) > len(valueSection) {
return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection))
}
// Decode the key and values for this specific trie
paths, nodes, err := decodeSingleWithValue(keySection[keyStart:keyLimit], valueSection[valStart:valLimit])
if err != nil {
return err
}
h.nodeList[owners[i]] = paths
h.nodes[owners[i]] = nodes
}
return nil
}
type iRange struct {
start uint32
limit uint32
}
func (ir iRange) len() uint32 {
return ir.limit - ir.start
}
type singleTrienodeHistoryReader struct {
id uint64
reader ethdb.AncientReader
keyData []byte
valueRange iRange
}
func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) {
keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id, uint64(keyRange.start), uint64(keyRange.len()))
if err != nil {
return nil, err
}
return &singleTrienodeHistoryReader{
id: id,
reader: reader,
keyData: keyData,
valueRange: valueRange,
}, nil
}
// searchSingle searches for a specific trie node identified by the provided
// key within a single trie node chunk.
//
// It returns the node value's offset range (start and limit) within the
// trie node data. An error is returned if the node cannot be found.
func (sr *singleTrienodeHistoryReader) searchSingle(key []byte) (int, int, bool, error) {
keyOffsets, valOffsets, keyLimit, err := decodeRestartTrailer(sr.keyData)
if err != nil {
return 0, 0, false, err
}
// Binary search against the boundary keys for each restart section
var (
boundFind bool
boundValueLen uint64
)
pos := sort.Search(len(keyOffsets), func(i int) bool {
_, nValue, dkey, _, derr := decodeKeyEntry(sr.keyData[keyOffsets[i]:], 0)
if derr != nil {
err = derr
return false
}
n := bytes.Compare(key, dkey)
if n == 0 {
boundFind = true
boundValueLen = nValue
}
return n <= 0
})
if err != nil {
return 0, 0, false, err
}
// The node is found as the boundary of restart section
if boundFind {
start := valOffsets[pos]
limit := valOffsets[pos] + uint32(boundValueLen)
return int(start), int(limit), true, nil
}
// The node is not found as all others have larger key than the target
if pos == 0 {
return 0, 0, false, nil
}
// Search the target node within the restart section
var keyData []byte
if pos == len(keyOffsets) {
keyData = sr.keyData[keyOffsets[pos-1]:keyLimit] // last section
} else {
keyData = sr.keyData[keyOffsets[pos-1]:keyOffsets[pos]] // non-last section
}
var (
nStart int
nLimit int
found bool
)
err = decodeRestartSection(keyData, func(ikey []byte, start, limit int) (bool, error) {
if bytes.Equal(key, ikey) {
nStart = int(valOffsets[pos-1]) + start
nLimit = int(valOffsets[pos-1]) + limit
found = true
return true, nil // abort = true
}
return false, nil // abort = false
})
if err != nil {
return 0, 0, false, err
}
if !found {
return 0, 0, false, nil
}
return nStart, nLimit, true, nil
}
// read retrieves the trie node data with the provided node path.
func (sr *singleTrienodeHistoryReader) read(key []byte) ([]byte, bool, error) {
start, limit, found, err := sr.searchSingle(key)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
valStart := uint64(start) + uint64(sr.valueRange.start)
valLen := uint64(limit - start)
value, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, valStart, valLen)
if err != nil {
return nil, false, err
}
return value, true, nil
}
// trienodeHistoryReader provides read access to node data in the trie node history.
// It resolves data from the underlying ancient store only when needed, minimizing
// I/O overhead.
type trienodeHistoryReader struct {
id uint64 // ID of the associated trienode history
reader ethdb.AncientReader // Database reader of ancient store
}
// newTrienodeHistoryReader constructs the reader for specific trienode history.
func newTrienodeHistoryReader(id uint64, reader ethdb.AncientReader) *trienodeHistoryReader {
return &trienodeHistoryReader{
id: id,
reader: reader,
}
}
// decodeHeader decodes the header section of trienode history.
func (r *trienodeHistoryReader) decodeHeader(owner common.Hash) (iRange, iRange, bool, error) {
header, err := rawdb.ReadTrienodeHistoryHeader(r.reader, r.id)
if err != nil {
return iRange{}, iRange{}, false, err
}
_, owners, keyOffsets, valOffsets, err := decodeHeader(header)
if err != nil {
return iRange{}, iRange{}, false, err
}
pos := sort.Search(len(owners), func(i int) bool {
return owner.Cmp(owners[i]) <= 0
})
if pos == len(owners) || owners[pos] != owner {
return iRange{}, iRange{}, false, nil
}
var keyRange iRange
if pos != 0 {
keyRange.start = keyOffsets[pos-1]
}
keyRange.limit = keyOffsets[pos]
var valRange iRange
if pos != 0 {
valRange.start = valOffsets[pos-1]
}
valRange.limit = valOffsets[pos]
return keyRange, valRange, true, nil
}
// read retrieves the trie node data with the provided TrieID and node path.
func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, bool, error) {
keyRange, valRange, found, err := r.decodeHeader(owner)
if err != nil {
return nil, false, err
}
if !found {
return nil, false, nil
}
ir, err := newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange)
if err != nil {
return nil, false, err
}
return ir.read([]byte(path))
}
// writeTrienodeHistory persists the trienode history associated with the given diff layer.
func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer, rate uint32) error {
start := time.Now()
nodes, err := dl.nodes.encodeNodeHistory(dl.root, rate)
if err != nil {
return err
}
h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, nodes)
header, keySection, valueSection, err := h.encode()
if err != nil {
return err
}
// Write history data into five freezer table respectively.
if err := rawdb.WriteTrienodeHistory(writer, dl.stateID(), header, keySection, valueSection); err != nil {
return err
}
trienodeHistoryDataBytesMeter.Mark(int64(len(valueSection)))
trienodeHistoryIndexBytesMeter.Mark(int64(len(header) + len(keySection)))
trienodeHistoryBuildTimeMeter.UpdateSince(start)
log.Debug(
"Stored trienode history", "id", dl.stateID(), "block", dl.block,
"header", common.StorageSize(len(header)),
"keySection", common.StorageSize(len(keySection)),
"valueSection", common.StorageSize(len(valueSection)),
"elapsed", common.PrettyDuration(time.Since(start)),
)
return nil
}
// readTrienodeMetadata resolves the metadata of the specified trienode history.
func readTrienodeMetadata(reader ethdb.AncientReader, id uint64) (*trienodeMetadata, error) {
header, err := rawdb.ReadTrienodeHistoryHeader(reader, id)
if err != nil {
return nil, err
}
metadata, _, _, _, err := decodeHeader(header)
if err != nil {
return nil, err
}
return metadata, nil
}
// readTrienodeHistory resolves a single trienode history object with specific id.
func readTrienodeHistory(reader ethdb.AncientReader, id uint64) (*trienodeHistory, error) {
header, keySection, valueSection, err := rawdb.ReadTrienodeHistory(reader, id)
if err != nil {
return nil, err
}
var h trienodeHistory
if err := h.decode(header, keySection, valueSection); err != nil {
return nil, err
}
return &h, nil
}
// readTrienodeHistories resolves a list of trienode histories with the specific range.
func readTrienodeHistories(reader ethdb.AncientReader, start uint64, count uint64) ([]history, error) {
headers, keySections, valueSections, err := rawdb.ReadTrienodeHistoryList(reader, start, count)
if err != nil {
return nil, err
}
var res []history
for i, header := range headers {
var h trienodeHistory
if err := h.decode(header, keySections[i], valueSections[i]); err != nil {
return nil, err
}
res = append(res, &h)
}
return res, nil
}