mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
triedb/pathdb: optimize history inspection by selective decoding
This commit is contained in:
parent
8a171dce1f
commit
a281cbf8b1
2 changed files with 282 additions and 22 deletions
|
|
@ -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
|
||||||
|
}
|
||||||
|
if id == start {
|
||||||
|
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.Blocks = append(stats.Blocks, h.meta.block)
|
||||||
stats.Origins = append(stats.Origins, blob)
|
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
|
||||||
}
|
}
|
||||||
key := slotHash
|
for id := start; id <= end; id += 1 {
|
||||||
if h.meta.version != stateHistoryV0 {
|
h, err := readSlotHistory(freezer, id, targetAddr, targetSlot)
|
||||||
key = slot
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
blob, exists := slots[key]
|
if id == start {
|
||||||
if !exists {
|
stats.Start = h.meta.block
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if id == end {
|
||||||
|
stats.End = h.meta.block
|
||||||
|
}
|
||||||
|
|
||||||
|
if slots, exists := h.storages[targetAddr]; exists {
|
||||||
|
for _, blob := range slots {
|
||||||
stats.Blocks = append(stats.Blocks, h.meta.block)
|
stats.Blocks = append(stats.Blocks, h.meta.block)
|
||||||
stats.Origins = append(stats.Origins, blob)
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue