mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
triedb/pathdb: improve trienode reader
This commit is contained in:
parent
9cfb2225dc
commit
5df5020f0d
3 changed files with 185 additions and 59 deletions
|
|
@ -284,10 +284,10 @@ func newTrienodeReader(disk ethdb.KeyValueReader, freezer ethdb.AncientReader, r
|
|||
}
|
||||
|
||||
// readTrienode retrieves the trienode data from the specified trienode history.
|
||||
func (r *trienodeReader) readTrienode(addrHash common.Hash, path string, historyID uint64) ([]byte, error) {
|
||||
func (r *trienodeReader) readTrienode(addrHash common.Hash, path string, historyID uint64) ([]byte, bool, error) {
|
||||
tr, err := newTrienodeHistoryReader(historyID, r.freezer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
return tr.read(addrHash, path)
|
||||
}
|
||||
|
|
@ -355,15 +355,19 @@ func (r *trienodeReader) readOptimized(state stateIdent, it HistoryIndexIterator
|
|||
seq += 1
|
||||
|
||||
eg.Go(func() error {
|
||||
data, found, err := r.readTrienode(state.addressHash, state.path, id)
|
||||
if err != nil {
|
||||
term.Store(true)
|
||||
return err
|
||||
}
|
||||
// In optimistic readahead mode, it is theoretically possible to encounter a
|
||||
// NotFound error, where the trie node does not actually exist and the iterator
|
||||
// reports a false-positive mutation record. Terminate the iterator if so, as
|
||||
// all the necessary data (checkpoints and all diffs) required has already been
|
||||
// fetching.
|
||||
data, err := r.readTrienode(state.addressHash, state.path, id)
|
||||
if err != nil {
|
||||
if !found {
|
||||
term.Store(true)
|
||||
log.Debug("Failed to read the trienode", "err", err)
|
||||
log.Debug("Failed to read the trienode")
|
||||
return nil
|
||||
}
|
||||
full, _, err := decodeNodeFull(data)
|
||||
|
|
|
|||
|
|
@ -606,51 +606,113 @@ 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
|
||||
id uint64
|
||||
reader ethdb.AncientReader
|
||||
keyData []byte
|
||||
valueRange iRange
|
||||
}
|
||||
|
||||
// TODO(rjl493456442): This function performs a large number of allocations.
|
||||
// Given the large data size, a byte pool could be used to mitigate this.
|
||||
func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) {
|
||||
// TODO(rjl493456442) the data size is known in advance, allocating the
|
||||
// dedicated byte slices from the pool.
|
||||
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) + valueRange.start,
|
||||
limit: uint32(limit) + valueRange.start,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &singleTrienodeHistoryReader{
|
||||
id: id,
|
||||
reader: reader,
|
||||
valueRange: valueRange,
|
||||
valueInternalOffsets: valueOffsets,
|
||||
id: id,
|
||||
reader: reader,
|
||||
keyData: keyData,
|
||||
valueRange: valueRange,
|
||||
}, 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))
|
||||
// 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
|
||||
}
|
||||
return rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, uint64(offset.start), uint64(offset.len()))
|
||||
// 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.
|
||||
|
|
@ -714,25 +776,25 @@ func (r *trienodeHistoryReader) decodeHeader() error {
|
|||
}
|
||||
|
||||
// read retrieves the trie node data with the provided TrieID and node path.
|
||||
func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, error) {
|
||||
func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, bool, error) {
|
||||
ir, ok := r.iReaders[owner]
|
||||
if !ok {
|
||||
keyRange, exists := r.keyRanges[owner]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("trie %x is unknown", owner)
|
||||
return nil, false, nil // not found
|
||||
}
|
||||
valRange, exists := r.valRanges[owner]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("trie %x is unknown", owner)
|
||||
return nil, false, nil // not found
|
||||
}
|
||||
var err error
|
||||
ir, err = newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
r.iReaders[owner] = ir
|
||||
}
|
||||
return ir.read(path)
|
||||
return ir.read([]byte(path))
|
||||
}
|
||||
|
||||
// writeTrienodeHistory persists the trienode history associated with the given diff layer.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package pathdb
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
|
@ -144,7 +145,7 @@ func TestTrienodeHistoryReader(t *testing.T) {
|
|||
for _, owner := range h.owners {
|
||||
nodes := h.nodes[owner]
|
||||
for key, value := range nodes {
|
||||
blob, err := tr.read(owner, key)
|
||||
blob, _, err := tr.read(owner, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read trienode history: %v", err)
|
||||
}
|
||||
|
|
@ -424,16 +425,17 @@ func TestTrienodeHistoryReaderNonExistentPath(t *testing.T) {
|
|||
}
|
||||
|
||||
// Try to read a non-existent path
|
||||
_, err = tr.read(testrand.Hash(), "nonexistent")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for non-existent trie owner")
|
||||
var found bool
|
||||
_, found, err = tr.read(testrand.Hash(), "nonexistent")
|
||||
if found || err != nil {
|
||||
t.Fatal("Expected not found for non-existent trie owner")
|
||||
}
|
||||
|
||||
// Try to read from existing owner but non-existent path
|
||||
owner := h.owners[0]
|
||||
_, err = tr.read(owner, "nonexistent-path")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for non-existent path")
|
||||
_, found, err = tr.read(owner, "nonexistent-path")
|
||||
if found || err != nil {
|
||||
t.Fatal("Expected not found for non-existent path")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -464,16 +466,16 @@ func TestTrienodeHistoryReaderNilValues(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test reading nil values
|
||||
data1, err := tr.read(owner, "nil1")
|
||||
if err != nil {
|
||||
data1, found, err := tr.read(owner, "nil1")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
if len(data1) != 0 {
|
||||
t.Fatal("Expected nil data for nil value")
|
||||
}
|
||||
|
||||
data2, err := tr.read(owner, "nil2")
|
||||
if err != nil {
|
||||
data2, found, err := tr.read(owner, "nil2")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
if len(data2) != 0 {
|
||||
|
|
@ -481,8 +483,8 @@ func TestTrienodeHistoryReaderNilValues(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test reading non-nil value
|
||||
data3, err := tr.read(owner, "data1")
|
||||
if err != nil {
|
||||
data3, found, err := tr.read(owner, "data1")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("Failed to read non-nil value: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data3, []byte("some data")) {
|
||||
|
|
@ -498,7 +500,7 @@ func TestTrienodeHistoryReaderNilKey(t *testing.T) {
|
|||
|
||||
// Add some nil values
|
||||
nodes[owner][""] = []byte("some data")
|
||||
nodes[owner]["data1"] = []byte("some data")
|
||||
nodes[owner]["data1"] = []byte("some data1")
|
||||
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
|
||||
|
|
@ -516,7 +518,7 @@ func TestTrienodeHistoryReaderNilKey(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test reading nil values
|
||||
data1, err := tr.read(owner, "")
|
||||
data1, _, err := tr.read(owner, "")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read nil value: %v", err)
|
||||
}
|
||||
|
|
@ -525,11 +527,11 @@ func TestTrienodeHistoryReaderNilKey(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test reading non-nil value
|
||||
data2, err := tr.read(owner, "data1")
|
||||
data2, _, err := tr.read(owner, "data1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read non-nil value: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data2, []byte("some data")) {
|
||||
if !bytes.Equal(data2, []byte("some data1")) {
|
||||
t.Fatal("Data mismatch for non-nil key")
|
||||
}
|
||||
}
|
||||
|
|
@ -691,3 +693,61 @@ func testEncodeDecode(t *testing.T, h *trienodeHistory) {
|
|||
t.Fatal("Trienode content mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchSingle(t *testing.T) {
|
||||
nodes := make(map[common.Hash]map[string][]byte)
|
||||
ownerA, ownerB := testrand.Hash(), testrand.Hash()
|
||||
nodes[ownerA] = make(map[string][]byte)
|
||||
nodes[ownerB] = make(map[string][]byte)
|
||||
|
||||
for i := 0; i < trienodeDataBlockRestartLen*2; i++ {
|
||||
nodes[ownerA][fmt.Sprintf("%d", 2*i+1)] = testrand.Bytes(rand.Intn(5))
|
||||
nodes[ownerB][fmt.Sprintf("%d", 2*i+1)] = testrand.Bytes(rand.Intn(5))
|
||||
}
|
||||
h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes)
|
||||
|
||||
var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false)
|
||||
defer freezer.Close()
|
||||
|
||||
header, keySection, valueSection, _ := h.encode()
|
||||
if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil {
|
||||
t.Fatalf("Failed to write trienode history: %v", err)
|
||||
}
|
||||
|
||||
tr, err := newTrienodeHistoryReader(1, freezer)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct history reader: %v", err)
|
||||
}
|
||||
|
||||
// Test reading non-existent entry
|
||||
keys := []string{
|
||||
"0",
|
||||
"2",
|
||||
"30",
|
||||
"32",
|
||||
"64",
|
||||
"1000",
|
||||
}
|
||||
for _, key := range keys {
|
||||
_, found, err := tr.read(ownerA, key)
|
||||
if err != nil || found {
|
||||
t.Fatalf("Expected non-existent entry %v", err)
|
||||
}
|
||||
_, found, err = tr.read(ownerB, key)
|
||||
if err != nil || found {
|
||||
t.Fatalf("Expected non-existent entry %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for owner, subnodes := range nodes {
|
||||
for key, value := range subnodes {
|
||||
got, found, err := tr.read(owner, key)
|
||||
if err != nil || !found {
|
||||
t.Fatal("Failed to read trienode")
|
||||
}
|
||||
if bytes.Compare(got, value) != 0 {
|
||||
t.Fatalf("Unexpected value for key %v, got %v, expected %v", []byte(key), got, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue