mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
In this PR, several changes have been made: (a) restructure the trienode history header section Previously, the offsets of the key and value sections were recorded before encoding data into these sections. As a result, these offsets referred to the start position of each chunk rather than the end position. This caused an issue where the end position of the last chunk was unknown, making it incompatible with the freezer partial-read APIs. With this update, all offsets now refer to the end position, and the start position of the first chunk is always 0. (b) Enable partial freezer read for trienode data retrieval The partial freezer read feature is now utilized in trienode data retrieval, improving efficiency.
714 lines
26 KiB
Go
714 lines
26 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"
|
|
"fmt"
|
|
"iter"
|
|
"maps"
|
|
"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
|
|
//
|
|
// 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 resolving
|
|
// the corresponding trie node data. Note that these offsets are relative
|
|
// to the data chunk for the trie; the chunk offset must be added to obtain
|
|
// the absolute position.
|
|
//
|
|
// # 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.
|
|
//
|
|
// The header and key sections are sufficient for locating a trie node,
|
|
// while a partial read of the value section is enough to retrieve its data.
|
|
|
|
// 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) | 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 start of
|
|
// the trie data chunk. To obtain the absolute offset, add the offset of the
|
|
// trie data chunk itself.
|
|
//
|
|
// 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)
|
|
for owner, subset := range nodes {
|
|
keys := sort.StringSlice(slices.Collect(maps.Keys(subset)))
|
|
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,
|
|
}
|
|
}
|
|
|
|
// sharedLen returns the length of the common prefix shared by a and b.
|
|
func sharedLen(a, b []byte) int {
|
|
n := min(len(a), len(b))
|
|
for i := range n {
|
|
if a[i] != b[i] {
|
|
return i
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// 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[stateIdent] {
|
|
return func(yield func(stateIdent) bool) {
|
|
for _, owner := range h.owners {
|
|
for _, path := range h.nodeList[owner] {
|
|
if !yield(newTrienodeIdent(owner, path)) {
|
|
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 for the trie internally
|
|
internalValOffset uint32 // value offset for the trie internally
|
|
)
|
|
for i, path := range h.nodeList[owner] {
|
|
key := []byte(path)
|
|
if i%trienodeDataBlockRestartLen == 0 {
|
|
restarts = append(restarts, internalKeyOffset)
|
|
restarts = append(restarts, internalValOffset)
|
|
prefixLen = 0
|
|
} else {
|
|
prefixLen = sharedLen(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 the key/value offsets are intentionally tracked *after* encoding
|
|
// them into their respective sections, ensuring each offset refers to the end
|
|
// position. For n trie chunks, n offset pairs are sufficient to uniquely locate
|
|
// the corresponding data.
|
|
headerSection.Write(owner.Bytes()) // 32 bytes
|
|
binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes
|
|
|
|
// The offset to the value section is theoretically unnecessary, since the
|
|
// individual value offset is already tracked in the key section. However,
|
|
// we still keep it here for two reasons:
|
|
// - It's cheap to store (only 4 bytes for each trie).
|
|
// - It can be useful for decoding the trie data when key is not required (e.g., in hash mode).
|
|
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
|
|
}
|
|
|
|
func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]string, error) {
|
|
var (
|
|
prevKey []byte
|
|
items int
|
|
keyOffsets []uint32
|
|
valOffsets []uint32
|
|
|
|
keyOff int // the key offset within the single trie data
|
|
valOff int // the value offset within the single trie data
|
|
|
|
keys []string
|
|
)
|
|
// Decode the number of restart section
|
|
if len(keySection) < 4 {
|
|
return nil, fmt.Errorf("key section too short, size: %d", len(keySection))
|
|
}
|
|
nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:])
|
|
|
|
if len(keySection) < int(8*nRestarts)+4 {
|
|
return nil, 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, 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, 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
|
|
|
|
// Decode data
|
|
for keyOff < keyLimit {
|
|
// Validate the key and value offsets within the single trie data chunk
|
|
if items%trienodeDataBlockRestartLen == 0 {
|
|
restartIndex := items / trienodeDataBlockRestartLen
|
|
if restartIndex >= len(keyOffsets) {
|
|
return nil, fmt.Errorf("restart index out of range: %d, available restarts: %d", restartIndex, len(keyOffsets))
|
|
}
|
|
if keyOff != int(keyOffsets[restartIndex]) {
|
|
return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[restartIndex], keyOff)
|
|
}
|
|
if valOff != int(valOffsets[restartIndex]) {
|
|
return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[restartIndex], valOff)
|
|
}
|
|
}
|
|
// Resolve the entry from key section
|
|
nShared, nn := binary.Uvarint(keySection[keyOff:]) // key length shared (varint)
|
|
keyOff += nn
|
|
nUnshared, nn := binary.Uvarint(keySection[keyOff:]) // key length not shared (varint)
|
|
keyOff += nn
|
|
nValue, nn := binary.Uvarint(keySection[keyOff:]) // value length (varint)
|
|
keyOff += nn
|
|
|
|
// Resolve unshared key
|
|
if keyOff+int(nUnshared) > len(keySection) {
|
|
return nil, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, keyOff, len(keySection))
|
|
}
|
|
unsharedKey := keySection[keyOff : keyOff+int(nUnshared)]
|
|
keyOff += int(nUnshared)
|
|
|
|
// Assemble the full key
|
|
var key []byte
|
|
if items%trienodeDataBlockRestartLen == 0 {
|
|
if nShared != 0 {
|
|
return nil, fmt.Errorf("unexpected non-zero shared key prefix: %d", nShared)
|
|
}
|
|
key = unsharedKey
|
|
} else {
|
|
if int(nShared) > len(prevKey) {
|
|
return nil, fmt.Errorf("unexpected shared key prefix: %d, prefix key length: %d", nShared, len(prevKey))
|
|
}
|
|
key = append([]byte{}, prevKey[:nShared]...)
|
|
key = append(key, unsharedKey...)
|
|
}
|
|
if items != 0 && bytes.Compare(prevKey, key) >= 0 {
|
|
return nil, fmt.Errorf("trienode paths are out of order, prev: %v, cur: %v", prevKey, key)
|
|
}
|
|
prevKey = key
|
|
|
|
// Resolve value
|
|
if onValue != nil {
|
|
if err := onValue(key, valOff, valOff+int(nValue)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
valOff += int(nValue)
|
|
|
|
items++
|
|
keys = append(keys, string(key))
|
|
}
|
|
if keyOff != keyLimit {
|
|
return nil, fmt.Errorf("excessive key data after decoding, offset: %d, size: %d", keyOff, keyLimit)
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
func decodeSingleWithValue(keySection []byte, valueSection []byte) ([]string, map[string][]byte, error) {
|
|
var (
|
|
offset int
|
|
nodes = make(map[string][]byte)
|
|
)
|
|
paths, 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))
|
|
}
|
|
nodes[string(key)] = 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 key section
|
|
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 value section
|
|
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
|
|
}
|
|
|
|
// singleTrienodeHistoryReader provides read access to a single trie within the
|
|
// trienode history. It stores an offset to the trie's position in the history,
|
|
// along with a set of per-node offsets that can be resolved on demand.
|
|
type singleTrienodeHistoryReader struct {
|
|
id uint64
|
|
reader ethdb.AncientReader
|
|
valueRange iRange // value range within the global value section
|
|
valueInternalOffsets map[string]iRange // value offset within the single trie data
|
|
}
|
|
|
|
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
|
|
}
|
|
valueOffsets := make(map[string]iRange)
|
|
_, err = decodeSingle(keyData, func(key []byte, start int, limit int) error {
|
|
valueOffsets[string(key)] = iRange{
|
|
start: uint32(start),
|
|
limit: uint32(limit),
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &singleTrienodeHistoryReader{
|
|
id: id,
|
|
reader: reader,
|
|
valueRange: valueRange,
|
|
valueInternalOffsets: valueOffsets,
|
|
}, nil
|
|
}
|
|
|
|
// read retrieves the trie node data with the provided node path.
|
|
func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) {
|
|
offset, exists := sr.valueInternalOffsets[path]
|
|
if !exists {
|
|
return nil, fmt.Errorf("trienode %v not found", []byte(path))
|
|
}
|
|
return rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, uint64(sr.valueRange.start+offset.start), uint64(offset.len()))
|
|
}
|
|
|
|
// 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
|
|
keyRanges map[common.Hash]iRange // Key ranges identifying trie chunks
|
|
valRanges map[common.Hash]iRange // Value ranges identifying trie chunks
|
|
iReaders map[common.Hash]*singleTrienodeHistoryReader // readers for each individual trie chunk
|
|
}
|
|
|
|
// newTrienodeHistoryReader constructs the reader for specific trienode history.
|
|
func newTrienodeHistoryReader(id uint64, reader ethdb.AncientReader) (*trienodeHistoryReader, error) {
|
|
r := &trienodeHistoryReader{
|
|
id: id,
|
|
reader: reader,
|
|
keyRanges: make(map[common.Hash]iRange),
|
|
valRanges: make(map[common.Hash]iRange),
|
|
iReaders: make(map[common.Hash]*singleTrienodeHistoryReader),
|
|
}
|
|
if err := r.decodeHeader(); err != nil {
|
|
return nil, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// decodeHeader decodes the header section of trienode history.
|
|
func (r *trienodeHistoryReader) decodeHeader() error {
|
|
header, err := rawdb.ReadTrienodeHistoryHeader(r.reader, r.id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, owners, keyOffsets, valOffsets, err := decodeHeader(header)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i, owner := range owners {
|
|
// Decode the key range for this trie chunk
|
|
var keyStart uint32
|
|
if i != 0 {
|
|
keyStart = keyOffsets[i-1]
|
|
}
|
|
r.keyRanges[owner] = iRange{
|
|
start: keyStart,
|
|
limit: keyOffsets[i],
|
|
}
|
|
|
|
// Decode the value range for this trie chunk
|
|
var valStart uint32
|
|
if i != 0 {
|
|
valStart = valOffsets[i-1]
|
|
}
|
|
r.valRanges[owner] = iRange{
|
|
start: valStart,
|
|
limit: valOffsets[i],
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// read retrieves the trie node data with the provided TrieID and node path.
|
|
func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, error) {
|
|
ir, ok := r.iReaders[owner]
|
|
if !ok {
|
|
keyRange, exists := r.keyRanges[owner]
|
|
if !exists {
|
|
return nil, fmt.Errorf("trie %x is unknown", owner)
|
|
}
|
|
valRange, exists := r.valRanges[owner]
|
|
if !exists {
|
|
return nil, fmt.Errorf("trie %x is unknown", owner)
|
|
}
|
|
var err error
|
|
ir, err = newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.iReaders[owner] = ir
|
|
}
|
|
return ir.read(path)
|
|
}
|
|
|
|
// writeTrienodeHistory persists the trienode history associated with the given diff layer.
|
|
// nolint:unused
|
|
func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
|
start := time.Now()
|
|
h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, dl.nodes.nodeOrigin)
|
|
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.
|
|
// nolint:unused
|
|
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
|
|
}
|