Skip to content

Commit cfa3b96

Browse files
authored
core/rawdb, triedb/pathdb: re-structure the trienode history header (#32907)
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.
1 parent 17e5222 commit cfa3b96

File tree

2 files changed

+47
-67
lines changed

2 files changed

+47
-67
lines changed

core/rawdb/accessors_state.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -313,13 +313,13 @@ func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, err
313313
}
314314

315315
// ReadTrienodeHistoryKeySection retrieves the key section of trienode history.
316-
func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
317-
return db.Ancient(trienodeHistoryKeySectionTable, id-1)
316+
func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) {
317+
return db.AncientBytes(trienodeHistoryKeySectionTable, id-1, offset, length)
318318
}
319319

320320
// ReadTrienodeHistoryValueSection retrieves the value section of trienode history.
321-
func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
322-
return db.Ancient(trienodeHistoryValueSectionTable, id-1)
321+
func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) {
322+
return db.AncientBytes(trienodeHistoryValueSectionTable, id-1, offset, length)
323323
}
324324

325325
// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding

triedb/pathdb/history_trienode.go

Lines changed: 43 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"fmt"
2323
"iter"
2424
"maps"
25-
"math"
2625
"slices"
2726
"sort"
2827
"time"
@@ -202,17 +201,6 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
202201
binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte
203202

204203
for _, owner := range h.owners {
205-
// Fill the header section with offsets at key and value section
206-
headerSection.Write(owner.Bytes()) // 32 bytes
207-
binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes
208-
209-
// The offset to the value section is theoretically unnecessary, since the
210-
// individual value offset is already tracked in the key section. However,
211-
// we still keep it here for two reasons:
212-
// - It's cheap to store (only 4 bytes for each trie).
213-
// - It can be useful for decoding the trie data when key is not required (e.g., in hash mode).
214-
binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes
215-
216204
// Fill the key section with node index
217205
var (
218206
prevKey []byte
@@ -266,6 +254,21 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) {
266254
if _, err := keySection.Write(trailer); err != nil {
267255
return nil, nil, nil, err
268256
}
257+
258+
// Fill the header section with the offsets of the key and value sections.
259+
// Note that the key/value offsets are intentionally tracked *after* encoding
260+
// them into their respective sections, ensuring each offset refers to the end
261+
// position. For n trie chunks, n offset pairs are sufficient to uniquely locate
262+
// the corresponding data.
263+
headerSection.Write(owner.Bytes()) // 32 bytes
264+
binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes
265+
266+
// The offset to the value section is theoretically unnecessary, since the
267+
// individual value offset is already tracked in the key section. However,
268+
// we still keep it here for two reasons:
269+
// - It's cheap to store (only 4 bytes for each trie).
270+
// - It can be useful for decoding the trie data when key is not required (e.g., in hash mode).
271+
binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes
269272
}
270273
return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil
271274
}
@@ -475,22 +478,22 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection
475478

