2019-08-06 05:40:28 -05:00
|
|
|
// Copyright 2019 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 snapshot
|
|
|
|
|
|
|
|
import (
|
2019-11-26 01:48:29 -06:00
|
|
|
"encoding/binary"
|
2019-08-06 05:40:28 -05:00
|
|
|
"fmt"
|
2019-11-26 01:48:29 -06:00
|
|
|
"math"
|
2019-12-02 02:31:07 -06:00
|
|
|
"math/rand"
|
2024-03-25 01:50:18 -05:00
|
|
|
"slices"
|
2019-08-06 05:40:28 -05:00
|
|
|
"sync"
|
2020-01-19 13:57:56 -06:00
|
|
|
"sync/atomic"
|
2019-11-26 01:48:29 -06:00
|
|
|
"time"
|
2019-08-06 05:40:28 -05:00
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2023-06-06 03:17:39 -05:00
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
2019-08-06 05:40:28 -05:00
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
2021-01-12 10:39:31 -06:00
|
|
|
bloomfilter "github.com/holiman/bloomfilter/v2"
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
"golang.org/x/exp/maps"
|
2019-11-26 01:48:29 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// aggregatorMemoryLimit is the maximum size of the bottom-most diff layer
|
|
|
|
// that aggregates the writes from above until it's flushed into the disk
|
|
|
|
// layer.
|
|
|
|
//
|
|
|
|
// Note, bumping this up might drastically increase the size of the bloom
|
|
|
|
// filters that's stored in every diff layer. Don't do that without fully
|
|
|
|
// understanding all the implications.
|
|
|
|
aggregatorMemoryLimit = uint64(4 * 1024 * 1024)
|
|
|
|
|
|
|
|
// aggregatorItemLimit is an approximate number of items that will end up
|
2024-02-05 15:16:32 -06:00
|
|
|
// in the aggregator layer before it's flushed out to disk. A plain account
|
2019-12-03 02:00:26 -06:00
|
|
|
// weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot
|
2021-01-07 00:36:21 -06:00
|
|
|
// 0B (+hash). Slots are mostly set/unset in lockstep, so that average at
|
2019-12-03 02:00:26 -06:00
|
|
|
// 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a
|
|
|
|
// smaller number to be on the safe side.
|
|
|
|
aggregatorItemLimit = aggregatorMemoryLimit / 42
|
2019-11-26 01:48:29 -06:00
|
|
|
|
|
|
|
// bloomTargetError is the target false positive rate when the aggregator
|
|
|
|
// layer is at its fullest. The actual value will probably move around up
|
|
|
|
// and down from this number, it's mostly a ballpark figure.
|
|
|
|
//
|
|
|
|
// Note, dropping this down might drastically increase the size of the bloom
|
|
|
|
// filters that's stored in every diff layer. Don't do that without fully
|
|
|
|
// understanding all the implications.
|
|
|
|
bloomTargetError = 0.02
|
|
|
|
|
|
|
|
// bloomSize is the ideal bloom filter size given the maximum number of items
|
|
|
|
// it's expected to hold and the target false positive error rate.
|
|
|
|
bloomSize = math.Ceil(float64(aggregatorItemLimit) * math.Log(bloomTargetError) / math.Log(1/math.Pow(2, math.Log(2))))
|
|
|
|
|
|
|
|
// bloomFuncs is the ideal number of bits a single entry should set in the
|
|
|
|
// bloom filter to keep its size to a minimum (given it's size and maximum
|
|
|
|
// entry count).
|
|
|
|
bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2))
|
2019-12-02 02:31:07 -06:00
|
|
|
|
2020-03-03 07:52:00 -06:00
|
|
|
// the bloom offsets are runtime constants which determines which part of the
|
2022-08-29 03:16:34 -05:00
|
|
|
// account/storage hash the hasher functions looks at, to determine the
|
2019-12-02 02:31:07 -06:00
|
|
|
// bloom key for an account/slot. This is randomized at init(), so that the
|
|
|
|
// global population of nodes do not all display the exact same behaviour with
|
|
|
|
// regards to bloom content
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
bloomAccountHasherOffset = 0
|
|
|
|
bloomStorageHasherOffset = 0
|
2019-08-06 05:40:28 -05:00
|
|
|
)
|
|
|
|
|
2019-12-02 02:31:07 -06:00
|
|
|
func init() {
|
2020-03-03 07:52:00 -06:00
|
|
|
// Init the bloom offsets in the range [0:24] (requires 8 bytes)
|
|
|
|
bloomAccountHasherOffset = rand.Intn(25)
|
|
|
|
bloomStorageHasherOffset = rand.Intn(25)
|
2019-12-02 02:31:07 -06:00
|
|
|
}
|
|
|
|
|
2019-08-06 05:40:28 -05:00
|
|
|
// diffLayer represents a collection of modifications made to a state snapshot
|
|
|
|
// after running a block on top. It contains one sorted list for the account trie
|
|
|
|
// and one-one list for each storage tries.
|
|
|
|
//
|
|
|
|
// The goal of a diff layer is to act as a journal, tracking recent modifications
|
|
|
|
// made to the state, that have not yet graduated into a semi-immutable state.
|
|
|
|
type diffLayer struct {
|
2019-11-26 01:48:29 -06:00
|
|
|
origin *diskLayer // Base disk layer to directly use on bloom misses
|
|
|
|
parent snapshot // Parent snapshot modified by this one, never nil
|
|
|
|
memory uint64 // Approximate guess as to how much memory we use
|
2019-08-06 05:40:28 -05:00
|
|
|
|
2019-11-22 05:23:49 -06:00
|
|
|
root common.Hash // Root hash to which this snapshot diff belongs to
|
2023-03-28 02:06:50 -05:00
|
|
|
stale atomic.Bool // Signals that the layer became stale (state progressed)
|
2019-08-06 05:40:28 -05:00
|
|
|
|
2021-01-07 00:36:21 -06:00
|
|
|
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)
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil
|
|
|
|
storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil
|
2019-08-06 05:40:28 -05:00
|
|
|
|
2019-11-26 01:48:29 -06:00
|
|
|
diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer
|
|
|
|
|
2019-08-06 05:40:28 -05:00
|
|
|
lock sync.RWMutex
|
|
|
|
}
|
|
|
|
|
2024-01-23 08:15:48 -06:00
|
|
|
// accountBloomHash is used to convert an account hash into a 64 bit mini hash.
|
|
|
|
func accountBloomHash(h common.Hash) uint64 {
|
2020-03-03 07:52:00 -06:00
|
|
|
return binary.BigEndian.Uint64(h[bloomAccountHasherOffset : bloomAccountHasherOffset+8])
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
|
2024-01-23 08:15:48 -06:00
|
|
|
// storageBloomHash is used to convert an account hash and a storage hash into a 64 bit mini hash.
|
|
|
|
func storageBloomHash(h0, h1 common.Hash) uint64 {
|
|
|
|
return binary.BigEndian.Uint64(h0[bloomStorageHasherOffset:bloomStorageHasherOffset+8]) ^
|
|
|
|
binary.BigEndian.Uint64(h1[bloomStorageHasherOffset:bloomStorageHasherOffset+8])
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
|
2019-08-06 05:40:28 -05:00
|
|
|
// newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low
|
|
|
|
// level persistent database or a hierarchical diff already.
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer {
|
2019-08-06 05:40:28 -05:00
|
|
|
// Create the new layer with some pre-allocated data segments
|
|
|
|
dl := &diffLayer{
|
|
|
|
parent: parent,
|
|
|
|
root: root,
|
|
|
|
accountData: accounts,
|
|
|
|
storageData: storage,
|
2020-04-29 04:53:08 -05:00
|
|
|
storageList: make(map[common.Hash][]common.Hash),
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
switch parent := parent.(type) {
|
|
|
|
case *diskLayer:
|
|
|
|
dl.rebloom(parent)
|
|
|
|
case *diffLayer:
|
|
|
|
dl.rebloom(parent.origin)
|
|
|
|
default:
|
|
|
|
panic("unknown parent type")
|
|
|
|
}
|
2020-03-03 07:52:00 -06:00
|
|
|
// Sanity check that accounts or storage slots are never nil
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
for _, blob := range accounts {
|
2021-01-25 07:25:55 -06:00
|
|
|
// Determine memory size and track the dirty writes
|
|
|
|
dl.memory += uint64(common.HashLength + len(blob))
|
|
|
|
snapshotDirtyAccountWriteMeter.Mark(int64(len(blob)))
|
2020-03-03 07:52:00 -06:00
|
|
|
}
|
|
|
|
for accountHash, slots := range storage {
|
|
|
|
if slots == nil {
|
|
|
|
panic(fmt.Sprintf("storage %#x nil", accountHash))
|
|
|
|
}
|
2021-01-25 07:25:55 -06:00
|
|
|
// Determine memory size and track the dirty writes
|
2019-10-04 08:24:01 -05:00
|
|
|
for _, data := range slots {
|
2019-11-26 01:48:29 -06:00
|
|
|
dl.memory += uint64(common.HashLength + len(data))
|
|
|
|
snapshotDirtyStorageWriteMeter.Mark(int64(len(data)))
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return dl
|
|
|
|
}
|
|
|
|
|
2019-11-26 01:48:29 -06:00
|
|
|
// rebloom discards the layer's current bloom and rebuilds it from scratch based
|
|
|
|
// on the parent's and the local diffs.
|
|
|
|
func (dl *diffLayer) rebloom(origin *diskLayer) {
|
|
|
|
dl.lock.Lock()
|
|
|
|
defer dl.lock.Unlock()
|
|
|
|
|
|
|
|
defer func(start time.Time) {
|
|
|
|
snapshotBloomIndexTimer.Update(time.Since(start))
|
|
|
|
}(time.Now())
|
|
|
|
|
|
|
|
// Inject the new origin that triggered the rebloom
|
|
|
|
dl.origin = origin
|
|
|
|
|
|
|
|
// Retrieve the parent bloom or create a fresh empty one
|
|
|
|
if parent, ok := dl.parent.(*diffLayer); ok {
|
|
|
|
parent.lock.RLock()
|
|
|
|
dl.diffed, _ = parent.diffed.Copy()
|
|
|
|
parent.lock.RUnlock()
|
|
|
|
} else {
|
|
|
|
dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs))
|
|
|
|
}
|
|
|
|
for hash := range dl.accountData {
|
2024-01-23 08:15:48 -06:00
|
|
|
dl.diffed.AddHash(accountBloomHash(hash))
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
for accountHash, slots := range dl.storageData {
|
|
|
|
for storageHash := range slots {
|
2024-01-23 08:15:48 -06:00
|
|
|
dl.diffed.AddHash(storageBloomHash(accountHash, storageHash))
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Calculate the current false positive rate and update the error rate meter.
|
|
|
|
// This is a bit cheating because subsequent layers will overwrite it, but it
|
|
|
|
// should be fine, we're only interested in ballpark figures.
|
|
|
|
k := float64(dl.diffed.K())
|
|
|
|
n := float64(dl.diffed.N())
|
|
|
|
m := float64(dl.diffed.M())
|
|
|
|
snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k))
|
|
|
|
}
|
|
|
|
|
2019-11-22 05:23:49 -06:00
|
|
|
// Root returns the root hash for which this snapshot was made.
|
|
|
|
func (dl *diffLayer) Root() common.Hash {
|
|
|
|
return dl.root
|
|
|
|
}
|
|
|
|
|
2019-12-10 03:00:03 -06:00
|
|
|
// Parent returns the subsequent layer of a diff layer.
|
|
|
|
func (dl *diffLayer) Parent() snapshot {
|
2022-05-06 10:20:41 -05:00
|
|
|
dl.lock.RLock()
|
|
|
|
defer dl.lock.RUnlock()
|
|
|
|
|
2019-12-10 03:00:03 -06:00
|
|
|
return dl.parent
|
|
|
|
}
|
|
|
|
|
2019-11-22 05:23:49 -06:00
|
|
|
// Stale return whether this layer has become stale (was flattened across) or if
|
|
|
|
// it's still live.
|
|
|
|
func (dl *diffLayer) Stale() bool {
|
2023-03-28 02:06:50 -05:00
|
|
|
return dl.stale.Load()
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Account directly retrieves the account associated with a particular hash in
|
|
|
|
// the snapshot slim data format.
|
2023-06-06 03:17:39 -05:00
|
|
|
func (dl *diffLayer) Account(hash common.Hash) (*types.SlimAccount, error) {
|
2019-10-04 08:24:01 -05:00
|
|
|
data, err := dl.AccountRLP(hash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-08-06 05:40:28 -05:00
|
|
|
if len(data) == 0 { // can be both nil and []byte{}
|
2019-10-04 08:24:01 -05:00
|
|
|
return nil, nil
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
2023-06-06 03:17:39 -05:00
|
|
|
account := new(types.SlimAccount)
|
2019-08-06 05:40:28 -05:00
|
|
|
if err := rlp.DecodeBytes(data, account); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2019-10-04 08:24:01 -05:00
|
|
|
return account, nil
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// AccountRLP directly retrieves the account RLP associated with a particular
|
|
|
|
// hash in the snapshot slim data format.
|
2020-04-29 04:53:08 -05:00
|
|
|
//
|
|
|
|
// Note the returned account is not a copy, please don't modify it.
|
2019-10-04 08:24:01 -05:00
|
|
|
func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) {
|
2023-05-16 08:18:39 -05:00
|
|
|
// Check staleness before reaching further.
|
2023-06-06 03:17:39 -05:00
|
|
|
dl.lock.RLock()
|
2023-05-16 08:18:39 -05:00
|
|
|
if dl.Stale() {
|
|
|
|
dl.lock.RUnlock()
|
|
|
|
return nil, ErrSnapshotStale
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
// Check the bloom filter first whether there's even a point in reaching into
|
|
|
|
// all the maps in all the layers below
|
2021-04-06 03:57:00 -05:00
|
|
|
var origin *diskLayer
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
hit := dl.diffed.ContainsHash(accountBloomHash(hash))
|
2021-04-06 03:57:00 -05:00
|
|
|
if !hit {
|
|
|
|
origin = dl.origin // extract origin while holding the lock
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
dl.lock.RUnlock()
|
|
|
|
|
|
|
|
// If the bloom filter misses, don't even bother with traversing the memory
|
|
|
|
// diff layers, reach straight into the bottom persistent disk layer
|
2021-04-06 03:57:00 -05:00
|
|
|
if origin != nil {
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotBloomAccountMissMeter.Mark(1)
|
2021-04-06 03:57:00 -05:00
|
|
|
return origin.AccountRLP(hash)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
// The bloom filter hit, start poking in the internal maps
|
2019-12-03 02:00:26 -06:00
|
|
|
return dl.accountRLP(hash, 0)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// accountRLP is an internal version of AccountRLP that skips the bloom filter
|
|
|
|
// checks and uses the internal maps to try and retrieve the data. It's meant
|
|
|
|
// to be used if a higher layer's bloom filter hit already.
|
2019-12-03 02:00:26 -06:00
|
|
|
func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) {
|
2019-08-06 05:40:28 -05:00
|
|
|
dl.lock.RLock()
|
|
|
|
defer dl.lock.RUnlock()
|
|
|
|
|
2019-10-04 08:24:01 -05:00
|
|
|
// If the layer was flattened into, consider it invalid (any live reference to
|
|
|
|
// the original should be marked as unusable).
|
2020-01-19 13:57:56 -06:00
|
|
|
if dl.Stale() {
|
2019-10-04 08:24:01 -05:00
|
|
|
return nil, ErrSnapshotStale
|
|
|
|
}
|
2020-03-03 07:52:00 -06:00
|
|
|
// If the account is known locally, return it
|
2019-08-06 05:40:28 -05:00
|
|
|
if data, ok := dl.accountData[hash]; ok {
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotDirtyAccountHitMeter.Mark(1)
|
2019-12-03 02:00:26 -06:00
|
|
|
snapshotDirtyAccountHitDepthHist.Update(int64(depth))
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
if n := len(data); n > 0 {
|
|
|
|
snapshotDirtyAccountReadMeter.Mark(int64(n))
|
|
|
|
} else {
|
|
|
|
snapshotDirtyAccountInexMeter.Mark(1)
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotBloomAccountTrueHitMeter.Mark(1)
|
2019-10-04 08:24:01 -05:00
|
|
|
return data, nil
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
// Account unknown to this diff, resolve from parent
|
2019-11-26 01:48:29 -06:00
|
|
|
if diff, ok := dl.parent.(*diffLayer); ok {
|
2019-12-03 02:00:26 -06:00
|
|
|
return diff.accountRLP(hash, depth+1)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
// Failed to resolve through diff layers, mark a bloom error and use the disk
|
|
|
|
snapshotBloomAccountFalseHitMeter.Mark(1)
|
2019-08-06 05:40:28 -05:00
|
|
|
return dl.parent.AccountRLP(hash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Storage directly retrieves the storage data associated with a particular hash,
|
|
|
|
// within a particular account. If the slot is unknown to this diff, it's parent
|
|
|
|
// is consulted.
|
2020-04-29 04:53:08 -05:00
|
|
|
//
|
|
|
|
// Note the returned slot is not a copy, please don't modify it.
|
2019-10-04 08:24:01 -05:00
|
|
|
func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
|
2019-11-26 01:48:29 -06:00
|
|
|
// Check the bloom filter first whether there's even a point in reaching into
|
|
|
|
// all the maps in all the layers below
|
|
|
|
dl.lock.RLock()
|
2023-05-16 08:18:39 -05:00
|
|
|
// Check staleness before reaching further.
|
|
|
|
if dl.Stale() {
|
|
|
|
dl.lock.RUnlock()
|
|
|
|
return nil, ErrSnapshotStale
|
|
|
|
}
|
2021-04-06 03:57:00 -05:00
|
|
|
var origin *diskLayer
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
hit := dl.diffed.ContainsHash(storageBloomHash(accountHash, storageHash))
|
2021-04-06 03:57:00 -05:00
|
|
|
if !hit {
|
|
|
|
origin = dl.origin // extract origin while holding the lock
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
dl.lock.RUnlock()
|
|
|
|
|
|
|
|
// If the bloom filter misses, don't even bother with traversing the memory
|
|
|
|
// diff layers, reach straight into the bottom persistent disk layer
|
2021-04-06 03:57:00 -05:00
|
|
|
if origin != nil {
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotBloomStorageMissMeter.Mark(1)
|
2021-04-06 03:57:00 -05:00
|
|
|
return origin.Storage(accountHash, storageHash)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
// The bloom filter hit, start poking in the internal maps
|
2019-12-03 02:00:26 -06:00
|
|
|
return dl.storage(accountHash, storageHash, 0)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// storage is an internal version of Storage that skips the bloom filter checks
|
|
|
|
// and uses the internal maps to try and retrieve the data. It's meant to be
|
|
|
|
// used if a higher layer's bloom filter hit already.
|
2019-12-03 02:00:26 -06:00
|
|
|
func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) {
|
2019-08-06 05:40:28 -05:00
|
|
|
dl.lock.RLock()
|
|
|
|
defer dl.lock.RUnlock()
|
|
|
|
|
2019-10-04 08:24:01 -05:00
|
|
|
// If the layer was flattened into, consider it invalid (any live reference to
|
|
|
|
// the original should be marked as unusable).
|
2020-01-19 13:57:56 -06:00
|
|
|
if dl.Stale() {
|
2019-10-04 08:24:01 -05:00
|
|
|
return nil, ErrSnapshotStale
|
|
|
|
}
|
2020-03-03 07:52:00 -06:00
|
|
|
// If the account is known locally, try to resolve the slot locally
|
2019-08-06 05:40:28 -05:00
|
|
|
if storage, ok := dl.storageData[accountHash]; ok {
|
|
|
|
if data, ok := storage[storageHash]; ok {
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotDirtyStorageHitMeter.Mark(1)
|
2019-12-03 02:00:26 -06:00
|
|
|
snapshotDirtyStorageHitDepthHist.Update(int64(depth))
|
|
|
|
if n := len(data); n > 0 {
|
|
|
|
snapshotDirtyStorageReadMeter.Mark(int64(n))
|
|
|
|
} else {
|
|
|
|
snapshotDirtyStorageInexMeter.Mark(1)
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
snapshotBloomStorageTrueHitMeter.Mark(1)
|
2019-10-04 08:24:01 -05:00
|
|
|
return data, nil
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
}
|
2019-11-26 01:48:29 -06:00
|
|
|
// Storage slot unknown to this diff, resolve from parent
|
|
|
|
if diff, ok := dl.parent.(*diffLayer); ok {
|
2019-12-03 02:00:26 -06:00
|
|
|
return diff.storage(accountHash, storageHash, depth+1)
|
2019-11-26 01:48:29 -06:00
|
|
|
}
|
|
|
|
// Failed to resolve through diff layers, mark a bloom error and use the disk
|
|
|
|
snapshotBloomStorageFalseHitMeter.Mark(1)
|
2019-08-06 05:40:28 -05:00
|
|
|
return dl.parent.Storage(accountHash, storageHash)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update creates a new layer on top of the existing snapshot diff tree with
|
|
|
|
// the specified data items.
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer {
|
|
|
|
return newDiffLayer(dl, blockRoot, accounts, storage)
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// flatten pushes all data from this point downwards, flattening everything into
|
|
|
|
// a single diff at the bottom. Since usually the lowermost diff is the largest,
|
2020-05-25 03:21:28 -05:00
|
|
|
// the flattening builds up from there in reverse.
|
2019-08-06 05:40:28 -05:00
|
|
|
func (dl *diffLayer) flatten() snapshot {
|
|
|
|
// If the parent is not diff, we're the first in line, return unmodified
|
|
|
|
parent, ok := dl.parent.(*diffLayer)
|
|
|
|
if !ok {
|
|
|
|
return dl
|
|
|
|
}
|
|
|
|
// Parent is a diff, flatten it first (note, apart from weird corned cases,
|
|
|
|
// flatten will realistically only ever merge 1 layer, so there's no need to
|
|
|
|
// be smarter about grouping flattens together).
|
|
|
|
parent = parent.flatten().(*diffLayer)
|
|
|
|
|
2019-10-04 08:24:01 -05:00
|
|
|
parent.lock.Lock()
|
|
|
|
defer parent.lock.Unlock()
|
|
|
|
|
|
|
|
// Before actually writing all our data to the parent, first ensure that the
|
|
|
|
// parent hasn't been 'corrupted' by someone else already flattening into it
|
2023-03-28 02:06:50 -05:00
|
|
|
if parent.stale.Swap(true) {
|
2019-10-04 08:24:01 -05:00
|
|
|
panic("parent diff layer is stale") // we've flattened into the same parent from two children, boo
|
|
|
|
}
|
2019-08-06 05:40:28 -05:00
|
|
|
for hash, data := range dl.accountData {
|
|
|
|
parent.accountData[hash] = data
|
|
|
|
}
|
2019-12-10 03:00:03 -06:00
|
|
|
// Overwrite all the updated storage slots (individually)
|
2019-08-06 05:40:28 -05:00
|
|
|
for accountHash, storage := range dl.storageData {
|
2020-03-03 07:52:00 -06:00
|
|
|
// If storage didn't exist (or was deleted) in the parent, overwrite blindly
|
|
|
|
if _, ok := parent.storageData[accountHash]; !ok {
|
2019-08-06 05:40:28 -05:00
|
|
|
parent.storageData[accountHash] = storage
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Storage exists in both parent and child, merge the slots
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
maps.Copy(parent.storageData[accountHash], storage)
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
// Return the combo parent
|
2019-10-04 08:24:01 -05:00
|
|
|
return &diffLayer{
|
|
|
|
parent: parent.parent,
|
2019-12-01 13:49:00 -06:00
|
|
|
origin: parent.origin,
|
2019-10-04 08:24:01 -05:00
|
|
|
root: dl.root,
|
|
|
|
accountData: parent.accountData,
|
2019-12-10 03:00:03 -06:00
|
|
|
storageData: parent.storageData,
|
|
|
|
storageList: make(map[common.Hash][]common.Hash),
|
2019-11-26 01:48:29 -06:00
|
|
|
diffed: dl.diffed,
|
2019-10-04 08:24:01 -05:00
|
|
|
memory: parent.memory + dl.memory,
|
|
|
|
}
|
2019-08-06 05:40:28 -05:00
|
|
|
}
|
|
|
|
|
2021-01-07 00:36:21 -06:00
|
|
|
// AccountList returns a sorted list of all accounts in this diffLayer, including
|
2019-12-10 03:00:03 -06:00
|
|
|
// the deleted ones.
|
|
|
|
//
|
|
|
|
// Note, the returned slice is not a copy, so do not modify it.
|
2019-10-04 08:24:01 -05:00
|
|
|
func (dl *diffLayer) AccountList() []common.Hash {
|
2019-12-10 03:00:03 -06:00
|
|
|
// If an old list already exists, return it
|
|
|
|
dl.lock.RLock()
|
|
|
|
list := dl.accountList
|
|
|
|
dl.lock.RUnlock()
|
|
|
|
|
|
|
|
if list != nil {
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
// No old sorted account list exists, generate a new one
|
2019-10-04 08:24:01 -05:00
|
|
|
dl.lock.Lock()
|
|
|
|
defer dl.lock.Unlock()
|
2019-12-10 03:00:03 -06:00
|
|
|
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
dl.accountList = maps.Keys(dl.accountData)
|
2023-08-11 17:04:12 -05:00
|
|
|
slices.SortFunc(dl.accountList, common.Hash.Cmp)
|
2020-04-29 04:53:08 -05:00
|
|
|
dl.memory += uint64(len(dl.accountList) * common.HashLength)
|
2019-10-04 08:24:01 -05:00
|
|
|
return dl.accountList
|
|
|
|
}
|
|
|
|
|
2021-01-07 00:36:21 -06:00
|
|
|
// StorageList returns a sorted list of all storage slot hashes in this diffLayer
|
2020-04-29 04:53:08 -05:00
|
|
|
// for the given account. If the whole storage is destructed in this layer, then
|
|
|
|
// an additional flag *destructed = true* will be returned, otherwise the flag is
|
|
|
|
// false. Besides, the returned list will include the hash of deleted storage slot.
|
|
|
|
// Note a special case is an account is deleted in a prior tx but is recreated in
|
|
|
|
// the following tx with some storage slots set. In this case the returned list is
|
|
|
|
// not empty but the flag is true.
|
2019-12-10 03:00:03 -06:00
|
|
|
//
|
|
|
|
// Note, the returned slice is not a copy, so do not modify it.
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
|
2019-12-10 03:00:03 -06:00
|
|
|
dl.lock.RLock()
|
2020-05-06 09:22:38 -05:00
|
|
|
if _, ok := dl.storageData[accountHash]; !ok {
|
|
|
|
// Account not tracked by this layer
|
|
|
|
dl.lock.RUnlock()
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
return nil
|
2020-05-06 09:22:38 -05:00
|
|
|
}
|
2020-05-07 02:07:59 -05:00
|
|
|
// If an old list already exists, return it
|
2020-04-29 04:53:08 -05:00
|
|
|
if list, exist := dl.storageList[accountHash]; exist {
|
|
|
|
dl.lock.RUnlock()
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
return list // the cached list can't be nil
|
2020-04-29 04:53:08 -05:00
|
|
|
}
|
2019-12-10 03:00:03 -06:00
|
|
|
dl.lock.RUnlock()
|
|
|
|
|
|
|
|
// No old sorted account list exists, generate a new one
|
2019-10-04 08:24:01 -05:00
|
|
|
dl.lock.Lock()
|
|
|
|
defer dl.lock.Unlock()
|
2019-12-10 03:00:03 -06:00
|
|
|
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
storageList := maps.Keys(dl.storageData[accountHash])
|
2023-08-11 17:04:12 -05:00
|
|
|
slices.SortFunc(storageList, common.Hash.Cmp)
|
2019-12-10 03:00:03 -06:00
|
|
|
dl.storageList[accountHash] = storageList
|
2020-04-29 04:53:08 -05:00
|
|
|
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
|
core, triedb: remove destruct flag in state snapshot (#30752)
This pull request removes the destruct flag from the state snapshot to
simplify the code.
Previously, this flag indicated that an account was removed during a
state transition, making all associated storage slots inaccessible.
Because storage deletion can involve a large number of slots, the actual
deletion is deferred until the end of the process, where it is handled
in batches.
With the deprecation of self-destruct in the Cancun fork, storage
deletions are no longer expected. Historically, the largest storage
deletion event in Ethereum was around 15 megabytes—manageable in memory.
In this pull request, the single destruct flag is replaced by a set of
deletion markers for individual storage slots. Each deleted storage slot
will now appear in the Storage set with a nil value.
This change will simplify a lot logics, such as storage accessing,
storage flushing, storage iteration and so on.
2024-11-22 02:55:43 -06:00
|
|
|
return storageList
|
2019-10-04 08:24:01 -05:00
|
|
|
}
|