triedb/pathdb: optimize history inspection by selective decoding

This commit is contained in:
allen 2025-09-18 11:30:44 -04:00
parent 8a171dce1f
commit a281cbf8b1
2 changed files with 282 additions and 22 deletions

View file

@ -21,7 +21,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
@ -98,35 +97,90 @@ func inspectHistory(freezer ethdb.AncientReader, start, end uint64, onHistory fu
// accountHistory inspects the account history within the range. // accountHistory inspects the account history within the range.
func accountHistory(freezer ethdb.AncientReader, address common.Address, start, end uint64) (*HistoryStats, error) { func accountHistory(freezer ethdb.AncientReader, address common.Address, start, end uint64) (*HistoryStats, error) {
return inspectHistory(freezer, start, end, func(h *stateHistory, stats *HistoryStats) { return inspectAccountHistory(freezer, address, start, end)
blob, exists := h.accounts[address] }
if !exists {
return func inspectAccountHistory(freezer ethdb.AncientReader, targetAddr common.Address, start, end uint64) (*HistoryStats, error) {
var (
stats = &HistoryStats{}
init = time.Now()
logged = time.Now()
)
start, end, err := sanitizeRange(start, end, freezer)
if err != nil {
return nil, err
}
for id := start; id <= end; id += 1 {
h, err := readAccountHistory(freezer, id, targetAddr)
if err != nil {
return nil, err
} }
stats.Blocks = append(stats.Blocks, h.meta.block) if id == start {
stats.Origins = append(stats.Origins, blob) stats.Start = h.meta.block
}) }
if id == end {
stats.End = h.meta.block
}
if blob, exists := h.accounts[targetAddr]; exists {
stats.Blocks = append(stats.Blocks, h.meta.block)
stats.Origins = append(stats.Origins, blob)
}
if time.Since(logged) > time.Second*8 {
logged = time.Now()
eta := float64(time.Since(init)) / float64(id-start+1) * float64(end-id)
log.Info("Inspecting account history", "checked", id-start+1, "left", end-id, "elapsed", common.PrettyDuration(time.Since(init)), "eta", common.PrettyDuration(eta))
}
}
log.Info("Inspected account history", "total", end-start+1, "elapsed", common.PrettyDuration(time.Since(init)))
return stats, nil
} }
// storageHistory inspects the storage history within the range. // storageHistory inspects the storage history within the range.
func storageHistory(freezer ethdb.AncientReader, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) { func storageHistory(freezer ethdb.AncientReader, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) {
slotHash := crypto.Keccak256Hash(slot.Bytes()) return inspectStorageHistory(freezer, address, slot, start, end)
return inspectHistory(freezer, start, end, func(h *stateHistory, stats *HistoryStats) { }
slots, exists := h.storages[address]
if !exists { // inspectStorageHistory efficiently inspects storage history by only decoding the target account's storage.
return func inspectStorageHistory(freezer ethdb.AncientReader, targetAddr common.Address, targetSlot common.Hash, start, end uint64) (*HistoryStats, error) {
var (
stats = &HistoryStats{}
init = time.Now()
logged = time.Now()
)
start, end, err := sanitizeRange(start, end, freezer)
if err != nil {
return nil, err
}
for id := start; id <= end; id += 1 {
h, err := readSlotHistory(freezer, id, targetAddr, targetSlot)
if err != nil {
return nil, err
} }
key := slotHash if id == start {
if h.meta.version != stateHistoryV0 { stats.Start = h.meta.block
key = slot
} }
blob, exists := slots[key] if id == end {
if !exists { stats.End = h.meta.block
return
} }
stats.Blocks = append(stats.Blocks, h.meta.block)
stats.Origins = append(stats.Origins, blob) if slots, exists := h.storages[targetAddr]; exists {
}) for _, blob := range slots {
stats.Blocks = append(stats.Blocks, h.meta.block)
stats.Origins = append(stats.Origins, blob)
break
}
}
if time.Since(logged) > time.Second*8 {
logged = time.Now()
eta := float64(time.Since(init)) / float64(id-start+1) * float64(end-id)
log.Info("Inspecting storage history", "checked", id-start+1, "left", end-id, "elapsed", common.PrettyDuration(time.Since(init)), "eta", common.PrettyDuration(eta))
}
}
log.Info("Inspected storage history", "total", end-start+1, "elapsed", common.PrettyDuration(time.Since(init)))
return stats, nil
} }
// historyRange returns the block number range of local state histories. // historyRange returns the block number range of local state histories.

View file

@ -535,6 +535,178 @@ func (h *stateHistory) decode(accountData, storageData, accountIndexes, storageI
return nil return nil
} }
// decodeAccount selectively decodes only the specified account from the byte streams.
func (h *stateHistory) decodeAccount(accountData, accountIndexes []byte, targetAddr common.Address) error {
h.initializeEmptyState()
count := len(accountIndexes) / accountIndexSize
if count == 0 {
return nil
}
if err := (&decoder{accountData: accountData, accountIndexes: accountIndexes}).verify(); err != nil {
return err
}
targetIndex := h.binarySearchAccount(accountIndexes, count, targetAddr)
if targetIndex < 0 {
return nil
}
return h.extractAccountData(accountData, accountIndexes, targetIndex, targetAddr)
}
// initializeEmptyState initializes all state maps to empty
func (h *stateHistory) initializeEmptyState() {
h.accounts = make(map[common.Address][]byte)
h.accountList = []common.Address{}
h.storages = make(map[common.Address]map[common.Hash][]byte)
h.storageList = make(map[common.Address][]common.Hash)
}
// binarySearchAccount performs binary search to find the target account index
func (h *stateHistory) binarySearchAccount(accountIndexes []byte, count int, targetAddr common.Address) int {
left, right := 0, count-1
for left <= right {
mid := (left + right) / 2
var index accountIndex
index.decode(accountIndexes[mid*accountIndexSize : (mid+1)*accountIndexSize])
switch bytes.Compare(index.address.Bytes(), targetAddr.Bytes()) {
case 0:
return mid
case -1:
left = mid + 1
case 1:
right = mid - 1
}
}
return -1
}
// extractAccountData extracts and sets account data for the target account
func (h *stateHistory) extractAccountData(accountData, accountIndexes []byte, targetIndex int, targetAddr common.Address) error {
var index accountIndex
index.decode(accountIndexes[targetIndex*accountIndexSize : (targetIndex+1)*accountIndexSize])
end := index.offset + uint32(index.length)
if uint32(len(accountData)) < end {
return errors.New("account data buffer is corrupted")
}
h.accounts[targetAddr] = accountData[index.offset:end]
h.accountList = []common.Address{targetAddr}
return nil
}
// decodeStorage decodes the specified account's storage slot.
func (h *stateHistory) decodeStorage(accountIndexes, storageIndexes, storageData []byte, targetAddr common.Address, targetSlot common.Hash) error {
h.initializeEmptyState()
count := len(accountIndexes) / accountIndexSize
if count == 0 {
return nil
}
targetAccountIdx := h.binarySearchAccount(accountIndexes, count, targetAddr)
if targetAccountIdx < 0 {
return nil
}
var accountIdx accountIndex
accountIdx.decode(accountIndexes[targetAccountIdx*accountIndexSize : (targetAccountIdx+1)*accountIndexSize])
if accountIdx.storageSlots == 0 {
return nil
}
return h.extractStorageSlot(storageIndexes, storageData, &accountIdx, targetAddr, targetSlot)
}
// extractStorageSlot finds and extracts the target storage slot data using binary search
func (h *stateHistory) extractStorageSlot(storageIndexes, storageData []byte, accountIdx *accountIndex, targetAddr common.Address, targetSlot common.Hash) error {
slotPos := h.binarySearchStorageSlot(storageIndexes, accountIdx, targetSlot)
if slotPos < 0 {
return nil
}
slotIdx := h.getSlotIndex(storageIndexes, accountIdx.storageOffset+uint32(slotPos))
if slotIdx == nil {
return nil
}
return h.setStorageData(storageData, slotIdx, targetAddr)
}
// binarySearchStorageSlot performs binary search to find the target storage slot index within an account's slots
func (h *stateHistory) binarySearchStorageSlot(storageIndexes []byte, accountIdx *accountIndex, targetSlot common.Hash) int {
left, right := 0, int(accountIdx.storageSlots)-1
for left <= right {
mid := (left + right) / 2
slotIdx := h.getSlotIndex(storageIndexes, accountIdx.storageOffset+uint32(mid))
if slotIdx == nil {
return -1
}
var cmp int
if h.meta.version == stateHistoryV0 {
targetSlotHash := crypto.Keccak256Hash(targetSlot.Bytes())
cmp = bytes.Compare(slotIdx.id.Bytes(), targetSlotHash.Bytes())
} else {
cmp = bytes.Compare(slotIdx.id.Bytes(), targetSlot.Bytes())
}
switch cmp {
case 0:
return mid
case -1:
left = mid + 1
case 1:
right = mid - 1
}
}
return -1
}
// getSlotIndex safely extracts a slot index at the given position
func (h *stateHistory) getSlotIndex(storageIndexes []byte, position uint32) *slotIndex {
start := position * uint32(slotIndexSize)
end := start + uint32(slotIndexSize)
if uint32(len(storageIndexes)) < end {
return nil
}
var index slotIndex
index.decode(storageIndexes[start:end])
return &index
}
// isTargetSlot checks if the slot index matches the target slot (handles v0/v1 compatibility)
func (h *stateHistory) isTargetSlot(slotIdx *slotIndex, targetSlot common.Hash) bool {
if h.meta.version == stateHistoryV0 {
return slotIdx.id == crypto.Keccak256Hash(targetSlot.Bytes())
}
return slotIdx.id == targetSlot
}
// setStorageData safely extracts and sets the storage data
func (h *stateHistory) setStorageData(storageData []byte, slotIdx *slotIndex, targetAddr common.Address) error {
end := slotIdx.offset + uint32(slotIdx.length)
if uint32(len(storageData)) < end {
return errors.New("storage data buffer is corrupted")
}
h.storages = map[common.Address]map[common.Hash][]byte{
targetAddr: {slotIdx.id: storageData[slotIdx.offset:end]},
}
h.storageList = map[common.Address][]common.Hash{
targetAddr: {slotIdx.id},
}
return nil
}
// readStateHistoryMeta reads the metadata of state history with the specified id. // readStateHistoryMeta reads the metadata of state history with the specified id.
func readStateHistoryMeta(reader ethdb.AncientReader, id uint64) (*meta, error) { func readStateHistoryMeta(reader ethdb.AncientReader, id uint64) (*meta, error) {
data := rawdb.ReadStateHistoryMeta(reader, id) data := rawdb.ReadStateHistoryMeta(reader, id)
@ -566,6 +738,40 @@ func readStateHistory(reader ethdb.AncientReader, id uint64) (*stateHistory, err
return &h, nil return &h, nil
} }
// readAccountHistory reads account state history
func readAccountHistory(reader ethdb.AncientReader, id uint64, targetAddr common.Address) (*stateHistory, error) {
mData, accountIndexes, _, accountData, _, err := rawdb.ReadStateHistory(reader, id)
if err != nil {
return nil, err
}
var m meta
if err := m.decode(mData); err != nil {
return nil, err
}
h := stateHistory{meta: &m}
if err := h.decodeAccount(accountData, accountIndexes, targetAddr); err != nil {
return nil, err
}
return &h, nil
}
// readSlotHistory reads slot state history
func readSlotHistory(reader ethdb.AncientReader, id uint64, targetAddr common.Address, targetSlot common.Hash) (*stateHistory, error) {
mData, accountIndexes, storageIndexes, _, storageData, err := rawdb.ReadStateHistory(reader, id)
if err != nil {
return nil, err
}
var m meta
if err := m.decode(mData); err != nil {
return nil, err
}
h := stateHistory{meta: &m}
if err := h.decodeStorage(accountIndexes, storageIndexes, storageData, targetAddr, targetSlot); err != nil {
return nil, err
}
return &h, nil
}
// readStateHistories reads a list of state history records within the specified range. // readStateHistories reads a list of state history records within the specified range.
func readStateHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]history, error) { func readStateHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]history, error) {
var histories []history var histories []history