core/rawdb, triedb/pathdb: re-structure the trienode history header

This commit is contained in:
Gary Rong 2025-10-14 12:44:38 +08:00
parent 52c484de86
commit 76651a6bfe
2 changed files with 47 additions and 67 deletions

View file

@ -313,13 +313,13 @@ func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, err
} }
// ReadTrienodeHistoryKeySection retrieves the key section of trienode history. // ReadTrienodeHistoryKeySection retrieves the key section of trienode history.
func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) {
return db.Ancient(trienodeHistoryKeySectionTable, id-1) return db.AncientBytes(trienodeHistoryKeySectionTable, id-1, offset, length)
} }
// ReadTrienodeHistoryValueSection retrieves the value section of trienode history. // ReadTrienodeHistoryValueSection retrieves the value section of trienode history.
func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) {
return db.Ancient(trienodeHistoryValueSectionTable, id-1) return db.AncientBytes(trienodeHistoryValueSectionTable, id-1, offset, length)
} }
// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding // ReadTrienodeHistoryList retrieves the a list of trienode history corresponding

View file

@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"iter" "iter"
"maps" "maps"
"math"
"slices" "slices"
"sort" "sort"
"time" "time"
@ -202,17 +201,6 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte
for _, owner := range h.owners { for _, owner := range h.owners {
// Fill the header section with offsets at key and value section
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
// Fill the key section with node index // Fill the key section with node index
var ( var (
prevKey []byte prevKey []byte
@ -266,6 +254,21 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
if _, err := keySection.Write(trailer); err != nil { if _, err := keySection.Write(trailer); err != nil {
return nil, nil, nil, err 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 return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil
} }
@ -471,22 +474,22 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection
for i := range len(owners) { for i := range len(owners) {
// Resolve the boundary of key section // Resolve the boundary of key section
keyStart := keyOffsets[i] var keyStart, keyLimit uint32
keyLimit := len(keySection) if i != 0 {
if i != len(owners)-1 { keyStart = keyOffsets[i-1]
keyLimit = int(keyOffsets[i+1])
} }
if int(keyStart) > len(keySection) || keyLimit > len(keySection) { 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)) return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection))
} }
// Resolve the boundary of value section // Resolve the boundary of value section
valStart := valueOffsets[i] var valStart, valLimit uint32
valLimit := len(valueSection) if i != 0 {
if i != len(owners)-1 { valStart = valueOffsets[i-1]
valLimit = int(valueOffsets[i+1])
} }
if int(valStart) > len(valueSection) || valLimit > len(valueSection) { 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)) return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection))
} }
@ -506,33 +509,27 @@ type iRange struct {
limit uint32 limit uint32
} }
func (ir iRange) len() uint32 {
return ir.limit - ir.start
}
// singleTrienodeHistoryReader provides read access to a single trie within the // singleTrienodeHistoryReader provides read access to a single trie within the
// trienode history. It stores an offset to the trie's position in the history, // 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. // along with a set of per-node offsets that can be resolved on demand.
type singleTrienodeHistoryReader struct { type singleTrienodeHistoryReader struct {
id uint64 id uint64
reader ethdb.AncientReader reader ethdb.AncientReader
valueRange iRange // value range within the total value section valueRange iRange // value range within the global value section
valueInternalOffsets map[string]iRange // value offset within the single trie data valueInternalOffsets map[string]iRange // value offset within the single trie data
} }
func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) { func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) {
// TODO(rjl493456442) partial freezer read should be supported keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id, uint64(keyRange.start), uint64(keyRange.len()))
keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyStart := int(keyRange.start)
keyLimit := int(keyRange.limit)
if keyRange.limit == math.MaxUint32 {
keyLimit = len(keyData)
}
if len(keyData) < keyStart || len(keyData) < keyLimit {
return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData))
}
valueOffsets := make(map[string]iRange) valueOffsets := make(map[string]iRange)
_, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error { _, err = decodeSingle(keyData, func(key []byte, start int, limit int) error {
valueOffsets[string(key)] = iRange{ valueOffsets[string(key)] = iRange{
start: uint32(start), start: uint32(start),
limit: uint32(limit), limit: uint32(limit),
@ -556,20 +553,7 @@ func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) {
if !exists { if !exists {
return nil, fmt.Errorf("trienode %v not found", []byte(path)) return nil, fmt.Errorf("trienode %v not found", []byte(path))
} }
// TODO(rjl493456442) partial freezer read should be supported return rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, uint64(sr.valueRange.start+offset.start), uint64(offset.len()))
valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id)
if err != nil {
return nil, err
}
if len(valueData) < int(sr.valueRange.start) {
return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData))
}
entryStart := sr.valueRange.start + offset.start
entryLimit := sr.valueRange.start + offset.limit
if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) {
return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData))
}
return valueData[int(entryStart):int(entryLimit)], nil
} }
// trienodeHistoryReader provides read access to node data in the trie node history. // trienodeHistoryReader provides read access to node data in the trie node history.
@ -610,27 +594,23 @@ func (r *trienodeHistoryReader) decodeHeader() error {
} }
for i, owner := range owners { for i, owner := range owners {
// Decode the key range for this trie chunk // Decode the key range for this trie chunk
var keyLimit uint32 var keyStart uint32
if i == len(owners)-1 { if i != 0 {
keyLimit = math.MaxUint32 keyStart = keyOffsets[i-1]
} else {
keyLimit = keyOffsets[i+1]
} }
r.keyRanges[owner] = iRange{ r.keyRanges[owner] = iRange{
start: keyOffsets[i], start: keyStart,
limit: keyLimit, limit: keyOffsets[i],
} }
// Decode the value range for this trie chunk // Decode the value range for this trie chunk
var valLimit uint32 var valStart uint32
if i == len(owners)-1 { if i != 0 {
valLimit = math.MaxUint32 valStart = valOffsets[i-1]
} else {
valLimit = valOffsets[i+1]
} }
r.valRanges[owner] = iRange{ r.valRanges[owner] = iRange{
start: valOffsets[i], start: valStart,
limit: valLimit, limit: valOffsets[i],
} }
} }
return nil return nil