548 lines
18 KiB
Go
548 lines
18 KiB
Go
// Copyright 2024 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
package pathdb
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/metrics"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
// counter helps in tracking items and their corresponding sizes.
|
|
type counter struct {
|
|
n int
|
|
size int
|
|
}
|
|
|
|
// add size to the counter and increase the item counter.
|
|
func (c *counter) add(size int) {
|
|
c.n++
|
|
c.size += size
|
|
}
|
|
|
|
// report uploads the cached statistics to meters.
|
|
func (c *counter) report(count metrics.Meter, size metrics.Meter) {
|
|
count.Mark(int64(c.n))
|
|
size.Mark(int64(c.size))
|
|
}
|
|
|
|
// stateSet represents a collection of state modifications associated with a
|
|
// transition (e.g., a block execution) or multiple aggregated transitions.
|
|
//
|
|
// A stateSet can only reside within a diffLayer or the buffer of a diskLayer,
|
|
// serving as the envelope for the set. Lock protection is not required for
|
|
// accessing or mutating the account set and storage set, as the associated
|
|
// envelope is always marked as stale before any mutation is applied. Any
|
|
// subsequent state access will be denied due to the stale flag. Therefore,
|
|
// state access and mutation won't happen at the same time with guarantee.
|
|
type stateSet struct {
|
|
accountData map[common.Hash][]byte // Keyed accounts for direct retrieval (nil means deleted)
|
|
storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted)
|
|
size uint64 // Memory size of the state data (accountData and storageData)
|
|
|
|
accountListSorted []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil
|
|
storageListSorted map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil
|
|
|
|
// Lock for guarding the two lists above. These lists might be accessed
|
|
// concurrently and lock protection is essential to avoid concurrent
|
|
// slice or map read/write.
|
|
listLock sync.RWMutex
|
|
}
|
|
|
|
// newStates constructs the state set with the provided account and storage data.
|
|
func newStates(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *stateSet {
|
|
// Don't panic for the lazy callers, initialize the nil maps instead.
|
|
if accounts == nil {
|
|
accounts = make(map[common.Hash][]byte)
|
|
}
|
|
if storages == nil {
|
|
storages = make(map[common.Hash]map[common.Hash][]byte)
|
|
}
|
|
s := &stateSet{
|
|
accountData: accounts,
|
|
storageData: storages,
|
|
storageListSorted: make(map[common.Hash][]common.Hash),
|
|
}
|
|
s.size = s.check()
|
|
return s
|
|
}
|
|
|
|
// account returns the account data associated with the specified address hash.
|
|
func (s *stateSet) account(hash common.Hash) ([]byte, bool) {
|
|
// If the account is known locally, return it
|
|
if data, ok := s.accountData[hash]; ok {
|
|
return data, true
|
|
}
|
|
return nil, false // account is unknown in this set
|
|
}
|
|
|
|
// storage returns the storage slot associated with the specified address hash
|
|
// and storage key hash.
|
|
func (s *stateSet) storage(accountHash, storageHash common.Hash) ([]byte, bool) {
|
|
// If the account is known locally, try to resolve the slot locally
|
|
if storage, ok := s.storageData[accountHash]; ok {
|
|
if data, ok := storage[storageHash]; ok {
|
|
return data, true
|
|
}
|
|
}
|
|
return nil, false // storage is unknown in this set
|
|
}
|
|
|
|
// check sanitizes accounts and storage slots to ensure the data validity.
|
|
// Additionally, it computes the total memory size occupied by the maps.
|
|
func (s *stateSet) check() uint64 {
|
|
var size int
|
|
for _, blob := range s.accountData {
|
|
size += common.HashLength + len(blob)
|
|
}
|
|
for accountHash, slots := range s.storageData {
|
|
if slots == nil {
|
|
panic(fmt.Sprintf("storage %#x nil", accountHash)) // nil slots is not permitted
|
|
}
|
|
for _, blob := range slots {
|
|
size += 2*common.HashLength + len(blob)
|
|
}
|
|
}
|
|
return uint64(size)
|
|
}
|
|
|
|
// accountList returns a sorted list of all accounts in this state set, including
|
|
// the deleted ones.
|
|
//
|
|
// Note, the returned slice is not a copy, so do not modify it.
|
|
//
|
|
// nolint:unused
|
|
func (s *stateSet) accountList() []common.Hash {
|
|
// If an old list already exists, return it
|
|
s.listLock.RLock()
|
|
list := s.accountListSorted
|
|
s.listLock.RUnlock()
|
|
|
|
if list != nil {
|
|
return list
|
|
}
|
|
// No old sorted account list exists, generate a new one. It's possible that
|
|
// multiple threads waiting for the write lock may regenerate the list
|
|
// multiple times, which is acceptable.
|
|
s.listLock.Lock()
|
|
defer s.listLock.Unlock()
|
|
|
|
list = maps.Keys(s.accountData)
|
|
slices.SortFunc(list, common.Hash.Cmp)
|
|
s.accountListSorted = list
|
|
return list
|
|
}
|
|
|
|
// StorageList returns a sorted list of all storage slot hashes in this state set
|
|
// for the given account. The returned list will include the hash of deleted
|
|
// storage slot.
|
|
//
|
|
// Note, the returned slice is not a copy, so do not modify it.
|
|
//
|
|
// nolint:unused
|
|
func (s *stateSet) storageList(accountHash common.Hash) []common.Hash {
|
|
s.listLock.RLock()
|
|
if _, ok := s.storageData[accountHash]; !ok {
|
|
// Account not tracked by this layer
|
|
s.listLock.RUnlock()
|
|
return nil
|
|
}
|
|
// If an old list already exists, return it
|
|
if list, exist := s.storageListSorted[accountHash]; exist {
|
|
s.listLock.RUnlock()
|
|
return list // the cached list can't be nil
|
|
}
|
|
s.listLock.RUnlock()
|
|
|
|
// No old sorted account list exists, generate a new one. It's possible that
|
|
// multiple threads waiting for the write lock may regenerate the list
|
|
// multiple times, which is acceptable.
|
|
s.listLock.Lock()
|
|
defer s.listLock.Unlock()
|
|
|
|
list := maps.Keys(s.storageData[accountHash])
|
|
slices.SortFunc(list, common.Hash.Cmp)
|
|
s.storageListSorted[accountHash] = list
|
|
return list
|
|
}
|
|
|
|
// clearLists invalidates the cached account list and storage lists.
|
|
func (s *stateSet) clearLists() {
|
|
s.listLock.Lock()
|
|
defer s.listLock.Unlock()
|
|
|
|
s.accountListSorted = nil
|
|
s.storageListSorted = make(map[common.Hash][]common.Hash)
|
|
}
|
|
|
|
// merge integrates the accounts and storages from the external set into the
|
|
// local set, ensuring the combined set reflects the combined state of both.
|
|
//
|
|
// The stateSet supplied as parameter set will not be mutated by this operation,
|
|
// as it may still be referenced by other layers.
|
|
func (s *stateSet) merge(other *stateSet) {
|
|
var (
|
|
delta int
|
|
accountOverwrites counter
|
|
storageOverwrites counter
|
|
)
|
|
// Apply the updated account data
|
|
for accountHash, data := range other.accountData {
|
|
if origin, ok := s.accountData[accountHash]; ok {
|
|
delta += len(data) - len(origin)
|
|
accountOverwrites.add(common.HashLength + len(origin))
|
|
} else {
|
|
delta += common.HashLength + len(data)
|
|
}
|
|
s.accountData[accountHash] = data
|
|
}
|
|
// Apply all the updated storage slots (individually)
|
|
for accountHash, storage := range other.storageData {
|
|
// If storage didn't exist in the set, overwrite blindly
|
|
if _, ok := s.storageData[accountHash]; !ok {
|
|
// To prevent potential concurrent map read/write issues, allocate a
|
|
// new map for the storage instead of claiming it directly from the
|
|
// passed external set. Even after merging, the slots belonging to the
|
|
// external state set remain accessible, so ownership of the map should
|
|
// not be taken, and any mutation on it should be avoided.
|
|
slots := make(map[common.Hash][]byte, len(storage))
|
|
for storageHash, data := range storage {
|
|
slots[storageHash] = data
|
|
delta += 2*common.HashLength + len(data)
|
|
}
|
|
s.storageData[accountHash] = slots
|
|
continue
|
|
}
|
|
// Storage exists in both local and external set, merge the slots
|
|
slots := s.storageData[accountHash]
|
|
for storageHash, data := range storage {
|
|
if origin, ok := slots[storageHash]; ok {
|
|
delta += len(data) - len(origin)
|
|
storageOverwrites.add(2*common.HashLength + len(origin))
|
|
} else {
|
|
delta += 2*common.HashLength + len(data)
|
|
}
|
|
slots[storageHash] = data
|
|
}
|
|
}
|
|
accountOverwrites.report(gcAccountMeter, gcAccountBytesMeter)
|
|
storageOverwrites.report(gcStorageMeter, gcStorageBytesMeter)
|
|
s.clearLists()
|
|
s.updateSize(delta)
|
|
}
|
|
|
|
// revertTo takes the original value of accounts and storages as input and reverts
|
|
// the latest state transition applied on the state set.
|
|
//
|
|
// Notably, this operation may result in the set containing more entries after a
|
|
// revert. For example, if account x did not exist and was created during transition
|
|
// w, reverting w will retain an x=nil entry in the set. And also if account x along
|
|
// with its storage slots was deleted in the transition w, reverting w will retain
|
|
// a list of additional storage slots with their original value.
|
|
func (s *stateSet) revertTo(accountOrigin map[common.Hash][]byte, storageOrigin map[common.Hash]map[common.Hash][]byte) {
|
|
var delta int // size tracking
|
|
for addrHash, blob := range accountOrigin {
|
|
data, ok := s.accountData[addrHash]
|
|
if !ok {
|
|
panic(fmt.Sprintf("non-existent account for reverting, %x", addrHash))
|
|
}
|
|
if len(data) == 0 && len(blob) == 0 {
|
|
panic(fmt.Sprintf("invalid account mutation (null to null), %x", addrHash))
|
|
}
|
|
delta += len(blob) - len(data)
|
|
s.accountData[addrHash] = blob
|
|
}
|
|
// Overwrite the storage data with original value blindly
|
|
for addrHash, storage := range storageOrigin {
|
|
slots := s.storageData[addrHash]
|
|
if len(slots) == 0 {
|
|
panic(fmt.Sprintf("non-existent storage set for reverting, %x", addrHash))
|
|
}
|
|
for storageHash, blob := range storage {
|
|
data, ok := slots[storageHash]
|
|
if !ok {
|
|
panic(fmt.Sprintf("non-existent storage slot for reverting, %x-%x", addrHash, storageHash))
|
|
}
|
|
if len(blob) == 0 && len(data) == 0 {
|
|
panic(fmt.Sprintf("invalid storage slot mutation (null to null), %x-%x", addrHash, storageHash))
|
|
}
|
|
delta += len(blob) - len(data)
|
|
slots[storageHash] = blob
|
|
}
|
|
}
|
|
s.clearLists()
|
|
s.updateSize(delta)
|
|
}
|
|
|
|
// updateSize updates the total cache size by the given delta.
|
|
func (s *stateSet) updateSize(delta int) {
|
|
size := int64(s.size) + int64(delta)
|
|
if size >= 0 {
|
|
s.size = uint64(size)
|
|
return
|
|
}
|
|
log.Error("Stateset size underflow", "prev", common.StorageSize(s.size), "delta", common.StorageSize(delta))
|
|
s.size = 0
|
|
}
|
|
|
|
// encode serializes the content of state set into the provided writer.
|
|
func (s *stateSet) encode(w io.Writer) error {
|
|
// Encode accounts
|
|
type accounts struct {
|
|
AddrHashes []common.Hash
|
|
Accounts [][]byte
|
|
}
|
|
var enc accounts
|
|
for addrHash, blob := range s.accountData {
|
|
enc.AddrHashes = append(enc.AddrHashes, addrHash)
|
|
enc.Accounts = append(enc.Accounts, blob)
|
|
}
|
|
if err := rlp.Encode(w, enc); err != nil {
|
|
return err
|
|
}
|
|
// Encode storages
|
|
type Storage struct {
|
|
AddrHash common.Hash
|
|
Keys []common.Hash
|
|
Vals [][]byte
|
|
}
|
|
storages := make([]Storage, 0, len(s.storageData))
|
|
for addrHash, slots := range s.storageData {
|
|
keys := make([]common.Hash, 0, len(slots))
|
|
vals := make([][]byte, 0, len(slots))
|
|
for key, val := range slots {
|
|
keys = append(keys, key)
|
|
vals = append(vals, val)
|
|
}
|
|
storages = append(storages, Storage{
|
|
AddrHash: addrHash,
|
|
Keys: keys,
|
|
Vals: vals,
|
|
})
|
|
}
|
|
return rlp.Encode(w, storages)
|
|
}
|
|
|
|
// decode deserializes the content from the rlp stream into the state set.
|
|
func (s *stateSet) decode(r *rlp.Stream) error {
|
|
type accounts struct {
|
|
AddrHashes []common.Hash
|
|
Accounts [][]byte
|
|
}
|
|
var (
|
|
dec accounts
|
|
accountSet = make(map[common.Hash][]byte)
|
|
)
|
|
if err := r.Decode(&dec); err != nil {
|
|
return fmt.Errorf("load diff accounts: %v", err)
|
|
}
|
|
for i := 0; i < len(dec.AddrHashes); i++ {
|
|
accountSet[dec.AddrHashes[i]] = dec.Accounts[i]
|
|
}
|
|
s.accountData = accountSet
|
|
|
|
// Decode storages
|
|
type storage struct {
|
|
AddrHash common.Hash
|
|
Keys []common.Hash
|
|
Vals [][]byte
|
|
}
|
|
var (
|
|
storages []storage
|
|
storageSet = make(map[common.Hash]map[common.Hash][]byte)
|
|
)
|
|
if err := r.Decode(&storages); err != nil {
|
|
return fmt.Errorf("load diff storage: %v", err)
|
|
}
|
|
for _, entry := range storages {
|
|
storageSet[entry.AddrHash] = make(map[common.Hash][]byte, len(entry.Keys))
|
|
for i := 0; i < len(entry.Keys); i++ {
|
|
storageSet[entry.AddrHash][entry.Keys[i]] = entry.Vals[i]
|
|
}
|
|
}
|
|
s.storageData = storageSet
|
|
s.storageListSorted = make(map[common.Hash][]common.Hash)
|
|
|
|
s.size = s.check()
|
|
return nil
|
|
}
|
|
|
|
// reset clears all cached state data, including any optional sorted lists that
|
|
// may have been generated.
|
|
func (s *stateSet) reset() {
|
|
s.accountData = make(map[common.Hash][]byte)
|
|
s.storageData = make(map[common.Hash]map[common.Hash][]byte)
|
|
s.size = 0
|
|
s.accountListSorted = nil
|
|
s.storageListSorted = make(map[common.Hash][]common.Hash)
|
|
}
|
|
|
|
// dbsize returns the approximate size for db write.
|
|
//
|
|
// nolint:unused
|
|
func (s *stateSet) dbsize() int {
|
|
m := len(s.accountData) * len(rawdb.SnapshotAccountPrefix)
|
|
for _, slots := range s.storageData {
|
|
m += len(slots) * len(rawdb.SnapshotStoragePrefix)
|
|
}
|
|
return m + int(s.size)
|
|
}
|
|
|
|
// StateSetWithOrigin wraps the state set with additional original values of the
|
|
// mutated states.
|
|
type StateSetWithOrigin struct {
|
|
*stateSet
|
|
|
|
// AccountOrigin represents the account data before the state transition,
|
|
// corresponding to both the accountData and destructSet. It's keyed by the
|
|
// account address. The nil value means the account was not present before.
|
|
accountOrigin map[common.Address][]byte
|
|
|
|
// StorageOrigin represents the storage data before the state transition,
|
|
// corresponding to storageData and deleted slots of destructSet. It's keyed
|
|
// by the account address and slot key hash. The nil value means the slot was
|
|
// not present.
|
|
storageOrigin map[common.Address]map[common.Hash][]byte
|
|
|
|
// Memory size of the state data (accountOrigin and storageOrigin)
|
|
size uint64
|
|
}
|
|
|
|
// NewStateSetWithOrigin constructs the state set with the provided data.
|
|
func NewStateSetWithOrigin(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte, accountOrigin map[common.Address][]byte, storageOrigin map[common.Address]map[common.Hash][]byte) *StateSetWithOrigin {
|
|
// Don't panic for the lazy callers, initialize the nil maps instead.
|
|
if accountOrigin == nil {
|
|
accountOrigin = make(map[common.Address][]byte)
|
|
}
|
|
if storageOrigin == nil {
|
|
storageOrigin = make(map[common.Address]map[common.Hash][]byte)
|
|
}
|
|
// Count the memory size occupied by the set. Note that each slot key here
|
|
// uses 2*common.HashLength to keep consistent with the calculation method
|
|
// of stateSet.
|
|
var size int
|
|
for _, data := range accountOrigin {
|
|
size += common.HashLength + len(data)
|
|
}
|
|
for _, slots := range storageOrigin {
|
|
for _, data := range slots {
|
|
size += 2*common.HashLength + len(data)
|
|
}
|
|
}
|
|
set := newStates(accounts, storages)
|
|
return &StateSetWithOrigin{
|
|
stateSet: set,
|
|
accountOrigin: accountOrigin,
|
|
storageOrigin: storageOrigin,
|
|
size: set.size + uint64(size),
|
|
}
|
|
}
|
|
|
|
// encode serializes the content of state set into the provided writer.
|
|
func (s *StateSetWithOrigin) encode(w io.Writer) error {
|
|
// Encode state set
|
|
if err := s.stateSet.encode(w); err != nil {
|
|
return err
|
|
}
|
|
// Encode accounts
|
|
type Accounts struct {
|
|
Addresses []common.Address
|
|
Accounts [][]byte
|
|
}
|
|
var accounts Accounts
|
|
for address, blob := range s.accountOrigin {
|
|
accounts.Addresses = append(accounts.Addresses, address)
|
|
accounts.Accounts = append(accounts.Accounts, blob)
|
|
}
|
|
if err := rlp.Encode(w, accounts); err != nil {
|
|
return err
|
|
}
|
|
// Encode storages
|
|
type Storage struct {
|
|
Address common.Address
|
|
Keys []common.Hash
|
|
Vals [][]byte
|
|
}
|
|
storages := make([]Storage, 0, len(s.storageOrigin))
|
|
for address, slots := range s.storageOrigin {
|
|
keys := make([]common.Hash, 0, len(slots))
|
|
vals := make([][]byte, 0, len(slots))
|
|
for key, val := range slots {
|
|
keys = append(keys, key)
|
|
vals = append(vals, val)
|
|
}
|
|
storages = append(storages, Storage{Address: address, Keys: keys, Vals: vals})
|
|
}
|
|
return rlp.Encode(w, storages)
|
|
}
|
|
|
|
// decode deserializes the content from the rlp stream into the state set.
|
|
func (s *StateSetWithOrigin) decode(r *rlp.Stream) error {
|
|
if s.stateSet == nil {
|
|
s.stateSet = &stateSet{}
|
|
}
|
|
if err := s.stateSet.decode(r); err != nil {
|
|
return err
|
|
}
|
|
// Decode account origin
|
|
type Accounts struct {
|
|
Addresses []common.Address
|
|
Accounts [][]byte
|
|
}
|
|
var (
|
|
accounts Accounts
|
|
accountSet = make(map[common.Address][]byte)
|
|
)
|
|
if err := r.Decode(&accounts); err != nil {
|
|
return fmt.Errorf("load diff account origin set: %v", err)
|
|
}
|
|
for i := 0; i < len(accounts.Accounts); i++ {
|
|
accountSet[accounts.Addresses[i]] = accounts.Accounts[i]
|
|
}
|
|
s.accountOrigin = accountSet
|
|
|
|
// Decode storage origin
|
|
type Storage struct {
|
|
Address common.Address
|
|
Keys []common.Hash
|
|
Vals [][]byte
|
|
}
|
|
var (
|
|
storages []Storage
|
|
storageSet = make(map[common.Address]map[common.Hash][]byte)
|
|
)
|
|
if err := r.Decode(&storages); err != nil {
|
|
return fmt.Errorf("load diff storage origin: %v", err)
|
|
}
|
|
for _, storage := range storages {
|
|
storageSet[storage.Address] = make(map[common.Hash][]byte)
|
|
for i := 0; i < len(storage.Keys); i++ {
|
|
storageSet[storage.Address][storage.Keys[i]] = storage.Vals[i]
|
|
}
|
|
}
|
|
s.storageOrigin = storageSet
|
|
return nil
|
|
}
|