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"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"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.
|
||||
func accountHistory(freezer ethdb.AncientReader, address common.Address, start, end uint64) (*HistoryStats, error) {
|
||||
return inspectHistory(freezer, start, end, func(h *stateHistory, stats *HistoryStats) {
|
||||
blob, exists := h.accounts[address]
|
||||
if !exists {
|
||||
return
|
||||
return inspectAccountHistory(freezer, address, start, end)
|
||||
}
|
||||
|
||||
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.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.
|
||||
func storageHistory(freezer ethdb.AncientReader, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) {
|
||||
slotHash := crypto.Keccak256Hash(slot.Bytes())
|
||||
return inspectHistory(freezer, start, end, func(h *stateHistory, stats *HistoryStats) {
|
||||
slots, exists := h.storages[address]
|
||||
if !exists {
|
||||
return
|
||||
return inspectStorageHistory(freezer, address, slot, start, end)
|
||||
}
|
||||
key := slotHash
|
||||
if h.meta.version != stateHistoryV0 {
|
||||
key = slot
|
||||
|
||||
// inspectStorageHistory efficiently inspects storage history by only decoding the target account's storage.
|
||||
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
|
||||
}
|
||||
blob, exists := slots[key]
|
||||
if !exists {
|
||||
return
|
||||
for id := start; id <= end; id += 1 {
|
||||
h, err := readSlotHistory(freezer, id, targetAddr, targetSlot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if id == start {
|
||||
stats.Start = h.meta.block
|
||||
}
|
||||
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.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.
|
||||
|
|
|
|||
|
|
@ -535,6 +535,178 @@ func (h *stateHistory) decode(accountData, storageData, accountIndexes, storageI
|
|||
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.
|
||||
func readStateHistoryMeta(reader ethdb.AncientReader, id uint64) (*meta, error) {
|
||||
data := rawdb.ReadStateHistoryMeta(reader, id)
|
||||
|
|
@ -566,6 +738,40 @@ func readStateHistory(reader ethdb.AncientReader, id uint64) (*stateHistory, err
|
|||
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.
|
||||
func readStateHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]history, error) {
|
||||
var histories []history
|
||||
|
|
|
|||
Loading…
Reference in a new issue