476479
for i := range len(owners) {
477480
// Resolve the boundary of key section
478-
keyStart := keyOffsets[i]
479-
keyLimit := len(keySection)
480-
if i != len(owners)-1 {
481-
keyLimit = int(keyOffsets[i+1])
481+
var keyStart, keyLimit uint32
482+
if i != 0 {
483+
keyStart = keyOffsets[i-1]
482484
}
483-
if int(keyStart) > len(keySection) || keyLimit > len(keySection) {
485+
keyLimit = keyOffsets[i]
486+
if int(keyStart) > len(keySection) || int(keyLimit) > len(keySection) {
484487
return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection))
485488
}
486489

487490
// Resolve the boundary of value section
488-
valStart := valueOffsets[i]
489-
valLimit := len(valueSection)
490-
if i != len(owners)-1 {
491-
valLimit = int(valueOffsets[i+1])
491+
var valStart, valLimit uint32
492+
if i != 0 {
493+
valStart = valueOffsets[i-1]
492494
}
493-
if int(valStart) > len(valueSection) || valLimit > len(valueSection) {
495+
valLimit = valueOffsets[i]
496+
if int(valStart) > len(valueSection) || int(valLimit) > len(valueSection) {
494497
return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection))
495498
}
496499

@@ -510,33 +513,27 @@ type iRange struct {
510513
limit uint32
511514
}
512515

516+
func (ir iRange) len() uint32 {
517+
return ir.limit - ir.start
518+
}
519+
513520
// singleTrienodeHistoryReader provides read access to a single trie within the
514521
// trienode history. It stores an offset to the trie's position in the history,
515522
// along with a set of per-node offsets that can be resolved on demand.
516523
type singleTrienodeHistoryReader struct {
517524
id uint64
518525
reader ethdb.AncientReader
519-
valueRange iRange // value range within the total value section
526+
valueRange iRange // value range within the global value section
520527
valueInternalOffsets map[string]iRange // value offset within the single trie data
521528
}
522529

523530
func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) {
524-
// TODO(rjl493456442) partial freezer read should be supported
525-
keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id)
531+
keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id, uint64(keyRange.start), uint64(keyRange.len()))
526532
if err != nil {
527533
return nil, err
528534
}
529-
keyStart := int(keyRange.start)
530-
keyLimit := int(keyRange.limit)
531-
if keyRange.limit == math.MaxUint32 {
532-
keyLimit = len(keyData)
533-
}
534-
if len(keyData) < keyStart || len(keyData) < keyLimit {
535-
return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData))
536-
}
537-
538535
valueOffsets := make(map[string]iRange)
539-
_, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error {
536+
_, err = decodeSingle(keyData, func(key []byte, start int, limit int) error {
540537
valueOffsets[string(key)] = iRange{
541538
start: uint32(start),
542539
limit: uint32(limit),
@@ -560,20 +557,7 @@ func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) {
560557
if !exists {
561558
return nil, fmt.Errorf("trienode %v not found", []byte(path))
562559
}
563-
// TODO(rjl493456442) partial freezer read should be supported
564-
valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id)
565-
if err != nil {
566-
return nil, err
567-
}
568-
if len(valueData) < int(sr.valueRange.start) {
569-
return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData))
570-
}
571-
entryStart := sr.valueRange.start + offset.start
572-
entryLimit := sr.valueRange.start + offset.limit
573-
if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) {
574-
return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData))
575-
}
576-
return valueData[int(entryStart):int(entryLimit)], nil
560+
return rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, uint64(sr.valueRange.start+offset.start), uint64(offset.len()))
577561
}
578562

579563
// trienodeHistoryReader provides read access to node data in the trie node history.
@@ -614,27 +598,23 @@ func (r *trienodeHistoryReader) decodeHeader() error {
614598
}
615599
for i, owner := range owners {
616600
// Decode the key range for this trie chunk
617-
var keyLimit uint32
618-
if i == len(owners)-1 {
619-
keyLimit = math.MaxUint32
620-
} else {
621-
keyLimit = keyOffsets[i+1]
601+
var keyStart uint32
602+
if i != 0 {
603+
keyStart = keyOffsets[i-1]
622604
}
623605
r.keyRanges[owner] = iRange{
624-
start: keyOffsets[i],
625-
limit: keyLimit,
606+
start: keyStart,
607+
limit: keyOffsets[i],
626608
}
627609

628610
// Decode the value range for this trie chunk
629-
var valLimit uint32
630-
if i == len(owners)-1 {
631-
valLimit = math.MaxUint32
632-
} else {
633-
valLimit = valOffsets[i+1]
611+
var valStart uint32
612+
if i != 0 {
613+
valStart = valOffsets[i-1]
634614
}
635615
r.valRanges[owner] = iRange{
636-
start: valOffsets[i],
637-
limit: valLimit,
616+
start: valStart,
617+
limit: valOffsets[i],
638618
}
639619
}
640620
return nil

0 commit comments

Comments
 (0)