diff --git a/cmd/clef/README.md b/cmd/clef/README.md
index d23e70a3d4..a92dcb1d77 100644
--- a/cmd/clef/README.md
+++ b/cmd/clef/README.md
@@ -150,7 +150,7 @@ All hex encoded values must be prefixed with `0x`.
#### Create new password protected account
-The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and store it in the keystore directory.
+The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/) and store it in the keystore directory.
The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts.
#### Arguments
diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index 01a33455de..22becdbfd8 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -773,15 +773,8 @@ func downloadEra(ctx *cli.Context) error {
}
func parseRange(s string) (start uint64, end uint64, ok bool) {
- if m, _ := regexp.MatchString("[0-9]+", s); m {
- start, err := strconv.ParseUint(s, 10, 64)
- if err != nil {
- return 0, 0, false
- }
- end = start
- return start, end, true
- }
- if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m {
+ log.Info("Parsing block range", "input", s)
+ if m, _ := regexp.MatchString("^[0-9]+-[0-9]+$", s); m {
s1, s2, _ := strings.Cut(s, "-")
start, err := strconv.ParseUint(s1, 10, 64)
if err != nil {
@@ -791,6 +784,19 @@ func parseRange(s string) (start uint64, end uint64, ok bool) {
if err != nil {
return 0, 0, false
}
+ if start > end {
+ return 0, 0, false
+ }
+ log.Info("Parsing block range", "start", start, "end", end)
+ return start, end, true
+ }
+ if m, _ := regexp.MatchString("^[0-9]+$", s); m {
+ start, err := strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return 0, 0, false
+ }
+ end = start
+ log.Info("Parsing single block range", "block", start)
return start, end, true
}
return 0, 0, false
diff --git a/cmd/geth/chaincmd_test.go b/cmd/geth/chaincmd_test.go
new file mode 100644
index 0000000000..131f5c4501
--- /dev/null
+++ b/cmd/geth/chaincmd_test.go
@@ -0,0 +1,98 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package main
+
+import "testing"
+
+func TestParseRange(t *testing.T) {
+ var cases = []struct {
+ input string
+ valid bool
+ expStart uint64
+ expEnd uint64
+ }{
+ {
+ input: "0",
+ valid: true,
+ expStart: 0,
+ expEnd: 0,
+ },
+ {
+ input: "500",
+ valid: true,
+ expStart: 500,
+ expEnd: 500,
+ },
+ {
+ input: "-1",
+ valid: false,
+ expStart: 0,
+ expEnd: 0,
+ },
+ {
+ input: "1-1",
+ valid: true,
+ expStart: 1,
+ expEnd: 1,
+ },
+ {
+ input: "0-1",
+ valid: true,
+ expStart: 0,
+ expEnd: 1,
+ },
+ {
+ input: "1-0",
+ valid: false,
+ expStart: 0,
+ expEnd: 0,
+ },
+ {
+ input: "1-1000",
+ valid: true,
+ expStart: 1,
+ expEnd: 1000,
+ },
+ {
+ input: "1-1-",
+ valid: false,
+ expStart: 0,
+ expEnd: 0,
+ },
+ {
+ input: "-1-1",
+ valid: false,
+ expStart: 0,
+ expEnd: 0,
+ },
+ }
+ for _, c := range cases {
+ start, end, valid := parseRange(c.input)
+ if valid != c.valid {
+ t.Errorf("Unexpected result, want: %t, got: %t", c.valid, valid)
+ continue
+ }
+ if valid {
+ if c.expStart != start {
+ t.Errorf("Unexpected start, want: %d, got: %d", c.expStart, start)
+ }
+ if c.expEnd != end {
+ t.Errorf("Unexpected end, want: %d, got: %d", c.expEnd, end)
+ }
+ }
+ }
+}
diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go
index f0be52a0df..aa9ae7087f 100644
--- a/cmd/geth/snapshot.go
+++ b/cmd/geth/snapshot.go
@@ -220,22 +220,10 @@ func verifyState(ctx *cli.Context) error {
triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
defer triedb.Close()
- snapConfig := snapshot.Config{
- CacheSize: 256,
- Recovery: false,
- NoBuild: true,
- AsyncBuild: false,
- }
- snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root())
- if err != nil {
- log.Error("Failed to open snapshot tree", "err", err)
- return err
- }
- if ctx.NArg() > 1 {
- log.Error("Too many arguments given")
- return errors.New("too many arguments")
- }
- var root = headBlock.Root()
+ var (
+ err error
+ root = headBlock.Root()
+ )
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
@@ -243,12 +231,34 @@ func verifyState(ctx *cli.Context) error {
return err
}
}
- if err := snaptree.Verify(root); err != nil {
- log.Error("Failed to verify state", "root", root, "err", err)
- return err
+ if triedb.Scheme() == rawdb.PathScheme {
+ if err := triedb.VerifyState(root); err != nil {
+ log.Error("Failed to verify state", "root", root, "err", err)
+ return err
+ }
+ log.Info("Verified the state", "root", root)
+
+ // TODO(rjl493456442) implement dangling checks in pathdb.
+ return nil
+ } else {
+ snapConfig := snapshot.Config{
+ CacheSize: 256,
+ Recovery: false,
+ NoBuild: true,
+ AsyncBuild: false,
+ }
+ snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root())
+ if err != nil {
+ log.Error("Failed to open snapshot tree", "err", err)
+ return err
+ }
+ if err := snaptree.Verify(root); err != nil {
+ log.Error("Failed to verify state", "root", root, "err", err)
+ return err
+ }
+ log.Info("Verified the state", "root", root)
+ return snapshot.CheckDanglingStorage(chaindb)
}
- log.Info("Verified the state", "root", root)
- return snapshot.CheckDanglingStorage(chaindb)
}
// checkDanglingStorage iterates the snap storage data, and verifies that all
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index a08100e87a..ea0ca08883 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1672,11 +1672,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node")
}
-
- if cfg.StateScheme != rawdb.HashScheme {
- cfg.StateScheme = rawdb.HashScheme
- log.Warn("Forcing hash state-scheme for archive mode")
- }
}
if ctx.IsSet(LogHistoryFlag.Name) {
cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name)
@@ -1907,11 +1902,11 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
if c, err := hexutil.Decode(ctx.String(BeaconGenesisRootFlag.Name)); err == nil && len(c) <= 32 {
copy(config.GenesisValidatorsRoot[:len(c)], c)
} else {
- Fatalf("Invalid hex string", "beacon.genesis.gvroot", ctx.String(BeaconGenesisRootFlag.Name), "error", err)
+ Fatalf("Could not parse --%s: %v", BeaconGenesisRootFlag.Name, err)
}
configFile := ctx.String(BeaconConfigFlag.Name)
if err := config.ChainConfig.LoadForks(configFile); err != nil {
- Fatalf("Could not load beacon chain config", "file", configFile, "error", err)
+ Fatalf("Could not load beacon chain config '%s': %v", configFile, err)
}
log.Info("Using custom beacon chain config", "file", configFile)
} else {
@@ -1928,17 +1923,17 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
// are saved to the specified file.
if ctx.IsSet(BeaconCheckpointFileFlag.Name) {
if _, err := config.SetCheckpointFile(ctx.String(BeaconCheckpointFileFlag.Name)); err != nil {
- Fatalf("Could not load beacon checkpoint file", "beacon.checkpoint.file", ctx.String(BeaconCheckpointFileFlag.Name), "error", err)
+ Fatalf("Could not load beacon checkpoint file '%s': %v", ctx.String(BeaconCheckpointFileFlag.Name), err)
}
}
if ctx.IsSet(BeaconCheckpointFlag.Name) {
hex := ctx.String(BeaconCheckpointFlag.Name)
c, err := hexutil.Decode(hex)
if err != nil {
- Fatalf("Invalid hex string", "beacon.checkpoint", hex, "error", err)
+ Fatalf("Could not parse --%s: %v", BeaconCheckpointFlag.Name, err)
}
if len(c) != 32 {
- Fatalf("Invalid hex string length", "beacon.checkpoint", hex, "length", len(c))
+ Fatalf("Could not parse --%s: invalid length %d, want 32", BeaconCheckpointFlag.Name, len(c))
}
copy(config.Checkpoint[:len(c)], c)
}
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index f9a5a3233b..196cbc857c 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -391,8 +391,12 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
if err != nil {
return nil, fmt.Errorf("error opening pre-state tree root: %w", err)
}
+ postTrie := state.GetTrie()
+ if postTrie == nil {
+ return nil, errors.New("post-state tree is not available")
+ }
vktPreTrie, okpre := preTrie.(*trie.VerkleTrie)
- vktPostTrie, okpost := state.GetTrie().(*trie.VerkleTrie)
+ vktPostTrie, okpost := postTrie.(*trie.VerkleTrie)
// The witness is only attached iff both parent and current block are
// using verkle tree.
diff --git a/core/blockchain.go b/core/blockchain.go
index ec979a6b17..d8b101f616 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -162,9 +162,10 @@ const (
// BlockChainConfig contains the configuration of the BlockChain object.
type BlockChainConfig struct {
// Trie database related options
- TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
- TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
- TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
+ TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
+ TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk
+ TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk
+ TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed
Preimages bool // Whether to store preimage of trie key to the disk
StateHistory uint64 // Number of blocks from head whose state histories are reserved.
@@ -210,7 +211,7 @@ func DefaultConfig() *BlockChainConfig {
}
}
-// WithArchive enabled/disables archive mode on the config.
+// WithArchive enables/disables archive mode on the config.
func (cfg BlockChainConfig) WithArchive(on bool) *BlockChainConfig {
cfg.ArchiveMode = on
return &cfg
@@ -222,6 +223,12 @@ func (cfg BlockChainConfig) WithStateScheme(scheme string) *BlockChainConfig {
return &cfg
}
+// WithNoAsyncFlush enables/disables asynchronous buffer flushing mode on the config.
+func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig {
+ cfg.TrieNoAsyncFlush = on
+ return &cfg
+}
+
// triedbConfig derives the configures for trie database.
func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
config := &triedb.Config{
@@ -235,14 +242,16 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
}
if cfg.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{
- StateHistory: cfg.StateHistory,
- TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
- StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
+ StateHistory: cfg.StateHistory,
+ EnableStateIndexing: cfg.ArchiveMode,
+ TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
+ StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
// TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name
// should be updated to eliminate the confusion.
WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024,
+ NoAsyncFlush: cfg.TrieNoAsyncFlush,
}
}
return config
@@ -1236,7 +1245,7 @@ func (bc *BlockChain) stopWithoutSaving() {
bc.scope.Close()
// Signal shutdown to all goroutines.
- bc.StopInsert()
+ bc.InterruptInsert(true)
// Now wait for all chain modifications to end and persistent goroutines to exit.
//
@@ -1310,11 +1319,15 @@ func (bc *BlockChain) Stop() {
log.Info("Blockchain stopped")
}
-// StopInsert interrupts all insertion methods, causing them to return
-// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
-// calling this method.
-func (bc *BlockChain) StopInsert() {
- bc.procInterrupt.Store(true)
+// InterruptInsert interrupts all insertion methods, causing them to return
+// errInsertionInterrupted as soon as possible, or resume the chain insertion
+// if required.
+func (bc *BlockChain) InterruptInsert(on bool) {
+ if on {
+ bc.procInterrupt.Store(true)
+ } else {
+ bc.procInterrupt.Store(false)
+ }
}
// insertStopped returns true after StopInsert has been called.
diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go
index 5550907b0d..ae9398b97d 100644
--- a/core/blockchain_snapshot_test.go
+++ b/core/blockchain_snapshot_test.go
@@ -81,7 +81,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
}
engine = ethash.NewFullFaker()
)
- chain, err := NewBlockChain(db, gspec, engine, DefaultConfig().WithStateScheme(basic.scheme))
+ chain, err := NewBlockChain(db, gspec, engine, DefaultConfig().WithStateScheme(basic.scheme).WithNoAsyncFlush(true))
if err != nil {
t.Fatalf("Failed to create chain: %v", err)
}
@@ -572,7 +572,7 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) {
//
// Expected head header : C8
// Expected head fast block: C8
- // Expected head block : G (Hash mode), C6 (Hash mode)
+ // Expected head block : G (Hash mode), C6 (Path mode)
// Expected snapshot disk : C4 (Hash mode)
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} {
expHead := uint64(0)
diff --git a/core/chain_makers.go b/core/chain_makers.go
index fe2e25d2d7..b2559495a1 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -124,7 +124,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
}
// Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built.
- if b.statedb.GetTrie().IsVerkle() {
+ if b.statedb.Database().TrieDB().IsVerkle() {
b.statedb.AccessEvents().Merge(evm.AccessEvents)
}
b.txs = append(b.txs, tx)
diff --git a/core/genesis_test.go b/core/genesis_test.go
index 612fd87f54..7e39e7a71c 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -276,7 +276,9 @@ func newDbConfig(scheme string) *triedb.Config {
if scheme == rawdb.HashScheme {
return triedb.HashDefaults
}
- return &triedb.Config{PathDB: pathdb.Defaults}
+ config := *pathdb.Defaults
+ config.NoAsyncFlush = true
+ return &triedb.Config{PathDB: &config}
}
func TestVerkleGenesisCommit(t *testing.T) {
@@ -333,7 +335,14 @@ func TestVerkleGenesisCommit(t *testing.T) {
}
db := rawdb.NewMemoryDatabase()
- triedb := triedb.NewDatabase(db, triedb.VerkleDefaults)
+
+ config := *pathdb.Defaults
+ config.NoAsyncFlush = true
+
+ triedb := triedb.NewDatabase(db, &triedb.Config{
+ IsVerkle: true,
+ PathDB: &config,
+ })
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
diff --git a/core/rawdb/accessors_history.go b/core/rawdb/accessors_history.go
new file mode 100644
index 0000000000..cf1073f387
--- /dev/null
+++ b/core/rawdb/accessors_history.go
@@ -0,0 +1,179 @@
+// Copyright 2025 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 .
+
+package rawdb
+
+import (
+ "bytes"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+// ReadStateHistoryIndexMetadata retrieves the metadata of state history index.
+func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
+ data, _ := db.Get(headStateHistoryIndexKey)
+ return data
+}
+
+// WriteStateHistoryIndexMetadata stores the metadata of state history index
+// into database.
+func WriteStateHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) {
+ if err := db.Put(headStateHistoryIndexKey, blob); err != nil {
+ log.Crit("Failed to store the metadata of state history index", "err", err)
+ }
+}
+
+// DeleteStateHistoryIndexMetadata removes the metadata of state history index.
+func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) {
+ if err := db.Delete(headStateHistoryIndexKey); err != nil {
+ log.Crit("Failed to delete the metadata of state history index", "err", err)
+ }
+}
+
+// ReadAccountHistoryIndex retrieves the account history index with the provided
+// account address.
+func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte {
+ data, err := db.Get(accountHistoryIndexKey(addressHash))
+ if err != nil || len(data) == 0 {
+ return nil
+ }
+ return data
+}
+
+// WriteAccountHistoryIndex writes the provided account history index into database.
+func WriteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, data []byte) {
+ if err := db.Put(accountHistoryIndexKey(addressHash), data); err != nil {
+ log.Crit("Failed to store account history index", "err", err)
+ }
+}
+
+// DeleteAccountHistoryIndex deletes the specified account history index from
+// the database.
+func DeleteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash) {
+ if err := db.Delete(accountHistoryIndexKey(addressHash)); err != nil {
+ log.Crit("Failed to delete account history index", "err", err)
+ }
+}
+
+// ReadStorageHistoryIndex retrieves the storage history index with the provided
+// account address and storage key hash.
+func ReadStorageHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash) []byte {
+ data, err := db.Get(storageHistoryIndexKey(addressHash, storageHash))
+ if err != nil || len(data) == 0 {
+ return nil
+ }
+ return data
+}
+
+// WriteStorageHistoryIndex writes the provided storage history index into database.
+func WriteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, data []byte) {
+ if err := db.Put(storageHistoryIndexKey(addressHash, storageHash), data); err != nil {
+ log.Crit("Failed to store storage history index", "err", err)
+ }
+}
+
+// DeleteStorageHistoryIndex deletes the specified state index from the database.
+func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash) {
+ if err := db.Delete(storageHistoryIndexKey(addressHash, storageHash)); err != nil {
+ log.Crit("Failed to delete storage history index", "err", err)
+ }
+}
+
+// ReadAccountHistoryIndexBlock retrieves the index block with the provided
+// account address along with the block id.
+func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte {
+ data, err := db.Get(accountHistoryIndexBlockKey(addressHash, blockID))
+ if err != nil || len(data) == 0 {
+ return nil
+ }
+ return data
+}
+
+// WriteAccountHistoryIndexBlock writes the provided index block into database.
+func WriteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32, data []byte) {
+ if err := db.Put(accountHistoryIndexBlockKey(addressHash, blockID), data); err != nil {
+ log.Crit("Failed to store account index block", "err", err)
+ }
+}
+
+// DeleteAccountHistoryIndexBlock deletes the specified index block from the database.
+func DeleteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32) {
+ if err := db.Delete(accountHistoryIndexBlockKey(addressHash, blockID)); err != nil {
+ log.Crit("Failed to delete account index block", "err", err)
+ }
+}
+
+// ReadStorageHistoryIndexBlock retrieves the index block with the provided state
+// identifier along with the block id.
+func ReadStorageHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
+ data, err := db.Get(storageHistoryIndexBlockKey(addressHash, storageHash, blockID))
+ if err != nil || len(data) == 0 {
+ return nil
+ }
+ return data
+}
+
+// WriteStorageHistoryIndexBlock writes the provided index block into database.
+func WriteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32, data []byte) {
+ if err := db.Put(storageHistoryIndexBlockKey(addressHash, storageHash, id), data); err != nil {
+ log.Crit("Failed to store storage index block", "err", err)
+ }
+}
+
+// DeleteStorageHistoryIndexBlock deletes the specified index block from the database.
+func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32) {
+ if err := db.Delete(storageHistoryIndexBlockKey(addressHash, storageHash, id)); err != nil {
+ log.Crit("Failed to delete storage index block", "err", err)
+ }
+}
+
+// increaseKey increase the input key by one bit. Return nil if the entire
+// addition operation overflows.
+func increaseKey(key []byte) []byte {
+ for i := len(key) - 1; i >= 0; i-- {
+ key[i]++
+ if key[i] != 0x0 {
+ return key
+ }
+ }
+ return nil
+}
+
+// DeleteStateHistoryIndex completely removes all history indexing data, including
+// indexes for accounts and storages.
+//
+// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
+// is exclusively occupied by the history indexing data!
+func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
+ start := StateHistoryIndexPrefix
+ limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix))
+
+ // Try to remove the data in the range by a loop, as the leveldb
+ // doesn't support the native range deletion.
+ for {
+ err := db.DeleteRange(start, limit)
+ if err == nil {
+ return
+ }
+ if errors.Is(err, ethdb.ErrTooManyKeys) {
+ continue
+ }
+ log.Crit("Failed to delete history index range", "err", err)
+ }
+}
diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go
index 41e15debe9..7d7b37641b 100644
--- a/core/rawdb/accessors_state.go
+++ b/core/rawdb/accessors_state.go
@@ -18,6 +18,7 @@ package rawdb
import (
"encoding/binary"
+ "errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
@@ -255,6 +256,36 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by
return meta, accountIndex, storageIndex, accountData, storageData, nil
}
+// ReadStateHistoryList retrieves a list of state histories from database with
+// specific range. Compute the position of state history in freezer by minus one
+// since the id of first state history starts from one(zero for initial state).
+func ReadStateHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, [][]byte, [][]byte, error) {
+ metaList, err := db.AncientRange(stateHistoryMeta, start-1, count, 0)
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ aIndexList, err := db.AncientRange(stateHistoryAccountIndex, start-1, count, 0)
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ sIndexList, err := db.AncientRange(stateHistoryStorageIndex, start-1, count, 0)
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ aDataList, err := db.AncientRange(stateHistoryAccountData, start-1, count, 0)
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ sDataList, err := db.AncientRange(stateHistoryStorageData, start-1, count, 0)
+ if err != nil {
+ return nil, nil, nil, nil, nil, err
+ }
+ if len(metaList) != len(aIndexList) || len(metaList) != len(sIndexList) || len(metaList) != len(aDataList) || len(metaList) != len(sDataList) {
+ return nil, nil, nil, nil, nil, errors.New("state history is corrupted")
+ }
+ return metaList, aIndexList, sIndexList, aDataList, sDataList, nil
+}
+
// WriteStateHistory writes the provided state history to database. Compute the
// position of state history in freezer by minus one since the id of first state
// history starts from one(zero for initial state).
diff --git a/core/rawdb/database.go b/core/rawdb/database.go
index 8854336327..9681c39c58 100644
--- a/core/rawdb/database.go
+++ b/core/rawdb/database.go
@@ -412,6 +412,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
filterMapLastBlock stat
filterMapBlockLV stat
+ // Path-mode archive data
+ stateIndex stat
+
// Verkle statistics
verkleTries stat
verkleStateLookups stat
@@ -489,6 +492,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
bloomBits.Add(size)
+ // Path-based historic state indexes
+ case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
+ stateIndex.Add(size)
+
// Verkle trie data is detected, determine the sub-category
case bytes.HasPrefix(key, VerklePrefix):
remain := key[len(VerklePrefix):]
@@ -544,6 +551,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
{"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()},
{"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()},
+ {"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()},
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()},
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()},
{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()},
@@ -591,7 +599,7 @@ var knownMetadataKeys = [][]byte{
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
- filterMapsRangeKey,
+ filterMapsRangeKey, headStateHistoryIndexKey,
}
// printChainMetadata prints out chain metadata to stderr.
diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go
index fa125cecc0..eec25a8042 100644
--- a/core/rawdb/schema.go
+++ b/core/rawdb/schema.go
@@ -76,6 +76,10 @@ var (
// trieJournalKey tracks the in-memory trie node layers across restarts.
trieJournalKey = []byte("TrieJournal")
+ // headStateHistoryIndexKey tracks the ID of the latest state history that has
+ // been indexed.
+ headStateHistoryIndexKey = []byte("LastStateHistoryIndex")
+
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
txIndexTailKey = []byte("TransactionIndexTail")
@@ -117,6 +121,13 @@ var (
TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
+ // State history indexing within path-based storage scheme
+ StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data
+ StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata
+ StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata
+ StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + block_number => account block
+ StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + block_number => slot block
+
// VerklePrefix is the database prefix for Verkle trie data, which includes:
// (a) Trie nodes
// (b) In-memory trie node journal
@@ -362,3 +373,27 @@ func filterMapBlockLVKey(number uint64) []byte {
binary.BigEndian.PutUint64(key[l:], number)
return key
}
+
+// accountHistoryIndexKey = StateHistoryAccountMetadataPrefix + addressHash
+func accountHistoryIndexKey(addressHash common.Hash) []byte {
+ return append(StateHistoryAccountMetadataPrefix, addressHash.Bytes()...)
+}
+
+// storageHistoryIndexKey = StateHistoryStorageMetadataPrefix + addressHash + storageHash
+func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []byte {
+ return append(append(StateHistoryStorageMetadataPrefix, addressHash.Bytes()...), storageHash.Bytes()...)
+}
+
+// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID
+func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte {
+ var buf [4]byte
+ binary.BigEndian.PutUint32(buf[:], blockID)
+ return append(append(StateHistoryAccountBlockPrefix, addressHash.Bytes()...), buf[:]...)
+}
+
+// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID
+func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
+ var buf [4]byte
+ binary.BigEndian.PutUint32(buf[:], blockID)
+ return append(append(append(StateHistoryStorageBlockPrefix, addressHash.Bytes()...), storageHash.Bytes()...), buf[:]...)
+}
diff --git a/core/state/dump.go b/core/state/dump.go
index 3af9133f5b..a4abc33733 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -112,6 +112,9 @@ func (d iterativeDump) OnRoot(root common.Hash) {
// DumpToCollector iterates the state according to the given options and inserts
// the items into a collector for aggregation or serialization.
+//
+// The state iterator is still trie-based and can be converted to snapshot-based
+// once the state snapshot is fully integrated into database. TODO(rjl493456442).
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
// Sanitize the input to allow nil configs
if conf == nil {
@@ -123,15 +126,20 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
start = time.Now()
logged = time.Now()
)
- log.Info("Trie dumping started", "root", s.trie.Hash())
- c.OnRoot(s.trie.Hash())
+ log.Info("Trie dumping started", "root", s.originalRoot)
+ c.OnRoot(s.originalRoot)
- trieIt, err := s.trie.NodeIterator(conf.Start)
+ tr, err := s.db.OpenTrie(s.originalRoot)
+ if err != nil {
+ return nil
+ }
+ trieIt, err := tr.NodeIterator(conf.Start)
if err != nil {
log.Error("Trie dumping error", "err", err)
return nil
}
it := trie.NewIterator(trieIt)
+
for it.Next() {
var data types.StateAccount
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
@@ -147,7 +155,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
}
address *common.Address
addr common.Address
- addrBytes = s.trie.GetKey(it.Key)
+ addrBytes = tr.GetKey(it.Key)
)
if addrBytes == nil {
missingPreimages++
@@ -165,12 +173,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
}
if !conf.SkipStorage {
account.Storage = make(map[common.Hash]string)
- tr, err := obj.getTrie()
+
+ storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
if err != nil {
log.Error("Failed to load storage trie", "err", err)
continue
}
- trieIt, err := tr.NodeIterator(nil)
+ trieIt, err := storageTr.NodeIterator(nil)
if err != nil {
log.Error("Failed to create trie iterator", "err", err)
continue
@@ -182,7 +191,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
log.Error("Failed to decode the value returned by iterator", "error", err)
continue
}
- key := s.trie.GetKey(storageIt.Key)
+ key := storageTr.GetKey(storageIt.Key)
if key == nil {
continue
}
diff --git a/core/state/iterator.go b/core/state/iterator.go
index 5ea52c6183..0abae091d9 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -32,6 +32,7 @@ import (
// required in order to resolve the contract address.
type nodeIterator struct {
state *StateDB // State being iterated
+ tr Trie // Primary account trie for traversal
stateIt trie.NodeIterator // Primary iterator for the global state trie
dataIt trie.NodeIterator // Secondary iterator for the data trie of a contract
@@ -75,13 +76,20 @@ func (it *nodeIterator) step() error {
if it.state == nil {
return nil
}
- // Initialize the iterator if we've just started
- var err error
- if it.stateIt == nil {
- it.stateIt, err = it.state.trie.NodeIterator(nil)
+ if it.tr == nil {
+ tr, err := it.state.db.OpenTrie(it.state.originalRoot)
if err != nil {
return err
}
+ it.tr = tr
+ }
+ // Initialize the iterator if we've just started
+ if it.stateIt == nil {
+ stateIt, err := it.tr.NodeIterator(nil)
+ if err != nil {
+ return err
+ }
+ it.stateIt = stateIt
}
// If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil {
@@ -116,14 +124,14 @@ func (it *nodeIterator) step() error {
return err
}
// Lookup the preimage of account hash
- preimage := it.state.trie.GetKey(it.stateIt.LeafKey())
+ preimage := it.tr.GetKey(it.stateIt.LeafKey())
if preimage == nil {
return errors.New("account address is not available")
}
address := common.BytesToAddress(preimage)
// Traverse the storage slots belong to the account
- dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie)
+ dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.tr)
if err != nil {
return err
}
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
index 3f83cb1a00..4488630095 100644
--- a/core/state/snapshot/generate_test.go
+++ b/core/state/snapshot/generate_test.go
@@ -168,6 +168,7 @@ func newHelper(scheme string) *testHelper {
if scheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{
SnapshotNoBuild: true,
+ NoAsyncFlush: true,
} // disable caching
} else {
config.HashDB = &hashdb.Config{} // disable caching
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 73a3b5146c..767f469bfd 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -124,6 +124,7 @@ func (s *stateObject) touch() {
// subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) {
if s.trie == nil {
+ // Assumes the primary account trie is already loaded
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if err != nil {
return nil, err
diff --git a/core/state/state_test.go b/core/state/state_test.go
index b443411f1b..eeeb7fa2df 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -54,8 +54,6 @@ func TestDump(t *testing.T) {
obj3.SetBalance(uint256.NewInt(44))
// write some of them to the trie
- s.state.updateStateObject(obj1)
- s.state.updateStateObject(obj2)
root, _ := s.state.Commit(0, false, false)
// check that DumpToCollector contains the state objects that are in trie
@@ -114,8 +112,6 @@ func TestIterativeDump(t *testing.T) {
obj4.AddBalance(uint256.NewInt(1337))
// write some of them to the trie
- s.state.updateStateObject(obj1)
- s.state.updateStateObject(obj2)
root, _ := s.state.Commit(0, false, false)
s.state, _ = New(root, tdb)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 6b474ea0a4..0d76e8c61c 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -79,8 +79,8 @@ func (m *mutation) isDelete() bool {
type StateDB struct {
db Database
prefetcher *triePrefetcher
- trie Trie
reader Reader
+ trie Trie // it's resolved on first access
// originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called.
@@ -169,13 +169,8 @@ func New(root common.Hash, db Database) (*StateDB, error) {
// NewWithReader creates a new state for the specified state root. Unlike New,
// this function accepts an additional Reader which is bound to the given root.
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
- tr, err := db.OpenTrie(root)
- if err != nil {
- return nil, err
- }
sdb := &StateDB{
db: db,
- trie: tr,
originalRoot: root,
reader: reader,
stateObjects: make(map[common.Address]*stateObject),
@@ -664,7 +659,6 @@ func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones
state := &StateDB{
db: s.db,
- trie: mustCopyTrie(s.trie),
reader: s.reader,
originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
@@ -688,6 +682,9 @@ func (s *StateDB) Copy() *StateDB {
transientStorage: s.transientStorage.Copy(),
journal: s.journal.copy(),
}
+ if s.trie != nil {
+ state.trie = mustCopyTrie(s.trie)
+ }
if s.witness != nil {
state.witness = s.witness.Copy()
}
@@ -783,6 +780,20 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects)
+ // Initialize the trie if it's not constructed yet. If the prefetch
+ // is enabled, the trie constructed below will be replaced by the
+ // prefetched one.
+ //
+ // This operation must be done before state object storage hashing,
+ // as it assumes the main trie is already loaded.
+ if s.trie == nil {
+ tr, err := s.db.OpenTrie(s.originalRoot)
+ if err != nil {
+ s.setError(err)
+ return common.Hash{}
+ }
+ s.trie = tr
+ }
// If there was a trie prefetcher operating, terminate it async so that the
// individual storage tries can be updated as soon as the disk load finishes.
if s.prefetcher != nil {
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 2c87d81d40..67e27cc832 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -171,7 +171,6 @@ func TestCopy(t *testing.T) {
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)))
- orig.updateStateObject(obj)
}
orig.Finalise(false)
@@ -190,10 +189,6 @@ func TestCopy(t *testing.T) {
origObj.AddBalance(uint256.NewInt(2 * uint64(i)))
copyObj.AddBalance(uint256.NewInt(3 * uint64(i)))
ccopyObj.AddBalance(uint256.NewInt(4 * uint64(i)))
-
- orig.updateStateObject(origObj)
- copy.updateStateObject(copyObj)
- ccopy.updateStateObject(copyObj)
}
// Finalise the changes on all concurrently
@@ -238,7 +233,6 @@ func TestCopyWithDirtyJournal(t *testing.T) {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef")
- orig.updateStateObject(obj)
}
root, _ := orig.Commit(0, true, false)
orig, _ = New(root, db)
@@ -248,8 +242,6 @@ func TestCopyWithDirtyJournal(t *testing.T) {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
amount := uint256.NewInt(uint64(i))
obj.SetBalance(new(uint256.Int).Sub(obj.Balance(), amount))
-
- orig.updateStateObject(obj)
}
cpy := orig.Copy()
@@ -284,7 +276,6 @@ func TestCopyObjectState(t *testing.T) {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef")
- orig.updateStateObject(obj)
}
orig.Finalise(true)
cpy := orig.Copy()
@@ -573,7 +564,7 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H
)
for it.Next() {
- key := common.BytesToHash(s.trie.GetKey(it.Key))
+ key := common.BytesToHash(tr.GetKey(it.Key))
visited[key] = true
if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) {
@@ -982,6 +973,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
TrieCleanSize: 0,
StateCleanSize: 0,
WriteBufferSize: 0,
+ NoAsyncFlush: true,
}}) // disable caching
} else {
tdb = triedb.NewDatabase(memDb, &triedb.Config{HashDB: &hashdb.Config{
@@ -1004,18 +996,25 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
// force-flush
tdb.Commit(root, false)
}
- // Create a new state on the old root
- state, _ = New(root, db)
// Now we clear out the memdb
it := memDb.NewIterator(nil, nil)
for it.Next() {
k := it.Key()
- // Leave the root intact
- if !bytes.Equal(k, root[:]) {
- t.Logf("key: %x", k)
- memDb.Delete(k)
+ if scheme == rawdb.HashScheme {
+ if !bytes.Equal(k, root[:]) {
+ t.Logf("key: %x", k)
+ memDb.Delete(k)
+ }
+ }
+ if scheme == rawdb.PathScheme {
+ rk := k[len(rawdb.TrieNodeAccountPrefix):]
+ if len(rk) != 0 {
+ t.Logf("key: %x", k)
+ memDb.Delete(k)
+ }
}
}
+ state, _ = New(root, db)
balance := state.GetBalance(addr)
// The removed elem should lead to it returning zero balance
if exp, got := uint64(0), balance.Uint64(); got != exp {
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index 5c8b5a90f7..cae0e0a936 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -46,7 +46,9 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c
// Create an empty state
config := &triedb.Config{Preimages: true}
if scheme == rawdb.PathScheme {
- config.PathDB = pathdb.Defaults
+ pconfig := *pathdb.Defaults
+ pconfig.NoAsyncFlush = true
+ config.PathDB = &pconfig
} else {
config.HashDB = hashdb.Defaults
}
diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go
index 2f40e3dc97..c0ce705c77 100644
--- a/core/state_prefetcher.go
+++ b/core/state_prefetcher.go
@@ -57,7 +57,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
workers errgroup.Group
reader = statedb.Reader()
)
- workers.SetLimit(4 * runtime.NumCPU() / 5) // Aggressively run the prefetching
+ workers.SetLimit(max(1, 4*runtime.NumCPU()/5)) // Aggressively run the prefetching
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
diff --git a/core/state_processor.go b/core/state_processor.go
index b778befd77..ee98326467 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -161,10 +161,9 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
// Merge the tx-local access event into the "block-local" one, in order to collect
// all values, so that the witness can be built.
- if statedb.GetTrie().IsVerkle() {
+ if statedb.Database().TrieDB().IsVerkle() {
statedb.AccessEvents().Merge(evm.AccessEvents)
}
-
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
}
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index 8f8f219597..539aaef40e 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -199,8 +199,8 @@ type BlockChain interface {
// InsertChain inserts a batch of blocks into the local chain.
InsertChain(types.Blocks) (int, error)
- // StopInsert interrupts the inserting process.
- StopInsert()
+ // InterruptInsert whether disables the chain insertion.
+ InterruptInsert(on bool)
// InsertReceiptChain inserts a batch of blocks along with their receipts
// into the local chain. Blocks older than the specified `ancientLimit`
@@ -630,16 +630,15 @@ func (d *Downloader) cancel() {
// Cancel aborts all of the operations and waits for all download goroutines to
// finish before returning.
func (d *Downloader) Cancel() {
+ d.blockchain.InterruptInsert(true)
d.cancel()
d.cancelWg.Wait()
+ d.blockchain.InterruptInsert(false)
}
// Terminate interrupts the downloader, canceling all pending operations.
// The downloader cannot be reused after calling Terminate.
func (d *Downloader) Terminate() {
- // Signal to stop inserting in-flight blocks
- d.blockchain.StopInsert()
-
// Close the termination channel (make sure double close is allowed)
d.quitLock.Lock()
select {
diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json
new file mode 100644
index 0000000000..14874dcc07
--- /dev/null
+++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json
@@ -0,0 +1,135 @@
+{
+ "genesis": {
+ "baseFeePerGas": "0x389ef14a",
+ "blobGasUsed": "0x120000",
+ "difficulty": "0x0",
+ "excessBlobGas": "0x20000",
+ "extraData": "0x6265617665726275696c642e6f7267",
+ "gasLimit": "0x225da53",
+ "hash": "0x9c1d4eb19d30fa830e02493f5108ddfd49f2736983cecb6b3748b79e78f98d14",
+ "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
+ "mixHash": "0x82bbbb55d5e4edf221aadaefe697f265210cc4afd8a0fa977769da5be8c100c0",
+ "nonce": "0x0000000000000000",
+ "number": "0x15b589d",
+ "parentBeaconBlockRoot": "0x64c714ee5b2d66ea6fd1f6633e41bf2863955c0b7a9e925a241f5e4e3c19f81e",
+ "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "stateRoot": "0x5c700c05128ae491d83b5602fd96f0faa487562441bef96ec9474c144820ed10",
+ "timestamp": "0x6858a55f",
+ "alloc": {
+ "0x17816e9a858b161c3e37016d139cf618056cacd4": {
+ "balance": "0x0",
+ "code": "0xef0100b684710e6d5914ad6e64493de2a3c424cc43e970",
+ "nonce": 15809
+ },
+ "0x236501327e701692a281934230af0b6be8df3353": {
+ "balance": "0x0",
+ "code": "0x6080604052600a600c565b005b60186014601a565b605e565b565b600060597f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e808015607c573d6000f35b3d6000fdfea26469706673582212200b737106e31d6abde738d261a4c4f12fcdfac5141ebc6ab5ffe4cf6e1630aaed64736f6c63430008140033",
+ "nonce": 1,
+ "storage": {
+ "0x078d9cc432fb3eab476f678ef9a73d8ca570f23897c68eb99b2721ebf46e5a9e": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc0",
+ "0x5555c0547520ec9521cc3134a71677625cdeb6accbb330321dcaf2cbc22c1fe9": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x84fdd52031be5dc8bcfa0ffd090a0bf85ef922e1fa9d026be0cf5716edafb4db": "0x0000000000000000000000000000000000000000007b74591c97f086c1057bee",
+ "0x8c854b3845c254f768d5435bc89fa04fb52bd2f72a1cf4370b962cf104ecd5fc": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0xc45aef11733ee3a84cf02368a8b99ca24b1e3bfc2f5f532a1a2439aa077d2843": "0x000000000000000000000000000000000000000000000738cda8f7729a2a8a1e",
+ "0xda699a88dd51ba5e1d66c40fd985a4ad1511875941c3dd2936300679d596ab7b": "0x0000000000000000000000000000000000000000000000000000000000000000"
+ }
+ },
+ "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": {
+ "balance": "0x8c2e6837fe7fb165",
+ "nonce": 1874580
+ },
+ "0xb684710e6d5914ad6e64493de2a3c424cc43e970": {
+ "balance": "0x0",
+ "code": "0x60806040525f4711156100b6575f3273ffffffffffffffffffffffffffffffffffffffff16476040516100319061048b565b5f6040518083038185875af1925050503d805f811461006b576040519150601f19603f3d011682016040523d82523d5f602084013e610070565b606091505b50509050806100b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100ab906104f9565b60405180910390fd5b505b73ffffffffffffffffffffffffffffffffffffffff80166001336100da9190610563565b73ffffffffffffffffffffffffffffffffffffffff161115610131576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610128906105f4565b60405180910390fd5b73b9df4a9ba45917e71d664d51462d46926e4798e873ffffffffffffffffffffffffffffffffffffffff166001336101699190610563565b73ffffffffffffffffffffffffffffffffffffffff160361045c575f8036906101929190610631565b5f1c90505f73cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff16631f3a71ba306040518263ffffffff1660e01b81526004016101e491906106af565b602060405180830381865afa1580156101ff573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022391906106ff565b90508181106104595773cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff1663a9059cbb5f836040518363ffffffff1660e01b815260040161027b929190610739565b6020604051808303815f875af1158015610297573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102bb9190610795565b6102fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102f1906104f9565b60405180910390fd5b5f73236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161034891906106af565b602060405180830381865afa158015610363573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061038791906106ff565b905073236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb32836040518363ffffffff1660e01b81526004016103d8929190610739565b6020604051808303815f875af11580156103f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104189190610795565b610457576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044e9061080a565b60405180910390fd5b505b50505b005b5f81905092915050565b50565b5f6104765f8361045e565b915061048182610468565b5f82019050919050565b5f6104958261046b565b9150819050919050565b5f82825260208201905092915050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f6104e3600f8361049f565b91506104ee826104af565b602082019050919050565b5f6020820190508181035f830152610510816104d7565b9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61056d82610517565b915061057883610517565b9250828201905073ffffffffffffffffffffffffffffffffffffffff8111156105a4576105a3610536565b5b92915050565b7f50616e69632831372900000000000000000000000000000000000000000000005f82015250565b5f6105de60098361049f565b91506105e9826105aa565b602082019050919050565b5f6020820190508181035f83015261060b816105d2565b9050919050565b5f82905092915050565b5f819050919050565b5f82821b905092915050565b5f61063c8383610612565b82610647813561061c565b92506020821015610687576106827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83602003600802610625565b831692505b505092915050565b5f61069982610517565b9050919050565b6106a98161068f565b82525050565b5f6020820190506106c25f8301846106a0565b92915050565b5f80fd5b5f819050919050565b6106de816106cc565b81146106e8575f80fd5b50565b5f815190506106f9816106d5565b92915050565b5f60208284031215610714576107136106c8565b5b5f610721848285016106eb565b91505092915050565b610733816106cc565b82525050565b5f60408201905061074c5f8301856106a0565b610759602083018461072a565b9392505050565b5f8115159050919050565b61077481610760565b811461077e575f80fd5b50565b5f8151905061078f8161076b565b92915050565b5f602082840312156107aa576107a96106c8565b5b5f6107b784828501610781565b91505092915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f6107f460158361049f565b91506107ff826107c0565b602082019050919050565b5f6020820190508181035f830152610821816107e8565b905091905056fea2646970667358221220b6a06cc7b930dc4e34352a145f3548d57ec5a60d0097c1979ef363376bf9a69164736f6c63430008140033",
+ "nonce": 1
+ },
+ "0xb9df4a9ba45917e71d664d51462d46926e4798e7": {
+ "balance": "0x597af049b190a724",
+ "code": "0xef0100000000009b1d0af20d8c6d0a44e162d11f9b8f00",
+ "nonce": 1887
+ },
+ "0xbdb50eff425fb2b1b67fea21b8420eeb6d99ccc0": {
+ "balance": "0x0",
+ "code": "0x6080604052600436106101cd5760003560e01c80637ecebe00116100f7578063a9059cbb11610095578063d505accf11610064578063d505accf146105b2578063dd62ed3e146105d2578063f1127ed814610637578063f2fde38b1461068357600080fd5b8063a9059cbb14610509578063ad3cb1cc14610529578063b119490e14610572578063c3cda5201461059257600080fd5b80638e539e8c116100d15780638e539e8c1461048857806391ddadf4146104a857806395d89b41146104d45780639ab24eb0146104e957600080fd5b80637ecebe001461040357806384b0196e146104235780638da5cb5b1461044b57600080fd5b80634bf5d7e91161016f5780635c19a95c1161013e5780635c19a95c146103795780636fcfff451461039957806370a08231146103ce578063715018a6146103ee57600080fd5b80634bf5d7e9146102dc5780634f1ef286146102f157806352d1902d14610306578063587cde1e1461031b57600080fd5b806323b872dd116101ab57806323b872dd1461026b578063313ce5671461028b5780633644e515146102a75780633a46b1a8146102bc57600080fd5b806306fdde03146101d2578063095ea7b3146101fd57806318160ddd1461022d575b600080fd5b3480156101de57600080fd5b506101e76106a3565b6040516101f49190612a81565b60405180910390f35b34801561020957600080fd5b5061021d610218366004612ab0565b61075e565b60405190151581526020016101f4565b34801561023957600080fd5b507f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b6040519081526020016101f4565b34801561027757600080fd5b5061021d610286366004612ada565b610778565b34801561029757600080fd5b50604051601281526020016101f4565b3480156102b357600080fd5b5061025d61079e565b3480156102c857600080fd5b5061025d6102d7366004612ab0565b6107ad565b3480156102e857600080fd5b506101e7610845565b6103046102ff366004612ba2565b6108d6565b005b34801561031257600080fd5b5061025d6108f5565b34801561032757600080fd5b50610361610336366004612c04565b6001600160a01b03908116600090815260008051602061312283398151915260205260409020541690565b6040516001600160a01b0390911681526020016101f4565b34801561038557600080fd5b50610304610394366004612c04565b610924565b3480156103a557600080fd5b506103b96103b4366004612c04565b61092f565b60405163ffffffff90911681526020016101f4565b3480156103da57600080fd5b5061025d6103e9366004612c04565b61093a565b3480156103fa57600080fd5b5061030461097f565b34801561040f57600080fd5b5061025d61041e366004612c04565b610993565b34801561042f57600080fd5b5061043861099e565b6040516101f49796959493929190612c1f565b34801561045757600080fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316610361565b34801561049457600080fd5b5061025d6104a3366004612cd1565b610a9a565b3480156104b457600080fd5b506104bd610b16565b60405165ffffffffffff90911681526020016101f4565b3480156104e057600080fd5b506101e7610b20565b3480156104f557600080fd5b5061025d610504366004612c04565b610b71565b34801561051557600080fd5b5061021d610524366004612ab0565b610bd1565b34801561053557600080fd5b506101e76040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561057e57600080fd5b5061030461058d366004612d0a565b610bdf565b34801561059e57600080fd5b506103046105ad366004612d88565b610d4b565b3480156105be57600080fd5b506103046105cd366004612de0565b610e21565b3480156105de57600080fd5b5061025d6105ed366004612e4a565b6001600160a01b0391821660009081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b34801561064357600080fd5b50610657610652366004612e7d565b610fac565b60408051825165ffffffffffff1681526020928301516001600160d01b031692810192909252016101f4565b34801561068f57600080fd5b5061030461069e366004612c04565b610fca565b606060007f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace005b90508060030180546106da90612ebd565b80601f016020809104026020016040519081016040528092919081815260200182805461070690612ebd565b80156107535780601f1061072857610100808354040283529160200191610753565b820191906000526020600020905b81548152906001019060200180831161073657829003601f168201915b505050505091505090565b60003361076c818585611021565b60019150505b92915050565b600033610786858285611033565b6107918585856110e9565b60019150505b9392505050565b60006107a8611161565b905090565b6000600080516020613122833981519152816107c7610b16565b90508065ffffffffffff16841061080757604051637669fc0f60e11b81526004810185905265ffffffffffff821660248201526044015b60405180910390fd5b6108336108138561116b565b6001600160a01b03871660009081526001850160205260409020906111a2565b6001600160d01b031695945050505050565b606061084f61125b565b65ffffffffffff1661085f610b16565b65ffffffffffff161461089e576040517f6ff0714000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060408051808201909152601d81527f6d6f64653d626c6f636b6e756d6265722666726f6d3d64656661756c74000000602082015290565b6108de611266565b6108e78261131d565b6108f18282611325565b5050565b60006108ff61140d565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b336108f18183611456565b600061077282611513565b6000807f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace005b6001600160a01b0390931660009081526020939093525050604090205490565b610987611564565b61099160006115d8565b565b600061077282611656565b600060608082808083817fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10080549091501580156109dd57506001810154155b610a43576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a6564000000000000000000000060448201526064016107fe565b610a4b611661565b610a536116b2565b604080516000808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009c939b5091995046985030975095509350915050565b600060008051602061312283398151915281610ab4610b16565b90508065ffffffffffff168410610aef57604051637669fc0f60e11b81526004810185905265ffffffffffff821660248201526044016107fe565b610b05610afb8561116b565b60028401906111a2565b6001600160d01b0316949350505050565b60006107a861125b565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060917f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00916106da90612ebd565b6001600160a01b03811660009081527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d016020526040812060008051602061312283398151915290610bc1906116dc565b6001600160d01b03169392505050565b60003361076c8185856110e9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff16600081158015610c2a5750825b905060008267ffffffffffffffff166001148015610c475750303b155b905081158015610c55575080155b15610c8c576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610cc057845468ff00000000000000001916680100000000000000001785555b610cca8888611718565b610cd38861172a565b610cdb611771565b610ce433611779565b610cec611771565b610cf6338761178a565b8315610d4157845468ff000000000000000019168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b83421115610d88576040517f4683af0e000000000000000000000000000000000000000000000000000000008152600481018590526024016107fe565b604080517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf60208201526001600160a01b038816918101919091526060810186905260808101859052600090610e0290610dfa9060a001604051602081830303815290604052805190602001206117c0565b858585611808565b9050610e0e8187611836565b610e188188611456565b50505050505050565b83421115610e5e576040517f62791302000000000000000000000000000000000000000000000000000000008152600481018590526024016107fe565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9888888610eca8c6001600160a01b031660009081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610f25826117c0565b90506000610f3582878787611808565b9050896001600160a01b0316816001600160a01b031614610f95576040517f4b800e460000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301528b1660248201526044016107fe565b610fa08a8a8a611021565b50505050505050505050565b604080518082019091526000808252602082015261079783836118c1565b610fd2611564565b6001600160a01b038116611015576040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b61101e816115d8565b50565b61102e838383600161192c565b505050565b6001600160a01b0383811660009081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace01602090815260408083209386168352929052205460001981146110e357818110156110d4576040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526001600160a01b038416600482015260248101829052604481018390526064016107fe565b6110e38484848403600061192c565b50505050565b6001600160a01b03831661112c576040517f96c6fd1e000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b0382166111565760405163ec442f0560e01b8152600060048201526024016107fe565b61102e838383611a58565b60006107a8611a63565b600065ffffffffffff82111561119e576040516306dfcc6560e41b815260306004820152602481018390526044016107fe565b5090565b8154600090818160058111156112015760006111bd84611ad7565b6111c79085612f0d565b60008881526020902090915081015465ffffffffffff90811690871610156111f1578091506111ff565b6111fc816001612f20565b92505b505b600061120f87878585611bbf565b9050801561124d5761123487611226600184612f0d565b600091825260209091200190565b54660100000000000090046001600160d01b0316611250565b60005b979650505050505050565b60006107a84361116b565b306001600160a01b037f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc01614806112ff57507f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc06001600160a01b03166112f37f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6001600160a01b031614155b156109915760405163703e46dd60e11b815260040160405180910390fd5b61101e611564565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561137f575060408051601f3d908101601f1916820190925261137c91810190612f33565b60015b6113a757604051634c9c8ce360e01b81526001600160a01b03831660048201526024016107fe565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8114611403576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b61102e8383611c21565b306001600160a01b037f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc016146109915760405163703e46dd60e11b815260040160405180910390fd5b6000805160206131228339815191526000611496846001600160a01b03908116600090815260008051602061312283398151915260205260409020541690565b6001600160a01b03858116600081815260208690526040808220805473ffffffffffffffffffffffffffffffffffffffff1916898616908117909155905194955093928516927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a46110e3818461150e87611c77565b611c82565b6001600160a01b03811660009081527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d0160205260408120546000805160206131228339815191529061079790611dfc565b336115967f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b031614610991576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016107fe565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300805473ffffffffffffffffffffffffffffffffffffffff1981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b600061077282611e2d565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100916106da90612ebd565b606060007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1006106c9565b8054600090801561170f576116f683611226600184612f0d565b54660100000000000090046001600160d01b0316610797565b60009392505050565b611720611e56565b6108f18282611ebd565b611732611e56565b61101e816040518060400160405280600181526020017f3100000000000000000000000000000000000000000000000000000000000000815250611f20565b610991611e56565b611781611e56565b61101e81611f93565b6001600160a01b0382166117b45760405163ec442f0560e01b8152600060048201526024016107fe565b6108f160008383611a58565b60006107726117cd611161565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b60008060008061181a88888888611f9b565b92509250925061182a828261206a565b50909695505050505050565b6001600160a01b03821660009081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915581811461102e576040517f752d88c00000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602481018290526044016107fe565b604080518082018252600080825260208083018290526001600160a01b03861682527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d0190529190912060008051602061312283398151915290611924908461216e565b949350505050565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace006001600160a01b038516611990576040517fe602df05000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b0384166119d3576040517f94280d62000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b03808616600090815260018301602090815260408083209388168352929052208390558115611a5157836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051611a4891815260200190565b60405180910390a35b5050505050565b61102e8383836121e1565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f611a8e612280565b611a966122fc565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600081600003611ae957506000919050565b60006001611af684612352565b901c6001901b90506001818481611b0f57611b0f612f4c565b048201901c90506001818481611b2757611b27612f4c565b048201901c90506001818481611b3f57611b3f612f4c565b048201901c90506001818481611b5757611b57612f4c565b048201901c90506001818481611b6f57611b6f612f4c565b048201901c90506001818481611b8757611b87612f4c565b048201901c90506001818481611b9f57611b9f612f4c565b048201901c905061079781828581611bb957611bb9612f4c565b046123e6565b60005b81831015611c19576000611bd684846123fc565b60008781526020902090915065ffffffffffff86169082015465ffffffffffff161115611c0557809250611c13565b611c10816001612f20565b93505b50611bc2565b509392505050565b611c2a82612417565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a2805115611c6f5761102e828261249b565b6108f1612511565b60006107728261093a565b6000805160206131228339815191526001600160a01b0384811690841614801590611cad5750600082115b156110e3576001600160a01b03841615611d57576001600160a01b038416600090815260018201602052604081208190611cf290612549611ced87612555565b612589565b6001600160d01b031691506001600160d01b03169150856001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611d4c929190918252602082015260400190565b60405180910390a250505b6001600160a01b038316156110e3576001600160a01b038316600090815260018201602052604081208190611d92906125c2611ced87612555565b6001600160d01b031691506001600160d01b03169150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611dec929190918252602082015260400190565b60405180910390a2505050505050565b600063ffffffff82111561119e576040516306dfcc6560e41b815260206004820152602481018390526044016107fe565b6000807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb0061095f565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16610991576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ec5611e56565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace007f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace03611f118482612fb0565b50600481016110e38382612fb0565b611f28611e56565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102611f748482612fb0565b5060038101611f838382612fb0565b5060008082556001909101555050565b610fd2611e56565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611fd65750600091506003905082612060565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa15801561202a573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661205657506000925060019150829050612060565b9250600091508190505b9450945094915050565b600082600381111561207e5761207e613070565b03612087575050565b600182600381111561209b5761209b613070565b036120d2576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028260038111156120e6576120e6613070565b03612120576040517ffce698f7000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b600382600381111561213457612134613070565b036108f1576040517fd78bce0c000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b6040805180820190915260008082526020820152826000018263ffffffff168154811061219d5761219d613086565b60009182526020918290206040805180820190915291015465ffffffffffff81168252660100000000000090046001600160d01b0316918101919091529392505050565b6121ec8383836125ce565b6001600160a01b0383166122755760006122247f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace025490565b90506001600160d01b0380821115612272576040517f1cb15d2600000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016107fe565b50505b61102e838383612737565b60007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100816122ac611661565b8051909150156122c457805160209091012092915050565b815480156122d3579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b60007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100816123286116b2565b80519091501561234057805160209091012092915050565b600182015480156122d3579392505050565b600080608083901c1561236757608092831c92015b604083901c1561237957604092831c92015b602083901c1561238b57602092831c92015b601083901c1561239d57601092831c92015b600883901c156123af57600892831c92015b600483901c156123c157600492831c92015b600283901c156123d357600292831c92015b600183901c156107725760010192915050565b60008183106123f55781610797565b5090919050565b600061240b600284841861309c565b61079790848416612f20565b806001600160a01b03163b60000361244d57604051634c9c8ce360e01b81526001600160a01b03821660048201526024016107fe565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516124b891906130be565b600060405180830381855af49150503d80600081146124f3576040519150601f19603f3d011682016040523d82523d6000602084013e6124f8565b606091505b50915091506125088583836127cd565b95945050505050565b3415610991576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061079782846130da565b60006001600160d01b0382111561119e576040516306dfcc6560e41b815260d06004820152602481018390526044016107fe565b6000806125b5612597610b16565b6125ad6125a3886116dc565b868863ffffffff16565b879190612842565b915091505b935093915050565b60006107978284613101565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace006001600160a01b03841661261c57818160020160008282546126119190612f20565b909155506126a79050565b6001600160a01b03841660009081526020829052604090205482811015612688576040517fe450d38c0000000000000000000000000000000000000000000000000000000081526001600160a01b038616600482015260248101829052604481018490526064016107fe565b6001600160a01b03851660009081526020839052604090209083900390555b6001600160a01b0383166126c55760028101805483900390556126e4565b6001600160a01b03831660009081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161272991815260200190565b60405180910390a350505050565b6000805160206131228339815191526001600160a01b03841661276a57612767816002016125c2611ced85612555565b50505b6001600160a01b03831661278e5761278b81600201612549611ced85612555565b50505b6001600160a01b03848116600090815260008051602061312283398151915260205260408082205486841683529120546110e392918216911684611c82565b6060826127e2576127dd82612850565b610797565b81511580156127f957506001600160a01b0384163b155b1561283b576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016107fe565b5080610797565b6000806125b5858585612892565b8051156128605780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8254600090819080156129d35760006128b087611226600185612f0d565b60408051808201909152905465ffffffffffff80821680845266010000000000009092046001600160d01b031660208401529192509087161015612920576040517f2520601d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805165ffffffffffff80881691160361296f578461294388611226600186612f0d565b80546001600160d01b039290921666010000000000000265ffffffffffff9092169190911790556129c3565b6040805180820190915265ffffffffffff80881682526001600160d01b0380881660208085019182528b54600181018d5560008d815291909120945191519092166601000000000000029216919091179101555b6020015192508391506125ba9050565b50506040805180820190915265ffffffffffff80851682526001600160d01b0380851660208085019182528854600181018a5560008a81529182209551925190931666010000000000000291909316179201919091559050816125ba565b60005b83811015612a4c578181015183820152602001612a34565b50506000910152565b60008151808452612a6d816020860160208601612a31565b601f01601f19169290920160200192915050565b6020815260006107976020830184612a55565b80356001600160a01b0381168114612aab57600080fd5b919050565b60008060408385031215612ac357600080fd5b612acc83612a94565b946020939093013593505050565b600080600060608486031215612aef57600080fd5b612af884612a94565b9250612b0660208501612a94565b9150604084013590509250925092565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff80841115612b4757612b47612b16565b604051601f8501601f19908116603f01168101908282118183101715612b6f57612b6f612b16565b81604052809350858152868686011115612b8857600080fd5b858560208301376000602087830101525050509392505050565b60008060408385031215612bb557600080fd5b612bbe83612a94565b9150602083013567ffffffffffffffff811115612bda57600080fd5b8301601f81018513612beb57600080fd5b612bfa85823560208401612b2c565b9150509250929050565b600060208284031215612c1657600080fd5b61079782612a94565b7fff00000000000000000000000000000000000000000000000000000000000000881681526000602060e081840152612c5b60e084018a612a55565b8381036040850152612c6d818a612a55565b606085018990526001600160a01b038816608086015260a0850187905284810360c0860152855180825283870192509083019060005b81811015612cbf57835183529284019291840191600101612ca3565b50909c9b505050505050505050505050565b600060208284031215612ce357600080fd5b5035919050565b600082601f830112612cfb57600080fd5b61079783833560208501612b2c565b600080600060608486031215612d1f57600080fd5b833567ffffffffffffffff80821115612d3757600080fd5b612d4387838801612cea565b94506020860135915080821115612d5957600080fd5b50612d6686828701612cea565b925050604084013590509250925092565b803560ff81168114612aab57600080fd5b60008060008060008060c08789031215612da157600080fd5b612daa87612a94565b95506020870135945060408701359350612dc660608801612d77565b92506080870135915060a087013590509295509295509295565b600080600080600080600060e0888a031215612dfb57600080fd5b612e0488612a94565b9650612e1260208901612a94565b95506040880135945060608801359350612e2e60808901612d77565b925060a0880135915060c0880135905092959891949750929550565b60008060408385031215612e5d57600080fd5b612e6683612a94565b9150612e7460208401612a94565b90509250929050565b60008060408385031215612e9057600080fd5b612e9983612a94565b9150602083013563ffffffff81168114612eb257600080fd5b809150509250929050565b600181811c90821680612ed157607f821691505b602082108103612ef157634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561077257610772612ef7565b8082018082111561077257610772612ef7565b600060208284031215612f4557600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b601f82111561102e57600081815260208120601f850160051c81016020861015612f895750805b601f850160051c820191505b81811015612fa857828155600101612f95565b505050505050565b815167ffffffffffffffff811115612fca57612fca612b16565b612fde81612fd88454612ebd565b84612f62565b602080601f8311600181146130135760008415612ffb5750858301515b600019600386901b1c1916600185901b178555612fa8565b600085815260208120601f198616915b8281101561304257888601518255948401946001909101908401613023565b50858210156130605787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000826130b957634e487b7160e01b600052601260045260246000fd5b500490565b600082516130d0818460208701612a31565b9190910192915050565b6001600160d01b038281168282160390808211156130fa576130fa612ef7565b5092915050565b6001600160d01b038181168382160190808211156130fa576130fa612ef756fee8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d00a2646970667358221220c2a4c7c504a36ab9781f5fb312d81d27f781047ab9f97621c7f031a185ecb78864736f6c63430008140033",
+ "nonce": 1
+ },
+ "0xcda6461f1a30c618373f5790a83e1569fb685cba": {
+ "balance": "0x0",
+ "code": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063313ce5671161008c578063a9059cbb11610066578063a9059cbb146102ab578063dd62ed3e146102be578063e6fd48bc146102d4578063fc0c546a146102fb57600080fd5b8063313ce567146101f857806370a082311461023157806395d89b411461025157600080fd5b80631514617e116100c85780631514617e146101a857806318160ddd146101cf5780631f3a71ba146101d757806323b872dd146101ea57600080fd5b80630483a7f6146100ef57806306fdde0314610122578063095ea7b314610185575b600080fd5b61010f6100fd366004610926565b60006020819052908152604090205481565b6040519081526020015b60405180910390f35b604080517f466c75656e636520546f6b656e20284c6f636b65642900000000000000000000602082015281519082019091527f000000000000000000000000000000000000000000000000000000000000001681525b6040516101199190610965565b610198610193366004610998565b61033a565b6040519015158152602001610119565b61010f7f0000000000000000000000000000000000000000000000000000000001e1338081565b60025461010f565b61010f6101e5366004610926565b61038a565b6101986101933660046109c2565b61021f7f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff9091168152602001610119565b61010f61023f366004610926565b60016020526000908152604090205481565b604080517f464c542d4c000000000000000000000000000000000000000000000000000000602082015281519082019091527f00000000000000000000000000000000000000000000000000000000000000058152610178565b6101986102b9366004610998565b610485565b61010f6102cc3660046109fe565b600092915050565b61010f7f0000000000000000000000000000000000000000000000000000000067afabe881565b6103227f000000000000000000000000236501327e701692a281934230af0b6be8df335381565b6040516001600160a01b039091168152602001610119565b60405162461bcd60e51b815260206004820152601560248201527f556e737570706f72746564206f7065726174696f6e000000000000000000000060448201526000906064015b60405180910390fd5b60007f0000000000000000000000000000000000000000000000000000000067afabe842116103bb57506000919050565b6001600160a01b0382166000908152602081815260408083205460019092528220547f0000000000000000000000000000000000000000000000000000000001e13380929061040a9083610a47565b905060006104387f0000000000000000000000000000000000000000000000000000000067afabe842610a47565b905060008482106104545761044d8385610a47565b905061047b565b60006104608686610a5a565b90508361046d8285610a7c565b6104779190610a47565b9150505b9695505050505050565b60006001600160a01b038316156105045760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616c6c6f776564206f6e6c7920746f20746865207a657260448201527f6f206164647265737300000000000000000000000000000000000000000000006064820152608401610381565b3361050f8184610568565b836001600160a01b0316816001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8560405161055491815260200190565b60405180910390a360019150505b92915050565b60006105738361038a565b9050600081116105c55760405162461bcd60e51b815260206004820152601d60248201527f4e6f7420656e6f756768207468652072656c6561736520616d6f756e740000006044820152606401610381565b8115610620578082111561061b5760405162461bcd60e51b815260206004820152601d60248201527f4e6f7420656e6f756768207468652072656c6561736520616d6f756e740000006044820152606401610381565b610624565b8091505b6001600160a01b0383166000908152600160205260408120805484929061064c908490610a47565b9250508190555081600260008282546106659190610a47565b9091555061069f90506001600160a01b037f000000000000000000000000236501327e701692a281934230af0b6be8df33531684846106a4565b505050565b604080516001600160a01b03848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261069f9185919060009061073090841683610797565b905080516000141580156107555750808060200190518101906107539190610a93565b155b1561069f576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610381565b60606107a5838360006107ac565b9392505050565b6060814710156107ea576040517fcd786059000000000000000000000000000000000000000000000000000000008152306004820152602401610381565b600080856001600160a01b031684866040516108069190610ab5565b60006040518083038185875af1925050503d8060008114610843576040519150601f19603f3d011682016040523d82523d6000602084013e610848565b606091505b509150915061047b86838360608261086857610863826108c8565b6107a5565b815115801561087f57506001600160a01b0384163b155b156108c1576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610381565b50806107a5565b8051156108d85780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461092157600080fd5b919050565b60006020828403121561093857600080fd5b6107a58261090a565b60005b8381101561095c578181015183820152602001610944565b50506000910152565b6020815260008251806020840152610984816040850160208701610941565b601f01601f19169190910160400192915050565b600080604083850312156109ab57600080fd5b6109b48361090a565b946020939093013593505050565b6000806000606084860312156109d757600080fd5b6109e08461090a565b92506109ee6020850161090a565b9150604084013590509250925092565b60008060408385031215610a1157600080fd5b610a1a8361090a565b9150610a286020840161090a565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561056257610562610a31565b600082610a7757634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761056257610562610a31565b600060208284031215610aa557600080fd5b815180151581146107a557600080fd5b60008251610ac7818460208701610941565b919091019291505056fea2646970667358221220aa9a251bde32306273cb5f6045040ac4b74b767bd02205c60c6003c5346ac34c64736f6c63430008140033",
+ "nonce": 1,
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000007b74591c97f086c1057bee",
+ "0x4f2aab765280a617b8913308bffbaed810827576241edbcd290b48d2b699bf92": "0x0000000000000000000000000000000000000000000580926bcba6406ba40000",
+ "0xd057d56b4d1539d5c08615edc01a9792908fefc021b63dbdc5db20bf522e882e": "0x00000000000000000000000000000000000000000003920c271ee5a29be97bee"
+ }
+ }
+ },
+ "config": {
+ "chainId": 1,
+ "homesteadBlock": 1150000,
+ "daoForkBlock": 1920000,
+ "daoForkSupport": true,
+ "eip150Block": 2463000,
+ "eip155Block": 2675000,
+ "eip158Block": 2675000,
+ "byzantiumBlock": 4370000,
+ "constantinopleBlock": 7280000,
+ "petersburgBlock": 7280000,
+ "istanbulBlock": 9069000,
+ "muirGlacierBlock": 9200000,
+ "berlinBlock": 12244000,
+ "londonBlock": 12965000,
+ "arrowGlacierBlock": 13773000,
+ "grayGlacierBlock": 15050000,
+ "shanghaiTime": 1681338455,
+ "cancunTime": 1710338135,
+ "pragueTime": 1746612311,
+ "terminalTotalDifficulty": 58750000000000000000000,
+ "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa",
+ "ethash": {},
+ "blobSchedule": {
+ "cancun": {
+ "target": 3,
+ "max": 6,
+ "baseFeeUpdateFraction": 3338477
+ },
+ "prague": {
+ "target": 6,
+ "max": 9,
+ "baseFeeUpdateFraction": 5007716
+ }
+ }
+ }
+ },
+ "context": {
+ "number": "0x15b589e",
+ "timestamp": "0x6858a56b",
+ "difficulty": "0x0",
+ "gasLimit": "0x22550de",
+ "miner": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
+ "baseFeePerGas": "0x38e42046"
+ },
+ "input": "0x04f8ec0182075f830f424084714d24d7830493e09417816e9a858b161c3e37016d139cf618056cacd480a000000000000000000000000000000000000000000000000316580c3ab7e66cc4c0f85ef85c0194b684710e6d5914ad6e64493de2a3c424cc43e970823dc101a02f15ba55009fcd3682cd0f9c9645dd94e616f9a969ba3f1a5a2d871f9fe0f2b4a053c332a83312d0b17dd4c16eeb15b1ff5223398b14e0a55c70762e8f3972b7a580a02aceec9737d2a211c79aff3dbd4bf44a5cdabbdd6bbe19ff346a89d94d61914aa062e92842bfe7d2f3ff785c594c70fafafcb180fb32a774de1b92c588be8cd87b",
+ "result": {
+ "0x17816e9a858b161c3e37016d139cf618056cacd4": {
+ "balance": "0x0",
+ "code": "0xef0100b684710e6d5914ad6e64493de2a3c424cc43e970",
+ "nonce": 15809
+ },
+ "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": {
+ "balance": "0x8c2e6837fe7fb165",
+ "nonce": 1874580
+ },
+ "0xb684710e6d5914ad6e64493de2a3c424cc43e970": {
+ "balance": "0x0",
+ "code": "0x60806040525f4711156100b6575f3273ffffffffffffffffffffffffffffffffffffffff16476040516100319061048b565b5f6040518083038185875af1925050503d805f811461006b576040519150601f19603f3d011682016040523d82523d5f602084013e610070565b606091505b50509050806100b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100ab906104f9565b60405180910390fd5b505b73ffffffffffffffffffffffffffffffffffffffff80166001336100da9190610563565b73ffffffffffffffffffffffffffffffffffffffff161115610131576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610128906105f4565b60405180910390fd5b73b9df4a9ba45917e71d664d51462d46926e4798e873ffffffffffffffffffffffffffffffffffffffff166001336101699190610563565b73ffffffffffffffffffffffffffffffffffffffff160361045c575f8036906101929190610631565b5f1c90505f73cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff16631f3a71ba306040518263ffffffff1660e01b81526004016101e491906106af565b602060405180830381865afa1580156101ff573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022391906106ff565b90508181106104595773cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff1663a9059cbb5f836040518363ffffffff1660e01b815260040161027b929190610739565b6020604051808303815f875af1158015610297573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102bb9190610795565b6102fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102f1906104f9565b60405180910390fd5b5f73236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161034891906106af565b602060405180830381865afa158015610363573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061038791906106ff565b905073236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb32836040518363ffffffff1660e01b81526004016103d8929190610739565b6020604051808303815f875af11580156103f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104189190610795565b610457576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044e9061080a565b60405180910390fd5b505b50505b005b5f81905092915050565b50565b5f6104765f8361045e565b915061048182610468565b5f82019050919050565b5f6104958261046b565b9150819050919050565b5f82825260208201905092915050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f6104e3600f8361049f565b91506104ee826104af565b602082019050919050565b5f6020820190508181035f830152610510816104d7565b9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61056d82610517565b915061057883610517565b9250828201905073ffffffffffffffffffffffffffffffffffffffff8111156105a4576105a3610536565b5b92915050565b7f50616e69632831372900000000000000000000000000000000000000000000005f82015250565b5f6105de60098361049f565b91506105e9826105aa565b602082019050919050565b5f6020820190508181035f83015261060b816105d2565b9050919050565b5f82905092915050565b5f819050919050565b5f82821b905092915050565b5f61063c8383610612565b82610647813561061c565b92506020821015610687576106827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83602003600802610625565b831692505b505092915050565b5f61069982610517565b9050919050565b6106a98161068f565b82525050565b5f6020820190506106c25f8301846106a0565b92915050565b5f80fd5b5f819050919050565b6106de816106cc565b81146106e8575f80fd5b50565b5f815190506106f9816106d5565b92915050565b5f60208284031215610714576107136106c8565b5b5f610721848285016106eb565b91505092915050565b610733816106cc565b82525050565b5f60408201905061074c5f8301856106a0565b610759602083018461072a565b9392505050565b5f8115159050919050565b61077481610760565b811461077e575f80fd5b50565b5f8151905061078f8161076b565b92915050565b5f602082840312156107aa576107a96106c8565b5b5f6107b784828501610781565b91505092915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f6107f460158361049f565b91506107ff826107c0565b602082019050919050565b5f6020820190508181035f830152610821816107e8565b905091905056fea2646970667358221220b6a06cc7b930dc4e34352a145f3548d57ec5a60d0097c1979ef363376bf9a69164736f6c63430008140033",
+ "nonce": 1
+ },
+ "0xb9df4a9ba45917e71d664d51462d46926e4798e7": {
+ "balance": "0x597af049b190a724",
+ "code": "0xef0100000000009b1d0af20d8c6d0a44e162d11f9b8f00",
+ "nonce": 1887
+ }
+ }
+}
diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json
index 043130a072..121509f132 100644
--- a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json
+++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json
@@ -81,6 +81,10 @@
"0x0000000000000000000000000000000000000000": {
"balance": "0x272e0528"
},
+ "0x000000000000000000000000000000000000bbbb": {
+ "balance": "0x0",
+ "code": "0x6042604255"
+ },
"0x703c4b2bd70c169f5717101caee543299fc946c7": {
"balance": "0xde0b6b3a7640000",
"storage": {
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index a2d598adaa..57c66ae327 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -61,15 +61,16 @@ type accountMarshaling struct {
}
type prestateTracer struct {
- env *tracing.VMContext
- pre stateMap
- post stateMap
- to common.Address
- config prestateTracerConfig
- interrupt atomic.Bool // Atomic flag to signal execution interruption
- reason error // Textual reason for the interruption
- created map[common.Address]bool
- deleted map[common.Address]bool
+ env *tracing.VMContext
+ pre stateMap
+ post stateMap
+ to common.Address
+ config prestateTracerConfig
+ chainConfig *params.ChainConfig
+ interrupt atomic.Bool // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+ created map[common.Address]bool
+ deleted map[common.Address]bool
}
type prestateTracerConfig struct {
@@ -90,11 +91,12 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *p
return nil, errors.New("cannot use diffMode with includeEmpty")
}
t := &prestateTracer{
- pre: stateMap{},
- post: stateMap{},
- config: config,
- created: make(map[common.Address]bool),
- deleted: make(map[common.Address]bool),
+ pre: stateMap{},
+ post: stateMap{},
+ config: config,
+ chainConfig: chainConfig,
+ created: make(map[common.Address]bool),
+ deleted: make(map[common.Address]bool),
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
@@ -133,6 +135,13 @@ func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scop
case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
addr := common.Address(stackData[stackLen-2].Bytes20())
t.lookupAccount(addr)
+ // Lookup the delegation target
+ if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) {
+ code := t.env.StateDB.GetCode(addr)
+ if target, ok := types.ParseDelegation(code); ok {
+ t.lookupAccount(target)
+ }
+ }
case op == vm.CREATE:
nonce := t.env.StateDB.GetNonce(caller)
addr := crypto.CreateAddress(caller, nonce)
@@ -161,6 +170,13 @@ func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction
t.created[t.to] = true
} else {
t.to = *tx.To()
+ // Lookup the delegation target
+ if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) {
+ code := t.env.StateDB.GetCode(t.to)
+ if target, ok := types.ParseDelegation(code); ok {
+ t.lookupAccount(target)
+ }
+ }
}
t.lookupAccount(from)
diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go
index ac9eb9bdba..58a521f6fb 100644
--- a/ethdb/pebble/pebble.go
+++ b/ethdb/pebble/pebble.go
@@ -83,6 +83,7 @@ type Database struct {
estimatedCompDebtGauge *metrics.Gauge // Gauge for tracking the number of bytes that need to be compacted
liveCompGauge *metrics.Gauge // Gauge for tracking the number of in-progress compactions
liveCompSizeGauge *metrics.Gauge // Gauge for tracking the size of in-progress compactions
+ liveIterGauge *metrics.Gauge // Gauge for tracking the number of live database iterators
levelsGauge []*metrics.Gauge // Gauge for tracking the number of tables in levels
quitLock sync.RWMutex // Mutex protecting the quit channel and the closed flag
@@ -326,6 +327,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
db.estimatedCompDebtGauge = metrics.GetOrRegisterGauge(namespace+"compact/estimateDebt", nil)
db.liveCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/count", nil)
db.liveCompSizeGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/size", nil)
+ db.liveIterGauge = metrics.GetOrRegisterGauge(namespace+"iter/count", nil)
// Start up the metrics gathering and return
go db.meter(metricsGatheringInterval, namespace)
@@ -582,6 +584,7 @@ func (d *Database) meter(refresh time.Duration, namespace string) {
d.seekCompGauge.Update(stats.Compact.ReadCount)
d.liveCompGauge.Update(stats.Compact.NumInProgress)
d.liveCompSizeGauge.Update(stats.Compact.InProgressBytes)
+ d.liveIterGauge.Update(stats.TableIters)
d.liveMemTablesGauge.Update(stats.MemTable.Count)
d.zombieMemTablesGauge.Update(stats.MemTable.ZombieCount)
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 8f736226c7..db0297ae5a 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1831,8 +1831,16 @@ func (api *DebugAPI) ChaindbCompact() error {
}
// SetHead rewinds the head of the blockchain to a previous block.
-func (api *DebugAPI) SetHead(number hexutil.Uint64) {
+func (api *DebugAPI) SetHead(number hexutil.Uint64) error {
+ header := api.b.CurrentHeader()
+ if header == nil {
+ return errors.New("current header is not available")
+ }
+ if header.Number.Uint64() <= uint64(number) {
+ return errors.New("not allowed to rewind to a future block")
+ }
api.b.SetHead(uint64(number))
+ return nil
}
// NetAPI offers network related RPC methods
diff --git a/miner/miner.go b/miner/miner.go
index 3cec054d19..ddfe0dcf26 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -52,7 +52,7 @@ type Config struct {
// DefaultConfig contains default settings for miner.
var DefaultConfig = Config{
- GasCeil: 36_000_000,
+ GasCeil: 45_000_000,
GasPrice: big.NewInt(params.GWei / 1000),
// The default recommit time is chosen as two seconds since
diff --git a/triedb/database.go b/triedb/database.go
index 1c9d49b245..03d07c1c1b 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -317,6 +317,16 @@ func (db *Database) Journal(root common.Hash) error {
return pdb.Journal(root)
}
+// VerifyState traverses the flat states specified by the given state root and
+// ensures they are matched with each other.
+func (db *Database) VerifyState(root common.Hash) error {
+ pdb, ok := db.backend.(*pathdb.Database)
+ if !ok {
+ return errors.New("not supported")
+ }
+ return pdb.VerifyState(root)
+}
+
// AccountIterator creates a new account iterator for the specified root hash and
// seeks to a starting account hash.
func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (pathdb.AccountIterator, error) {
diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go
index e639a43835..138962110f 100644
--- a/triedb/pathdb/buffer.go
+++ b/triedb/pathdb/buffer.go
@@ -17,6 +17,7 @@
package pathdb
import (
+ "errors"
"fmt"
"time"
@@ -37,6 +38,13 @@ type buffer struct {
limit uint64 // The maximum memory allowance in bytes
nodes *nodeSet // Aggregated trie node set
states *stateSet // Aggregated state set
+
+ // done is the notifier whether the content in buffer has been flushed or not.
+ // This channel is nil if the buffer is not frozen.
+ done chan struct{}
+
+ // flushErr memorizes the error if any exception occurs during flushing
+ flushErr error
}
// newBuffer initializes the buffer with the provided states and trie nodes.
@@ -61,7 +69,7 @@ func (b *buffer) account(hash common.Hash) ([]byte, bool) {
return b.states.account(hash)
}
-// storage retrieves the storage slot with account address hash and slot key.
+// storage retrieves the storage slot with account address hash and slot key hash.
func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) {
return b.states.storage(addrHash, storageHash)
}
@@ -124,43 +132,78 @@ func (b *buffer) size() uint64 {
// flush persists the in-memory dirty trie node into the disk if the configured
// memory threshold is reached. Note, all data must be written atomically.
-func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64) error {
- // Ensure the target state id is aligned with the internal counter.
- head := rawdb.ReadPersistentStateID(db)
- if head+b.layers != id {
- return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
+func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64, postFlush func()) {
+ if b.done != nil {
+ panic("duplicated flush operation")
}
- // Terminate the state snapshot generation if it's active
- var (
- start = time.Now()
- batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff
- )
- // Explicitly sync the state freezer to ensure all written data is persisted to disk
- // before updating the key-value store.
- //
- // This step is crucial to guarantee that the corresponding state history remains
- // available for state rollback.
- if freezer != nil {
- if err := freezer.SyncAncient(); err != nil {
- return err
- }
- }
- nodes := b.nodes.write(batch, nodesCache)
- accounts, slots := b.states.write(batch, progress, statesCache)
- rawdb.WritePersistentStateID(batch, id)
- rawdb.WriteSnapshotRoot(batch, root)
+ b.done = make(chan struct{}) // allocate the channel for notification
- // Flush all mutations in a single batch
- size := batch.ValueSize()
- if err := batch.Write(); err != nil {
- return err
- }
- commitBytesMeter.Mark(int64(size))
- commitNodesMeter.Mark(int64(nodes))
- commitAccountsMeter.Mark(int64(accounts))
- commitStoragesMeter.Mark(int64(slots))
- commitTimeTimer.UpdateSince(start)
- b.reset()
- log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
- return nil
+ // Schedule the background thread to construct the batch, which usually
+ // take a few seconds.
+ go func() {
+ defer func() {
+ if postFlush != nil {
+ postFlush()
+ }
+ close(b.done)
+ }()
+
+ // Ensure the target state id is aligned with the internal counter.
+ head := rawdb.ReadPersistentStateID(db)
+ if head+b.layers != id {
+ b.flushErr = fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
+ return
+ }
+
+ // Terminate the state snapshot generation if it's active
+ var (
+ start = time.Now()
+ batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff
+ )
+ // Explicitly sync the state freezer to ensure all written data is persisted to disk
+ // before updating the key-value store.
+ //
+ // This step is crucial to guarantee that the corresponding state history remains
+ // available for state rollback.
+ if freezer != nil {
+ if err := freezer.SyncAncient(); err != nil {
+ b.flushErr = err
+ return
+ }
+ }
+ nodes := b.nodes.write(batch, nodesCache)
+ accounts, slots := b.states.write(batch, progress, statesCache)
+ rawdb.WritePersistentStateID(batch, id)
+ rawdb.WriteSnapshotRoot(batch, root)
+
+ // Flush all mutations in a single batch
+ size := batch.ValueSize()
+ if err := batch.Write(); err != nil {
+ b.flushErr = err
+ return
+ }
+ commitBytesMeter.Mark(int64(size))
+ commitNodesMeter.Mark(int64(nodes))
+ commitAccountsMeter.Mark(int64(accounts))
+ commitStoragesMeter.Mark(int64(slots))
+ commitTimeTimer.UpdateSince(start)
+
+ // The content in the frozen buffer is kept for consequent state access,
+ // TODO (rjl493456442) measure the gc overhead for holding this struct.
+ // TODO (rjl493456442) can we somehow get rid of it after flushing??
+ // TODO (rjl493456442) buffer itself is not thread-safe, add the lock
+ // protection if try to reset the buffer here.
+ // b.reset()
+ log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
+ }()
+}
+
+// waitFlush blocks until the buffer has been fully flushed and returns any
+// stored errors that occurred during the process.
+func (b *buffer) waitFlush() error {
+ if b.done == nil {
+ return errors.New("the buffer is not frozen")
+ }
+ <-b.done
+ return b.flushErr
}
diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go
index 3174a7c964..8932f3f7f8 100644
--- a/triedb/pathdb/database.go
+++ b/triedb/pathdb/database.go
@@ -114,12 +114,17 @@ type layer interface {
// Config contains the settings for database.
type Config struct {
- StateHistory uint64 // Number of recent blocks to maintain state history for
- TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie nodes
- StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
- WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer
- ReadOnly bool // Flag whether the database is opened in read only mode
- SnapshotNoBuild bool // Flag Whether the background generation is allowed
+ StateHistory uint64 // Number of recent blocks to maintain state history for
+ EnableStateIndexing bool // Whether to enable state history indexing for external state access
+ TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie nodes
+ StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data
+ WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer
+ ReadOnly bool // Flag whether the database is opened in read only mode
+
+ // Testing configurations
+ SnapshotNoBuild bool // Flag Whether the state generation is allowed
+ NoAsyncFlush bool // Flag whether the background buffer flushing is allowed
+ NoAsyncGeneration bool // Flag whether the background generation is allowed
}
// sanitize checks the provided user configurations and changes anything that's
@@ -145,7 +150,12 @@ func (c *Config) fields() []interface{} {
list = append(list, "triecache", common.StorageSize(c.TrieCleanSize))
list = append(list, "statecache", common.StorageSize(c.StateCleanSize))
list = append(list, "buffer", common.StorageSize(c.WriteBufferSize))
- list = append(list, "history", c.StateHistory)
+
+ if c.StateHistory == 0 {
+ list = append(list, "history", "entire chain")
+ } else {
+ list = append(list, "history", fmt.Sprintf("last %d blocks", c.StateHistory))
+ }
return list
}
@@ -209,6 +219,7 @@ type Database struct {
tree *layerTree // The group for all known layers
freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
+ indexer *historyIndexer // History indexer
}
// New attempts to load an already existing layer from a persistent key-value
@@ -258,6 +269,11 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
if err := db.setStateGenerator(); err != nil {
log.Crit("Failed to setup the generator", "err", err)
}
+ // TODO (rjl493456442) disable the background indexing in read-only mode
+ if db.freezer != nil && db.config.EnableStateIndexing {
+ db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID())
+ log.Info("Enabled state history indexing")
+ }
fields := config.fields()
if db.isVerkle {
fields = append(fields, "verkle", true)
@@ -295,6 +311,11 @@ func (db *Database) repairHistory() error {
log.Crit("Failed to retrieve head of state history", "err", err)
}
if frozen != 0 {
+ // TODO(rjl493456442) would be better to group them into a batch.
+ //
+ // Purge all state history indexing data first
+ rawdb.DeleteStateHistoryIndexMetadata(db.diskdb)
+ rawdb.DeleteStateHistoryIndex(db.diskdb)
err := db.freezer.Reset()
if err != nil {
log.Crit("Failed to reset state histories", "err", err)
@@ -366,6 +387,12 @@ func (db *Database) setStateGenerator() error {
}
stats.log("Starting snapshot generation", root, generator.Marker)
dl.generator.run(root)
+
+ // Block until the generation completes. It's the feature used in
+ // unit tests.
+ if db.config.NoAsyncGeneration {
+ <-dl.generator.done
+ }
return nil
}
@@ -434,8 +461,8 @@ func (db *Database) Disable() error {
// Terminate the state generator if it's active and mark the disk layer
// as stale to prevent access to persistent state.
disk := db.tree.bottom()
- if disk.generator != nil {
- disk.generator.stop()
+ if err := disk.terminate(); err != nil {
+ return err
}
disk.markStale()
@@ -477,6 +504,11 @@ func (db *Database) Enable(root common.Hash) error {
// mappings can be huge and might take a while to clear
// them, just leave them in disk and wait for overwriting.
if db.freezer != nil {
+ // TODO(rjl493456442) would be better to group them into a batch.
+ //
+ // Purge all state history indexing data first
+ rawdb.DeleteStateHistoryIndexMetadata(db.diskdb)
+ rawdb.DeleteStateHistoryIndex(db.diskdb)
if err := db.freezer.Reset(); err != nil {
return err
}
@@ -592,13 +624,19 @@ func (db *Database) Close() error {
// following mutations.
db.readOnly = true
- // Terminate the background generation if it's active
- disk := db.tree.bottom()
- if disk.generator != nil {
- disk.generator.stop()
+ // Block until the background flushing is finished. It must
+ // be done before terminating the potential background snapshot
+ // generator.
+ dl := db.tree.bottom()
+ if err := dl.terminate(); err != nil {
+ return err
}
- disk.resetCache() // release the memory held by clean cache
+ dl.resetCache() // release the memory held by clean cache
+ // Terminate the background state history indexer
+ if db.indexer != nil {
+ db.indexer.close()
+ }
// Close the attached state history freezer.
if db.freezer == nil {
return nil
@@ -662,16 +700,6 @@ func (db *Database) HistoryRange() (uint64, uint64, error) {
return historyRange(db.freezer)
}
-// waitGeneration waits until the background generation is finished. It assumes
-// that the generation is permitted; otherwise, it will block indefinitely.
-func (db *Database) waitGeneration() {
- gen := db.tree.bottom().generator
- if gen == nil || gen.completed() {
- return
- }
- <-gen.done
-}
-
// AccountIterator creates a new account iterator for the specified root hash and
// seeks to a starting account hash.
func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) {
@@ -681,7 +709,7 @@ func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (Account
if wait {
return nil, errDatabaseWaitSync
}
- if gen := db.tree.bottom().generator; gen != nil && !gen.completed() {
+ if !db.tree.bottom().genComplete() {
return nil, errNotConstructed
}
return newFastAccountIterator(db, root, seek)
@@ -696,7 +724,7 @@ func (db *Database) StorageIterator(root common.Hash, account common.Hash, seek
if wait {
return nil, errDatabaseWaitSync
}
- if gen := db.tree.bottom().generator; gen != nil && !gen.completed() {
+ if !db.tree.bottom().genComplete() {
return nil, errNotConstructed
}
return newFastStorageIterator(db, root, account, seek)
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index ca106cc27c..2982202009 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -121,14 +121,16 @@ type tester struct {
snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte // Keyed by the hash of account address and the hash of storage key
}
-func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *tester {
+func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int, enableIndex bool) *tester {
var (
disk, _ = rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()})
db = New(disk, &Config{
- StateHistory: historyLimit,
- TrieCleanSize: 256 * 1024,
- StateCleanSize: 256 * 1024,
- WriteBufferSize: 256 * 1024,
+ StateHistory: historyLimit,
+ EnableStateIndexing: enableIndex,
+ TrieCleanSize: 256 * 1024,
+ StateCleanSize: 256 * 1024,
+ WriteBufferSize: 256 * 1024,
+ NoAsyncFlush: true,
}, isVerkle)
obj = &tester{
@@ -163,6 +165,20 @@ func (t *tester) hashPreimage(hash common.Hash) common.Hash {
return common.BytesToHash(t.preimages[hash])
}
+func (t *tester) extend(layers int) {
+ for i := 0; i < layers; i++ {
+ var parent = types.EmptyRootHash
+ if len(t.roots) != 0 {
+ parent = t.roots[len(t.roots)-1]
+ }
+ root, nodes, states := t.generate(parent, true)
+ if err := t.db.Update(root, parent, uint64(i), nodes, states); err != nil {
+ panic(fmt.Errorf("failed to update state changes, err: %w", err))
+ }
+ t.roots = append(t.roots, root)
+ }
+}
+
func (t *tester) release() {
t.db.Close()
t.db.diskdb.Close()
@@ -450,7 +466,7 @@ func TestDatabaseRollback(t *testing.T) {
}()
// Verify state histories
- tester := newTester(t, 0, false, 32)
+ tester := newTester(t, 0, false, 32, false)
defer tester.release()
if err := tester.verifyHistory(); err != nil {
@@ -484,7 +500,7 @@ func TestDatabaseRecoverable(t *testing.T) {
}()
var (
- tester = newTester(t, 0, false, 12)
+ tester = newTester(t, 0, false, 12, false)
index = tester.bottomIndex()
)
defer tester.release()
@@ -528,7 +544,7 @@ func TestDisable(t *testing.T) {
maxDiffLayers = 128
}()
- tester := newTester(t, 0, false, 32)
+ tester := newTester(t, 0, false, 32, false)
defer tester.release()
stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
@@ -570,7 +586,7 @@ func TestCommit(t *testing.T) {
maxDiffLayers = 128
}()
- tester := newTester(t, 0, false, 12)
+ tester := newTester(t, 0, false, 12, false)
defer tester.release()
if err := tester.db.Commit(tester.lastHash(), false); err != nil {
@@ -600,7 +616,7 @@ func TestJournal(t *testing.T) {
maxDiffLayers = 128
}()
- tester := newTester(t, 0, false, 12)
+ tester := newTester(t, 0, false, 12, false)
defer tester.release()
if err := tester.db.Journal(tester.lastHash()); err != nil {
@@ -630,7 +646,7 @@ func TestCorruptedJournal(t *testing.T) {
maxDiffLayers = 128
}()
- tester := newTester(t, 0, false, 12)
+ tester := newTester(t, 0, false, 12, false)
defer tester.release()
if err := tester.db.Journal(tester.lastHash()); err != nil {
@@ -678,7 +694,7 @@ func TestTailTruncateHistory(t *testing.T) {
maxDiffLayers = 128
}()
- tester := newTester(t, 10, false, 12)
+ tester := newTester(t, 10, false, 12, false)
defer tester.release()
tester.db.Close()
diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go
index 1c9efb024b..06f0a7285f 100644
--- a/triedb/pathdb/disklayer.go
+++ b/triedb/pathdb/disklayer.go
@@ -41,17 +41,20 @@ type diskLayer struct {
nodes *fastcache.Cache // GC friendly memory cache of clean nodes
states *fastcache.Cache // GC friendly memory cache of clean states
- buffer *buffer // Dirty buffer to aggregate writes of nodes and states
- stale bool // Signals that the layer became stale (state progressed)
- lock sync.RWMutex // Lock used to protect stale flag and genMarker
+ buffer *buffer // Live buffer to aggregate writes
+ frozen *buffer // Frozen node buffer waiting for flushing
+
+ stale bool // Signals that the layer became stale (state progressed)
+ lock sync.RWMutex // Lock used to protect stale flag and genMarker
// The generator is set if the state snapshot was not fully completed,
// regardless of whether the background generation is running or not.
+ // It should only be unset if the generation completes.
generator *generator
}
// newDiskLayer creates a new disk layer based on the passing arguments.
-func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer) *diskLayer {
+func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer, frozen *buffer) *diskLayer {
// Initialize the clean caches if the memory allowance is not zero
// or reuse the provided caches if they are not nil (inherited from
// the original disk layer).
@@ -68,6 +71,7 @@ func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Ca
nodes: nodes,
states: states,
buffer: buffer,
+ frozen: frozen,
}
}
@@ -114,16 +118,19 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
if dl.stale {
return nil, common.Hash{}, nil, errSnapshotStale
}
- // Try to retrieve the trie node from the not-yet-written
- // node buffer first. Note the buffer is lock free since
- // it's impossible to mutate the buffer before tagging the
- // layer as stale.
- n, found := dl.buffer.node(owner, path)
- if found {
- dirtyNodeHitMeter.Mark(1)
- dirtyNodeReadMeter.Mark(int64(len(n.Blob)))
- dirtyNodeHitDepthHist.Update(int64(depth))
- return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil
+ // Try to retrieve the trie node from the not-yet-written node buffer first
+ // (both the live one and the frozen one). Note the buffer is lock free since
+ // it's impossible to mutate the buffer before tagging the layer as stale.
+ for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
+ if buffer != nil {
+ n, found := buffer.node(owner, path)
+ if found {
+ dirtyNodeHitMeter.Mark(1)
+ dirtyNodeReadMeter.Mark(int64(len(n.Blob)))
+ dirtyNodeHitDepthHist.Update(int64(depth))
+ return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil
+ }
+ }
}
dirtyNodeMissMeter.Mark(1)
@@ -144,6 +151,11 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
} else {
blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
}
+ // Store the resolved data in the clean cache. The background buffer flusher
+ // may also write to the clean cache concurrently, but two writers cannot
+ // write the same item with different content. If the item already exists,
+ // it will be found in the frozen buffer, eliminating the need to check the
+ // database.
if dl.nodes != nil && len(blob) > 0 {
dl.nodes.Set(key, blob)
cleanNodeWriteMeter.Mark(int64(len(blob)))
@@ -162,22 +174,25 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
if dl.stale {
return nil, errSnapshotStale
}
- // Try to retrieve the account from the not-yet-written
- // node buffer first. Note the buffer is lock free since
- // it's impossible to mutate the buffer before tagging the
- // layer as stale.
- blob, found := dl.buffer.account(hash)
- if found {
- dirtyStateHitMeter.Mark(1)
- dirtyStateReadMeter.Mark(int64(len(blob)))
- dirtyStateHitDepthHist.Update(int64(depth))
+ // Try to retrieve the trie node from the not-yet-written node buffer first
+ // (both the live one and the frozen one). Note the buffer is lock free since
+ // it's impossible to mutate the buffer before tagging the layer as stale.
+ for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
+ if buffer != nil {
+ blob, found := buffer.account(hash)
+ if found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+ dirtyStateHitDepthHist.Update(int64(depth))
- if len(blob) == 0 {
- stateAccountInexMeter.Mark(1)
- } else {
- stateAccountExistMeter.Mark(1)
+ if len(blob) == 0 {
+ stateAccountInexMeter.Mark(1)
+ } else {
+ stateAccountExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
}
- return blob, nil
}
dirtyStateMissMeter.Mark(1)
@@ -203,7 +218,13 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
cleanStateMissMeter.Mark(1)
}
// Try to retrieve the account from the disk.
- blob = rawdb.ReadAccountSnapshot(dl.db.diskdb, hash)
+ blob := rawdb.ReadAccountSnapshot(dl.db.diskdb, hash)
+
+ // Store the resolved data in the clean cache. The background buffer flusher
+ // may also write to the clean cache concurrently, but two writers cannot
+ // write the same item with different content. If the item already exists,
+ // it will be found in the frozen buffer, eliminating the need to check the
+ // database.
if dl.states != nil {
dl.states.Set(hash[:], blob)
cleanStateWriteMeter.Mark(int64(len(blob)))
@@ -231,21 +252,24 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
if dl.stale {
return nil, errSnapshotStale
}
- // Try to retrieve the storage slot from the not-yet-written
- // node buffer first. Note the buffer is lock free since
- // it's impossible to mutate the buffer before tagging the
- // layer as stale.
- if blob, found := dl.buffer.storage(accountHash, storageHash); found {
- dirtyStateHitMeter.Mark(1)
- dirtyStateReadMeter.Mark(int64(len(blob)))
- dirtyStateHitDepthHist.Update(int64(depth))
+ // Try to retrieve the trie node from the not-yet-written node buffer first
+ // (both the live one and the frozen one). Note the buffer is lock free since
+ // it's impossible to mutate the buffer before tagging the layer as stale.
+ for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
+ if buffer != nil {
+ if blob, found := buffer.storage(accountHash, storageHash); found {
+ dirtyStateHitMeter.Mark(1)
+ dirtyStateReadMeter.Mark(int64(len(blob)))
+ dirtyStateHitDepthHist.Update(int64(depth))
- if len(blob) == 0 {
- stateStorageInexMeter.Mark(1)
- } else {
- stateStorageExistMeter.Mark(1)
+ if len(blob) == 0 {
+ stateStorageInexMeter.Mark(1)
+ } else {
+ stateStorageExistMeter.Mark(1)
+ }
+ return blob, nil
+ }
}
- return blob, nil
}
dirtyStateMissMeter.Mark(1)
@@ -273,6 +297,12 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
}
// Try to retrieve the account from the disk
blob := rawdb.ReadStorageSnapshot(dl.db.diskdb, accountHash, storageHash)
+
+ // Store the resolved data in the clean cache. The background buffer flusher
+ // may also write to the clean cache concurrently, but two writers cannot
+ // write the same item with different content. If the item already exists,
+ // it will be found in the frozen buffer, eliminating the need to check the
+ // database.
if dl.states != nil {
dl.states.Set(key, blob)
cleanStateWriteMeter.Mark(int64(len(blob)))
@@ -325,6 +355,12 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
overflow = true
oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
}
+ // Notify the state history indexer for newly created history
+ if dl.db.indexer != nil {
+ if err := dl.db.indexer.extend(bottom.stateID()); err != nil {
+ return nil, err
+ }
+ }
}
// Mark the diskLayer as stale before applying any mutations on top.
dl.stale = true
@@ -341,7 +377,8 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// truncation) surpasses the persisted state ID, we take the necessary action
// of forcibly committing the cached dirty states to ensure that the persisted
// state ID remains higher.
- if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest {
+ persistedID := rawdb.ReadPersistentStateID(dl.db.diskdb)
+ if !force && persistedID < oldest {
force = true
}
// Merge the trie nodes and flat states of the bottom-most diff layer into the
@@ -351,12 +388,25 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// Terminate the background state snapshot generation before mutating the
// persistent state.
if combined.full() || force {
+ // Wait until the previous frozen buffer is fully flushed
+ if dl.frozen != nil {
+ if err := dl.frozen.waitFlush(); err != nil {
+ return nil, err
+ }
+ }
+ // Release the frozen buffer and the internally referenced maps will
+ // be reclaimed by GC.
+ dl.frozen = nil
+
// Terminate the background state snapshot generator before flushing
// to prevent data race.
- var progress []byte
- if dl.generator != nil {
- dl.generator.stop()
- progress = dl.generator.progressMarker()
+ var (
+ progress []byte
+ gen = dl.generator
+ )
+ if gen != nil {
+ gen.stop()
+ progress = gen.progressMarker()
// If the snapshot has been fully generated, unset the generator
if progress == nil {
@@ -365,18 +415,33 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
log.Info("Paused snapshot generation")
}
}
- // Flush the content in combined buffer. Any state data after the progress
- // marker will be ignored, as the generator will pick it up later.
- if err := combined.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID()); err != nil {
- return nil, err
- }
- // Resume the background generation if it's not completed yet
- if progress != nil {
- dl.generator.run(bottom.root)
+
+ // Freeze the live buffer and schedule background flushing
+ dl.frozen = combined
+ dl.frozen.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID(), func() {
+ // Resume the background generation if it's not completed yet.
+ // The generator is assumed to be available if the progress is
+ // not nil.
+ //
+ // Notably, the generator will be shared and linked by all the
+ // disk layer instances, regardless of the generation is terminated
+ // or not.
+ if progress != nil {
+ gen.run(bottom.root)
+ }
+ })
+ // Block until the frozen buffer is fully flushed out if the async flushing
+ // is not allowed, or if the oldest history surpasses the persisted state ID.
+ if dl.db.config.NoAsyncFlush || persistedID < oldest {
+ if err := dl.frozen.waitFlush(); err != nil {
+ return nil, err
+ }
+ dl.frozen = nil
}
+ combined = newBuffer(dl.db.config.WriteBufferSize, nil, nil, 0)
}
// Link the generator if snapshot is not yet completed
- ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined)
+ ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined, dl.frozen)
if dl.generator != nil {
ndl.setGenerator(dl.generator)
}
@@ -418,6 +483,12 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
dl.stale = true
+ // Unindex the corresponding state history
+ if dl.db.indexer != nil {
+ if err := dl.db.indexer.shorten(dl.id); err != nil {
+ return nil, err
+ }
+ }
// State change may be applied to node buffer, or the persistent
// state, depends on if node buffer is empty or not. If the node
// buffer is not empty, it means that the state transition that
@@ -428,7 +499,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
if err != nil {
return nil, err
}
- ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
+ ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen)
// Link the generator if it exists
if dl.generator != nil {
@@ -437,7 +508,19 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
log.Debug("Reverted data in write buffer", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start)))
return ndl, nil
}
- // Terminate the generation before writing any data into database
+ // Block until the frozen buffer is fully flushed
+ if dl.frozen != nil {
+ if err := dl.frozen.waitFlush(); err != nil {
+ return nil, err
+ }
+ // Unset the frozen buffer if it exists, otherwise these "reverted"
+ // states will still be accessible after revert in frozen buffer.
+ dl.frozen = nil
+ }
+
+ // Terminate the generator before writing any data to the database.
+ // This must be done after flushing the frozen buffer, as the generator
+ // may be restarted at the end of the flush process.
var progress []byte
if dl.generator != nil {
dl.generator.stop()
@@ -455,7 +538,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
}
// Link the generator and resume generation if the snapshot is not yet
// fully completed.
- ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer)
+ ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen)
if dl.generator != nil && !dl.generator.completed() {
ndl.generator = dl.generator
ndl.generator.run(h.meta.parent)
@@ -500,3 +583,41 @@ func (dl *diskLayer) genMarker() []byte {
}
return dl.generator.progressMarker()
}
+
+// genComplete returns a flag indicating whether the state snapshot has been
+// fully generated.
+func (dl *diskLayer) genComplete() bool {
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ return dl.genMarker() == nil
+}
+
+// waitFlush blocks until the background buffer flush is completed.
+func (dl *diskLayer) waitFlush() error {
+ dl.lock.RLock()
+ defer dl.lock.RUnlock()
+
+ if dl.frozen == nil {
+ return nil
+ }
+ return dl.frozen.waitFlush()
+}
+
+// terminate releases the frozen buffer if it's not nil and terminates the
+// background state generator.
+func (dl *diskLayer) terminate() error {
+ dl.lock.Lock()
+ defer dl.lock.Unlock()
+
+ if dl.frozen != nil {
+ if err := dl.frozen.waitFlush(); err != nil {
+ return err
+ }
+ dl.frozen = nil
+ }
+ if dl.generator != nil {
+ dl.generator.stop()
+ }
+ return nil
+}
diff --git a/triedb/pathdb/generate.go b/triedb/pathdb/generate.go
index f4f98c9d19..2efbbbb4e1 100644
--- a/triedb/pathdb/generate.go
+++ b/triedb/pathdb/generate.go
@@ -186,7 +186,7 @@ func generateSnapshot(triedb *Database, root common.Hash, noBuild bool) *diskLay
stats = &generatorStats{start: time.Now()}
genMarker = []byte{} // Initialized but empty!
)
- dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0))
+ dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0), nil)
dl.setGenerator(newGenerator(triedb.diskdb, noBuild, genMarker, stats))
if !noBuild {
diff --git a/triedb/pathdb/generate_test.go b/triedb/pathdb/generate_test.go
index 23efb0e3c5..f38a1ed7c4 100644
--- a/triedb/pathdb/generate_test.go
+++ b/triedb/pathdb/generate_test.go
@@ -49,6 +49,7 @@ func newGenTester() *genTester {
disk := rawdb.NewMemoryDatabase()
config := *Defaults
config.SnapshotNoBuild = true // no background generation
+ config.NoAsyncFlush = true // no async flush
db := New(disk, &config, false)
tr, _ := trie.New(trie.StateTrieID(types.EmptyRootHash), db)
return &genTester{
diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go
index 63c9152bf7..47f224170d 100644
--- a/triedb/pathdb/history.go
+++ b/triedb/pathdb/history.go
@@ -505,25 +505,41 @@ func (h *history) decode(accountData, storageData, accountIndexes, storageIndexe
// readHistory reads and decodes the state history object by the given id.
func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) {
- blob := rawdb.ReadStateHistoryMeta(reader, id)
- if len(blob) == 0 {
- return nil, fmt.Errorf("state history not found %d", id)
+ mData, accountIndexes, storageIndexes, accountData, storageData, err := rawdb.ReadStateHistory(reader, id)
+ if err != nil {
+ return nil, err
}
var m meta
- if err := m.decode(blob); err != nil {
+ if err := m.decode(mData); err != nil {
return nil, err
}
- var (
- dec = history{meta: &m}
- accountData = rawdb.ReadStateAccountHistory(reader, id)
- storageData = rawdb.ReadStateStorageHistory(reader, id)
- accountIndexes = rawdb.ReadStateAccountIndex(reader, id)
- storageIndexes = rawdb.ReadStateStorageIndex(reader, id)
- )
- if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
+ h := history{meta: &m}
+ if err := h.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
return nil, err
}
- return &dec, nil
+ return &h, nil
+}
+
+// readHistories reads and decodes a list of state histories with the specific
+// history range.
+func readHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]*history, error) {
+ var histories []*history
+ metaList, aIndexList, sIndexList, aDataList, sDataList, err := rawdb.ReadStateHistoryList(freezer, start, count)
+ if err != nil {
+ return nil, err
+ }
+ for i := 0; i < len(metaList); i++ {
+ var m meta
+ if err := m.decode(metaList[i]); err != nil {
+ return nil, err
+ }
+ h := history{meta: &m}
+ if err := h.decode(aDataList[i], sDataList[i], aIndexList[i], sIndexList[i]); err != nil {
+ return nil, err
+ }
+ histories = append(histories, &h)
+ }
+ return histories, nil
}
// writeHistory persists the state history with the provided state set.
diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go
new file mode 100644
index 0000000000..f79581b38b
--- /dev/null
+++ b/triedb/pathdb/history_index.go
@@ -0,0 +1,436 @@
+// Copyright 2025 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 = indexBlockEntriesCap
+}
+
+// encode packs index block descriptor into byte stream.
+func (d *indexBlockDesc) encode() []byte {
+ var buf [indexBlockDescSize]byte
+ binary.BigEndian.PutUint64(buf[0:8], d.max)
+ binary.BigEndian.PutUint16(buf[8:10], d.entries)
+ binary.BigEndian.PutUint32(buf[10:14], d.id)
+ return buf[:]
+}
+
+// decode unpacks index block descriptor from byte stream.
+func (d *indexBlockDesc) decode(blob []byte) {
+ d.max = binary.BigEndian.Uint64(blob[:8])
+ d.entries = binary.BigEndian.Uint16(blob[8:10])
+ d.id = binary.BigEndian.Uint32(blob[10:14])
+}
+
+// parseIndexBlock parses the index block with the supplied byte stream.
+// The index block format can be illustrated as below:
+//
+// +---->+------------------+
+// | | Chunk1 |
+// | +------------------+
+// | | ...... |
+// | +-->+------------------+
+// | | | ChunkN |
+// | | +------------------+
+// +-|---| Restart1 |
+// | | Restart... | 2N bytes
+// +---| RestartN |
+// +------------------+
+// | Restart count | 1 byte
+// +------------------+
+//
+// - Chunk list: A list of data chunks
+// - Restart list: A list of 2-byte pointers, each pointing to the start position of a chunk
+// - Restart count: The number of restarts in the block, stored at the end of the block (1 byte)
+//
+// Note: the pointer is encoded as a uint16, which is sufficient within a chunk.
+// A uint16 can cover offsets in the range [0, 65536), which is more than enough
+// to store 4096 integers.
+//
+// Each chunk begins with the full value of the first integer, followed by
+// subsequent integers representing the differences between the current value
+// and the preceding one. Integers are encoded with variable-size for best
+// storage efficiency. Each chunk can be illustrated as below.
+//
+// Restart ---> +----------------+
+// | Full integer |
+// +----------------+
+// | Diff with prev |
+// +----------------+
+// | ... |
+// +----------------+
+// | Diff with prev |
+// +----------------+
+//
+// Empty index block is regarded as invalid.
+func parseIndexBlock(blob []byte) ([]uint16, []byte, error) {
+ if len(blob) < 1 {
+ return nil, nil, fmt.Errorf("corrupted index block, len: %d", len(blob))
+ }
+ restartLen := blob[len(blob)-1]
+ if restartLen == 0 {
+ return nil, nil, errors.New("corrupted index block, no restart")
+ }
+ tailLen := int(restartLen)*2 + 1
+ if len(blob) < tailLen {
+ return nil, nil, fmt.Errorf("truncated restarts, size: %d, restarts: %d", len(blob), restartLen)
+ }
+ restarts := make([]uint16, 0, restartLen)
+ for i := int(restartLen); i > 0; i-- {
+ restart := binary.BigEndian.Uint16(blob[len(blob)-1-2*i:])
+ restarts = append(restarts, restart)
+ }
+ // Validate that restart points are strictly ordered and within the valid
+ // data range.
+ var prev uint16
+ for i := 0; i < len(restarts); i++ {
+ if i != 0 {
+ if restarts[i] <= prev {
+ return nil, nil, fmt.Errorf("restart out of order, prev: %d, next: %d", prev, restarts[i])
+ }
+ }
+ if int(restarts[i]) >= len(blob)-tailLen {
+ return nil, nil, fmt.Errorf("invalid restart position, restart: %d, size: %d", restarts[i], len(blob)-tailLen)
+ }
+ prev = restarts[i]
+ }
+ return restarts, blob[:len(blob)-tailLen], nil
+}
+
+// blockReader is the reader to access the element within a block.
+type blockReader struct {
+ restarts []uint16
+ data []byte
+}
+
+// newBlockReader constructs the block reader with the supplied block data.
+func newBlockReader(blob []byte) (*blockReader, error) {
+ restarts, data, err := parseIndexBlock(blob)
+ if err != nil {
+ return nil, err
+ }
+ return &blockReader{
+ restarts: restarts,
+ data: data, // safe to own the slice
+ }, nil
+}
+
+// readGreaterThan locates the first element in the block that is greater than
+// the specified value. If no such element is found, MaxUint64 is returned.
+func (br *blockReader) readGreaterThan(id uint64) (uint64, error) {
+ var err error
+ index := sort.Search(len(br.restarts), func(i int) bool {
+ item, n := binary.Uvarint(br.data[br.restarts[i]:])
+ if n <= 0 {
+ err = fmt.Errorf("failed to decode item at restart %d", br.restarts[i])
+ }
+ return item > id
+ })
+ if err != nil {
+ return 0, err
+ }
+ if index == 0 {
+ item, _ := binary.Uvarint(br.data[br.restarts[0]:])
+ return item, nil
+ }
+ var (
+ start int
+ limit int
+ result uint64
+ )
+ if index == len(br.restarts) {
+ // The element being searched falls within the last restart section,
+ // there is no guarantee such element can be found.
+ start = int(br.restarts[len(br.restarts)-1])
+ limit = len(br.data)
+ } else {
+ // The element being searched falls within the non-last restart section,
+ // such element can be found for sure.
+ start = int(br.restarts[index-1])
+ limit = int(br.restarts[index])
+ }
+ pos := start
+ for pos < limit {
+ x, n := binary.Uvarint(br.data[pos:])
+ if pos == start {
+ result = x
+ } else {
+ result += x
+ }
+ if result > id {
+ return result, nil
+ }
+ pos += n
+ }
+ // The element which is greater than specified id is not found.
+ if index == len(br.restarts) {
+ return math.MaxUint64, nil
+ }
+ // The element which is the first one greater than the specified id
+ // is exactly the one located at the restart point.
+ item, _ := binary.Uvarint(br.data[br.restarts[index]:])
+ return item, nil
+}
+
+type blockWriter struct {
+ desc *indexBlockDesc // Descriptor of the block
+ restarts []uint16 // Offsets into the data slice, marking the start of each section
+ scratch []byte // Buffer used for encoding full integers or value differences
+ data []byte // Aggregated encoded data slice
+}
+
+func newBlockWriter(blob []byte, desc *indexBlockDesc) (*blockWriter, error) {
+ scratch := make([]byte, binary.MaxVarintLen64)
+ if len(blob) == 0 {
+ return &blockWriter{
+ desc: desc,
+ scratch: scratch,
+ data: make([]byte, 0, 1024),
+ }, nil
+ }
+ restarts, data, err := parseIndexBlock(blob)
+ if err != nil {
+ return nil, err
+ }
+ return &blockWriter{
+ desc: desc,
+ restarts: restarts,
+ scratch: scratch,
+ data: data, // safe to own the slice
+ }, nil
+}
+
+// append adds a new element to the block. The new element must be greater than
+// the previous one. The provided ID is assumed to always be greater than 0.
+func (b *blockWriter) append(id uint64) error {
+ if id == 0 {
+ return errors.New("invalid zero id")
+ }
+ if id <= b.desc.max {
+ return fmt.Errorf("append element out of order, last: %d, this: %d", b.desc.max, id)
+ }
+ // Rotate the current restart section if it's full
+ if b.desc.entries%indexBlockRestartLen == 0 {
+ // Save the offset within the data slice as the restart point
+ // for the next section.
+ b.restarts = append(b.restarts, uint16(len(b.data)))
+
+ // The restart point item can either be encoded in variable
+ // size or fixed size. Although variable-size encoding is
+ // slightly slower (2ns per operation), it is still relatively
+ // fast, therefore, it's picked for better space efficiency.
+ //
+ // The first element in a restart range is encoded using its
+ // full value.
+ n := binary.PutUvarint(b.scratch[0:], id)
+ b.data = append(b.data, b.scratch[:n]...)
+ } else {
+ // The current section is not full, append the element.
+ // The element which is not the first one in the section
+ // is encoded using the value difference from the preceding
+ // element.
+ n := binary.PutUvarint(b.scratch[0:], id-b.desc.max)
+ b.data = append(b.data, b.scratch[:n]...)
+ }
+ b.desc.entries++
+
+ // The state history ID must be greater than 0.
+ //if b.desc.min == 0 {
+ // b.desc.min = id
+ //}
+ b.desc.max = id
+ return nil
+}
+
+// scanSection traverses the specified section and terminates if fn returns true.
+func (b *blockWriter) scanSection(section int, fn func(uint64, int) bool) {
+ var (
+ value uint64
+ start = int(b.restarts[section])
+ pos = start
+ limit int
+ )
+ if section == len(b.restarts)-1 {
+ limit = len(b.data)
+ } else {
+ limit = int(b.restarts[section+1])
+ }
+ for pos < limit {
+ x, n := binary.Uvarint(b.data[pos:])
+ if pos == start {
+ value = x
+ } else {
+ value += x
+ }
+ if fn(value, pos) {
+ return
+ }
+ pos += n
+ }
+}
+
+// sectionLast returns the last element in the specified section.
+func (b *blockWriter) sectionLast(section int) uint64 {
+ var n uint64
+ b.scanSection(section, func(v uint64, _ int) bool {
+ n = v
+ return false
+ })
+ return n
+}
+
+// sectionSearch looks up the specified value in the given section,
+// the position and the preceding value will be returned if found.
+func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int) {
+ b.scanSection(section, func(v uint64, p int) bool {
+ if n == v {
+ pos = p
+ found = true
+ return true // terminate iteration
+ }
+ prev = v
+ return false // continue iteration
+ })
+ return found, prev, pos
+}
+
+// pop removes the last element from the block. The assumption is held that block
+// writer must be non-empty.
+func (b *blockWriter) pop(id uint64) error {
+ if id == 0 {
+ return errors.New("invalid zero id")
+ }
+ if id != b.desc.max {
+ return fmt.Errorf("pop element out of order, last: %d, this: %d", b.desc.max, id)
+ }
+ // If there is only one entry left, the entire block should be reset
+ if b.desc.entries == 1 {
+ //b.desc.min = 0
+ b.desc.max = 0
+ b.desc.entries = 0
+ b.restarts = nil
+ b.data = b.data[:0]
+ return nil
+ }
+ // Pop the last restart section if the section becomes empty after removing
+ // one element.
+ if b.desc.entries%indexBlockRestartLen == 1 {
+ b.data = b.data[:b.restarts[len(b.restarts)-1]]
+ b.restarts = b.restarts[:len(b.restarts)-1]
+ b.desc.max = b.sectionLast(len(b.restarts) - 1)
+ b.desc.entries -= 1
+ return nil
+ }
+ // Look up the element preceding the one to be popped, in order to update
+ // the maximum element in the block.
+ found, prev, pos := b.sectionSearch(len(b.restarts)-1, id)
+ if !found {
+ return fmt.Errorf("pop element is not found, last: %d, this: %d", b.desc.max, id)
+ }
+ b.desc.max = prev
+ b.data = b.data[:pos]
+ b.desc.entries -= 1
+ return nil
+}
+
+func (b *blockWriter) empty() bool {
+ return b.desc.empty()
+}
+
+func (b *blockWriter) full() bool {
+ return b.desc.full()
+}
+
+// finish finalizes the index block encoding by appending the encoded restart points
+// and the restart counter to the end of the block.
+//
+// This function is safe to be called multiple times.
+func (b *blockWriter) finish() []byte {
+ var buf []byte
+ for _, number := range b.restarts {
+ binary.BigEndian.PutUint16(b.scratch[:2], number)
+ buf = append(buf, b.scratch[:2]...)
+ }
+ buf = append(buf, byte(len(b.restarts)))
+ return append(b.data, buf...)
+}
diff --git a/triedb/pathdb/history_index_block_test.go b/triedb/pathdb/history_index_block_test.go
new file mode 100644
index 0000000000..173387b447
--- /dev/null
+++ b/triedb/pathdb/history_index_block_test.go
@@ -0,0 +1,216 @@
+// Copyright 2025 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 value
+ })
+ got, err := br.readGreaterThan(value)
+ if err != nil {
+ t.Fatalf("Unexpected error, got %v", err)
+ }
+ if pos == len(elements) {
+ if got != math.MaxUint64 {
+ t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", got)
+ }
+ } else if got != elements[pos] {
+ t.Fatalf("Unexpected result, got %d, wanted %d", got, elements[pos])
+ }
+ }
+}
+
+func TestBlockWriterBasic(t *testing.T) {
+ bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
+ if !bw.empty() {
+ t.Fatal("expected empty block")
+ }
+ bw.append(2)
+ if err := bw.append(1); err == nil {
+ t.Fatal("out-of-order insertion is not expected")
+ }
+ for i := 0; i < 10; i++ {
+ bw.append(uint64(i + 3))
+ }
+
+ bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0))
+ if err != nil {
+ t.Fatalf("Failed to construct the block writer, %v", err)
+ }
+ for i := 0; i < 10; i++ {
+ if err := bw.append(uint64(i + 100)); err != nil {
+ t.Fatalf("Failed to append value %d: %v", i, err)
+ }
+ }
+ bw.finish()
+}
+
+func TestBlockWriterDelete(t *testing.T) {
+ bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
+ for i := 0; i < 10; i++ {
+ bw.append(uint64(i + 1))
+ }
+ // Pop unknown id, the request should be rejected
+ if err := bw.pop(100); err == nil {
+ t.Fatal("Expect error to occur for unknown id")
+ }
+ for i := 10; i >= 1; i-- {
+ if err := bw.pop(uint64(i)); err != nil {
+ t.Fatalf("Unexpected error for element popping, %v", err)
+ }
+ empty := i == 1
+ if empty != bw.empty() {
+ t.Fatalf("Emptiness is not matched, want: %T, got: %T", empty, bw.empty())
+ }
+ newMax := uint64(i - 1)
+ if bw.desc.max != newMax {
+ t.Fatalf("Maxmium element is not matched, want: %d, got: %d", newMax, bw.desc.max)
+ }
+ }
+}
+
+func TestBlcokWriterDeleteWithData(t *testing.T) {
+ elements := []uint64{
+ 1, 5, 10, 11, 20,
+ }
+ bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
+ for i := 0; i < len(elements); i++ {
+ bw.append(elements[i])
+ }
+
+ // Re-construct the block writer with data
+ desc := &indexBlockDesc{
+ id: 0,
+ max: 20,
+ entries: 5,
+ }
+ bw, err := newBlockWriter(bw.finish(), desc)
+ if err != nil {
+ t.Fatalf("Failed to construct block writer %v", err)
+ }
+ for i := len(elements) - 1; i > 0; i-- {
+ if err := bw.pop(elements[i]); err != nil {
+ t.Fatalf("Failed to pop element, %v", err)
+ }
+ newTail := elements[i-1]
+
+ // Ensure the element can still be queried with no issue
+ br, err := newBlockReader(bw.finish())
+ if err != nil {
+ t.Fatalf("Failed to construct the block reader, %v", err)
+ }
+ cases := []struct {
+ value uint64
+ result uint64
+ }{
+ {0, 1},
+ {1, 5},
+ {10, 11},
+ {19, 20},
+ {20, math.MaxUint64},
+ {21, math.MaxUint64},
+ }
+ for _, c := range cases {
+ want := c.result
+ if c.value >= newTail {
+ want = math.MaxUint64
+ }
+ got, err := br.readGreaterThan(c.value)
+ if err != nil {
+ t.Fatalf("Unexpected error, got %v", err)
+ }
+ if got != want {
+ t.Fatalf("Unexpected result, got %v, wanted %v", got, want)
+ }
+ }
+ }
+}
+
+func TestCorruptedIndexBlock(t *testing.T) {
+ bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
+ for i := 0; i < 10; i++ {
+ bw.append(uint64(i + 1))
+ }
+ buf := bw.finish()
+
+ // Mutate the buffer manually
+ buf[len(buf)-1]++
+ _, err := newBlockWriter(buf, newIndexBlockDesc(0))
+ if err == nil {
+ t.Fatal("Corrupted index block data is not detected")
+ }
+}
diff --git a/triedb/pathdb/history_index_test.go b/triedb/pathdb/history_index_test.go
new file mode 100644
index 0000000000..7b24b86fd6
--- /dev/null
+++ b/triedb/pathdb/history_index_test.go
@@ -0,0 +1,292 @@
+// Copyright 2025 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 value
+ })
+ got, err := br.readGreaterThan(value)
+ if err != nil {
+ t.Fatalf("Unexpected error, got %v", err)
+ }
+ if pos == len(elements) {
+ if got != math.MaxUint64 {
+ t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", got)
+ }
+ } else if got != elements[pos] {
+ t.Fatalf("Unexpected result, got %d, wanted %d", got, elements[pos])
+ }
+ }
+}
+
+func TestEmptyIndexReader(t *testing.T) {
+ br, err := newIndexReader(rawdb.NewMemoryDatabase(), newAccountIdent(common.Hash{0xa}))
+ if err != nil {
+ t.Fatalf("Failed to construct the index reader, %v", err)
+ }
+ res, err := br.readGreaterThan(100)
+ if err != nil {
+ t.Fatalf("Failed to query, %v", err)
+ }
+ if res != math.MaxUint64 {
+ t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", res)
+ }
+}
+
+func TestIndexWriterBasic(t *testing.T) {
+ db := rawdb.NewMemoryDatabase()
+ iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
+ iw.append(2)
+ if err := iw.append(1); err == nil {
+ t.Fatal("out-of-order insertion is not expected")
+ }
+ for i := 0; i < 10; i++ {
+ iw.append(uint64(i + 3))
+ }
+ batch := db.NewBatch()
+ iw.finish(batch)
+ batch.Write()
+
+ iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
+ if err != nil {
+ t.Fatalf("Failed to construct the block writer, %v", err)
+ }
+ for i := 0; i < 10; i++ {
+ if err := iw.append(uint64(i + 100)); err != nil {
+ t.Fatalf("Failed to append item, %v", err)
+ }
+ }
+ iw.finish(db.NewBatch())
+}
+
+func TestIndexWriterDelete(t *testing.T) {
+ db := rawdb.NewMemoryDatabase()
+ iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
+ for i := 0; i < indexBlockEntriesCap*4; i++ {
+ iw.append(uint64(i + 1))
+ }
+ batch := db.NewBatch()
+ iw.finish(batch)
+ batch.Write()
+
+ // Delete unknown id, the request should be rejected
+ id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}))
+ if err := id.pop(indexBlockEntriesCap * 5); err == nil {
+ t.Fatal("Expect error to occur for unknown id")
+ }
+ for i := indexBlockEntriesCap * 4; i >= 1; i-- {
+ if err := id.pop(uint64(i)); err != nil {
+ t.Fatalf("Unexpected error for element popping, %v", err)
+ }
+ if id.lastID != uint64(i-1) {
+ t.Fatalf("Unexpected lastID, want: %d, got: %d", uint64(i-1), iw.lastID)
+ }
+ if rand.Intn(10) == 0 {
+ batch := db.NewBatch()
+ id.finish(batch)
+ batch.Write()
+ }
+ }
+}
+
+func TestBatchIndexerWrite(t *testing.T) {
+ var (
+ db = rawdb.NewMemoryDatabase()
+ batch = newBatchIndexer(db, false)
+ histories = makeHistories(10)
+ )
+ for i, h := range histories {
+ if err := batch.process(h, uint64(i+1)); err != nil {
+ t.Fatalf("Failed to process history, %v", err)
+ }
+ }
+ if err := batch.finish(true); err != nil {
+ t.Fatalf("Failed to finish batch indexer, %v", err)
+ }
+ metadata := loadIndexMetadata(db)
+ if metadata == nil || metadata.Last != uint64(10) {
+ t.Fatal("Unexpected index position")
+ }
+ var (
+ accounts = make(map[common.Hash][]uint64)
+ storages = make(map[common.Hash]map[common.Hash][]uint64)
+ )
+ for i, h := range histories {
+ for _, addr := range h.accountList {
+ addrHash := crypto.Keccak256Hash(addr.Bytes())
+ accounts[addrHash] = append(accounts[addrHash], uint64(i+1))
+
+ if _, ok := storages[addrHash]; !ok {
+ storages[addrHash] = make(map[common.Hash][]uint64)
+ }
+ for _, slot := range h.storageList[addr] {
+ storages[addrHash][slot] = append(storages[addrHash][slot], uint64(i+1))
+ }
+ }
+ }
+ for addrHash, indexes := range accounts {
+ ir, _ := newIndexReader(db, newAccountIdent(addrHash))
+ for i := 0; i < len(indexes)-1; i++ {
+ n, err := ir.readGreaterThan(indexes[i])
+ if err != nil {
+ t.Fatalf("Failed to read index, %v", err)
+ }
+ if n != indexes[i+1] {
+ t.Fatalf("Unexpected result, want %d, got %d", indexes[i+1], n)
+ }
+ }
+ n, err := ir.readGreaterThan(indexes[len(indexes)-1])
+ if err != nil {
+ t.Fatalf("Failed to read index, %v", err)
+ }
+ if n != math.MaxUint64 {
+ t.Fatalf("Unexpected result, want math.MaxUint64, got %d", n)
+ }
+ }
+ for addrHash, slots := range storages {
+ for slotHash, indexes := range slots {
+ ir, _ := newIndexReader(db, newStorageIdent(addrHash, slotHash))
+ for i := 0; i < len(indexes)-1; i++ {
+ n, err := ir.readGreaterThan(indexes[i])
+ if err != nil {
+ t.Fatalf("Failed to read index, %v", err)
+ }
+ if n != indexes[i+1] {
+ t.Fatalf("Unexpected result, want %d, got %d", indexes[i+1], n)
+ }
+ }
+ n, err := ir.readGreaterThan(indexes[len(indexes)-1])
+ if err != nil {
+ t.Fatalf("Failed to read index, %v", err)
+ }
+ if n != math.MaxUint64 {
+ t.Fatalf("Unexpected result, want math.MaxUint64, got %d", n)
+ }
+ }
+ }
+}
+
+func TestBatchIndexerDelete(t *testing.T) {
+ var (
+ db = rawdb.NewMemoryDatabase()
+ bw = newBatchIndexer(db, false)
+ histories = makeHistories(10)
+ )
+ // Index histories
+ for i, h := range histories {
+ if err := bw.process(h, uint64(i+1)); err != nil {
+ t.Fatalf("Failed to process history, %v", err)
+ }
+ }
+ if err := bw.finish(true); err != nil {
+ t.Fatalf("Failed to finish batch indexer, %v", err)
+ }
+
+ // Unindex histories
+ bd := newBatchIndexer(db, true)
+ for i := len(histories) - 1; i >= 0; i-- {
+ if err := bd.process(histories[i], uint64(i+1)); err != nil {
+ t.Fatalf("Failed to process history, %v", err)
+ }
+ }
+ if err := bd.finish(true); err != nil {
+ t.Fatalf("Failed to finish batch indexer, %v", err)
+ }
+
+ metadata := loadIndexMetadata(db)
+ if metadata != nil {
+ t.Fatal("Unexpected index position")
+ }
+ it := db.NewIterator(rawdb.StateHistoryIndexPrefix, nil)
+ for it.Next() {
+ t.Fatal("Leftover history index data")
+ }
+ it.Release()
+}
diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go
new file mode 100644
index 0000000000..b09804ce9d
--- /dev/null
+++ b/triedb/pathdb/history_indexer.go
@@ -0,0 +1,613 @@
+// Copyright 2025 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 = tailID {
+ log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID)
+ return metadata.Last + 1, nil
+ }
+ // History has been shortened without indexing. Discard the gapped segment
+ // in the history and shift to the first available element.
+ //
+ // The missing indexes corresponding to the gapped histories won't be visible.
+ // It's fine to leave them unindexed.
+ log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID)
+ return tailID, nil
+}
+
+func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID uint64) {
+ defer close(done)
+
+ beginID, err := i.next()
+ if err != nil {
+ log.Error("Failed to find next state history for indexing", "err", err)
+ return
+ }
+ // All available state histories have been indexed, and the last indexed one
+ // exceeds the most recent available state history. This situation may occur
+ // when the state is reverted manually (chain.SetHead) or the deep reorg is
+ // encountered. In such cases, no indexing should be scheduled.
+ if beginID > lastID {
+ log.Debug("State history is fully indexed", "last", lastID)
+ return
+ }
+ log.Info("Start history indexing", "beginID", beginID, "lastID", lastID)
+
+ var (
+ current = beginID
+ start = time.Now()
+ logged = time.Now()
+ batch = newBatchIndexer(i.disk, false)
+ )
+ for current <= lastID {
+ count := lastID - current + 1
+ if count > historyReadBatch {
+ count = historyReadBatch
+ }
+ histories, err := readHistories(i.freezer, current, count)
+ if err != nil {
+ // The history read might fall if the history is truncated from
+ // head due to revert operation.
+ log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err)
+ return
+ }
+ for _, h := range histories {
+ if err := batch.process(h, current); err != nil {
+ log.Error("Failed to index history", "err", err)
+ return
+ }
+ current += 1
+
+ // Occasionally report the indexing progress
+ if time.Since(logged) > time.Second*8 {
+ logged = time.Now()
+
+ var (
+ left = lastID - current + 1
+ done = current - beginID
+ speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+ )
+ // Override the ETA if larger than the largest until now
+ eta := time.Duration(left/speed) * time.Millisecond
+ log.Info("Indexing state history", "processed", done, "left", left, "eta", common.PrettyDuration(eta))
+ }
+ }
+ // Check interruption signal and abort process if it's fired
+ if interrupt != nil {
+ if signal := interrupt.Load(); signal != 0 {
+ if err := batch.finish(true); err != nil {
+ log.Error("Failed to flush index", "err", err)
+ }
+ log.Info("State indexing interrupted")
+ return
+ }
+ }
+ }
+ if err := batch.finish(true); err != nil {
+ log.Error("Failed to flush index", "err", err)
+ }
+ log.Info("Indexed state history", "from", beginID, "to", lastID, "elapsed", common.PrettyDuration(time.Since(start)))
+}
+
+// historyIndexer manages the indexing and unindexing of state histories,
+// providing access to historical states.
+//
+// Upon initialization, historyIndexer starts a one-time background process
+// to complete the indexing of any remaining state histories. Once this
+// process is finished, all state histories are marked as fully indexed,
+// enabling handling of requests for historical states. Thereafter, any new
+// state histories must be indexed or unindexed synchronously, ensuring that
+// the history index is created or removed along with the corresponding
+// state history.
+type historyIndexer struct {
+ initer *indexIniter
+ disk ethdb.KeyValueStore
+ freezer ethdb.AncientStore
+}
+
+// checkVersion checks whether the index data in the database matches the version.
+func checkVersion(disk ethdb.KeyValueStore) {
+ blob := rawdb.ReadStateHistoryIndexMetadata(disk)
+ if len(blob) == 0 {
+ return
+ }
+ var m indexMetadata
+ err := rlp.DecodeBytes(blob, &m)
+ if err == nil && m.Version == stateIndexVersion {
+ return
+ }
+ // TODO(rjl493456442) would be better to group them into a batch.
+ rawdb.DeleteStateHistoryIndexMetadata(disk)
+ rawdb.DeleteStateHistoryIndex(disk)
+
+ version := "unknown"
+ if err == nil {
+ version = fmt.Sprintf("%d", m.Version)
+ }
+ log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion)
+}
+
+// newHistoryIndexer constructs the history indexer and launches the background
+// initer to complete the indexing of any remaining state histories.
+func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer {
+ checkVersion(disk)
+ return &historyIndexer{
+ initer: newIndexIniter(disk, freezer, lastHistoryID),
+ disk: disk,
+ freezer: freezer,
+ }
+}
+
+func (i *historyIndexer) close() {
+ i.initer.close()
+}
+
+// inited returns a flag indicating whether the existing state histories
+// have been fully indexed, in other words, whether they are available
+// for external access.
+func (i *historyIndexer) inited() bool {
+ return i.initer.inited()
+}
+
+// extend sends the notification that new state history with specified ID
+// has been written into the database and is ready for indexing.
+func (i *historyIndexer) extend(historyID uint64) error {
+ signal := &interruptSignal{
+ newLastID: historyID,
+ result: make(chan error, 1),
+ }
+ select {
+ case <-i.initer.closed:
+ return errors.New("indexer is closed")
+ case <-i.initer.done:
+ return indexSingle(historyID, i.disk, i.freezer)
+ case i.initer.interrupt <- signal:
+ return <-signal.result
+ }
+}
+
+// shorten sends the notification that state history with specified ID
+// is about to be deleted from the database and should be unindexed.
+func (i *historyIndexer) shorten(historyID uint64) error {
+ signal := &interruptSignal{
+ newLastID: historyID - 1,
+ result: make(chan error, 1),
+ }
+ select {
+ case <-i.initer.closed:
+ return errors.New("indexer is closed")
+ case <-i.initer.done:
+ return unindexSingle(historyID, i.disk, i.freezer)
+ case i.initer.interrupt <- signal:
+ return <-signal.result
+ }
+}
diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go
new file mode 100644
index 0000000000..9471ab423d
--- /dev/null
+++ b/triedb/pathdb/history_reader.go
@@ -0,0 +1,375 @@
+// Copyright 2025 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 lastID {
+ return 0, fmt.Errorf("index reader is stale, limit: %d, last-state-id: %d", r.limit, lastID)
+ }
+ // Try to find the element which is greater than the specified target
+ res, err := r.reader.readGreaterThan(id)
+ if err != nil {
+ return 0, err
+ }
+ // Short circuit if the element is found within the current index
+ if res != math.MaxUint64 {
+ return res, nil
+ }
+ // The element was not found, and no additional histories have been indexed.
+ // Return a not-found result.
+ if r.limit == lastID {
+ return res, nil
+ }
+ // Refresh the index reader and attempt again. If the latest indexed position
+ // is even below the ID of the disk layer, it indicates that state histories
+ // are being removed. In this case, it would theoretically be better to block
+ // the state rollback operation synchronously until all readers are released.
+ // Given that it's very unlikely to occur and users try to perform historical
+ // state queries while reverting the states at the same time. Simply returning
+ // an error should be sufficient for now.
+ metadata := loadIndexMetadata(r.db)
+ if metadata == nil || metadata.Last < lastID {
+ return 0, errors.New("state history hasn't been indexed yet")
+ }
+ if err := r.reader.refresh(); err != nil {
+ return 0, err
+ }
+ r.limit = metadata.Last
+
+ return r.reader.readGreaterThan(id)
+}
+
+// historyReader is the structure to access historic state data.
+type historyReader struct {
+ disk ethdb.KeyValueReader
+ freezer ethdb.AncientReader
+ readers map[string]*indexReaderWithLimitTag
+}
+
+// newHistoryReader constructs the history reader with the supplied db.
+func newHistoryReader(disk ethdb.KeyValueReader, freezer ethdb.AncientReader) *historyReader {
+ return &historyReader{
+ disk: disk,
+ freezer: freezer,
+ readers: make(map[string]*indexReaderWithLimitTag),
+ }
+}
+
+// readAccountMetadata resolves the account metadata within the specified
+// state history.
+func (r *historyReader) readAccountMetadata(address common.Address, historyID uint64) ([]byte, error) {
+ blob := rawdb.ReadStateAccountIndex(r.freezer, historyID)
+ if len(blob) == 0 {
+ return nil, fmt.Errorf("account index is truncated, historyID: %d", historyID)
+ }
+ if len(blob)%accountIndexSize != 0 {
+ return nil, fmt.Errorf("account index is corrupted, historyID: %d, size: %d", historyID, len(blob))
+ }
+ n := len(blob) / accountIndexSize
+
+ pos := sort.Search(n, func(i int) bool {
+ h := blob[accountIndexSize*i : accountIndexSize*i+common.HashLength]
+ return bytes.Compare(h, address.Bytes()) >= 0
+ })
+ if pos == n {
+ return nil, fmt.Errorf("account %#x is not found", address)
+ }
+ offset := accountIndexSize * pos
+ if address != common.BytesToAddress(blob[offset:offset+common.AddressLength]) {
+ return nil, fmt.Errorf("account %#x is not found", address)
+ }
+ return blob[offset : accountIndexSize*(pos+1)], nil
+}
+
+// readStorageMetadata resolves the storage slot metadata within the specified
+// state history.
+func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) {
+ // TODO(rj493456442) optimize it with partial read
+ blob := rawdb.ReadStateStorageIndex(r.freezer, historyID)
+ if len(blob) == 0 {
+ return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID)
+ }
+ if len(blob)%slotIndexSize != 0 {
+ return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob))
+ }
+ if slotIndexSize*(slotOffset+slotNumber) > len(blob) {
+ return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber)
+ }
+ subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)]
+
+ // TODO(rj493456442) get rid of the metadata resolution
+ var (
+ m meta
+ target common.Hash
+ )
+ blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID)
+ if err := m.decode(blob); err != nil {
+ return nil, err
+ }
+ if m.version == stateHistoryV0 {
+ target = storageHash
+ } else {
+ target = storageKey
+ }
+ pos := sort.Search(slotNumber, func(i int) bool {
+ slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength]
+ return bytes.Compare(slotID, target.Bytes()) >= 0
+ })
+ if pos == slotNumber {
+ return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
+ }
+ offset := slotIndexSize * pos
+ if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) {
+ return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID)
+ }
+ return subSlice[offset : slotIndexSize*(pos+1)], nil
+}
+
+// readAccount retrieves the account data from the specified state history.
+func (r *historyReader) readAccount(address common.Address, historyID uint64) ([]byte, error) {
+ metadata, err := r.readAccountMetadata(address, historyID)
+ if err != nil {
+ return nil, err
+ }
+ length := int(metadata[common.AddressLength]) // one byte for account data length
+ offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset
+
+ // TODO(rj493456442) optimize it with partial read
+ data := rawdb.ReadStateAccountHistory(r.freezer, historyID)
+ if len(data) < length+offset {
+ return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length)
+ }
+ return data[offset : offset+length], nil
+}
+
+// readStorage retrieves the storage slot data from the specified state history.
+func (r *historyReader) readStorage(address common.Address, storageKey common.Hash, storageHash common.Hash, historyID uint64) ([]byte, error) {
+ metadata, err := r.readAccountMetadata(address, historyID)
+ if err != nil {
+ return nil, err
+ }
+ // slotIndexOffset:
+ // The offset of storage indices associated with the specified account.
+ // slotIndexNumber:
+ // The number of storage indices associated with the specified account.
+ slotIndexOffset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+5 : common.AddressLength+9]))
+ slotIndexNumber := int(binary.BigEndian.Uint32(metadata[common.AddressLength+9 : common.AddressLength+13]))
+
+ slotMetadata, err := r.readStorageMetadata(storageKey, storageHash, historyID, slotIndexOffset, slotIndexNumber)
+ if err != nil {
+ return nil, err
+ }
+ length := int(slotMetadata[common.HashLength]) // one byte for slot data length
+ offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset
+
+ // TODO(rj493456442) optimize it with partial read
+ data := rawdb.ReadStateStorageHistory(r.freezer, historyID)
+ if len(data) < offset+length {
+ return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length)
+ }
+ return data[offset : offset+length], nil
+}
+
+// read retrieves the state element data associated with the stateID.
+// stateID: represents the ID of the state of the specified version;
+// lastID: represents the ID of the latest/newest state history;
+// latestValue: represents the state value at the current disk layer with ID == lastID;
+func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint64, latestValue []byte) ([]byte, error) {
+ tail, err := r.freezer.Tail()
+ if err != nil {
+ return nil, err
+ }
+ // stateID == tail is allowed, as the first history object preserved
+ // is tail+1
+ if stateID < tail {
+ return nil, errors.New("historical state has been pruned")
+ }
+
+ // To serve the request, all state histories from stateID+1 to lastID
+ // must be indexed. It's not supposed to happen unless system is very
+ // wrong.
+ metadata := loadIndexMetadata(r.disk)
+ if metadata == nil || metadata.Last < lastID {
+ indexed := "null"
+ if metadata != nil {
+ indexed = fmt.Sprintf("%d", metadata.Last)
+ }
+ return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed)
+ }
+
+ // Construct the index reader to locate the corresponding history for
+ // state retrieval
+ ir, ok := r.readers[state.String()]
+ if !ok {
+ ir, err = newIndexReaderWithLimitTag(r.disk, state.stateIdent)
+ if err != nil {
+ return nil, err
+ }
+ r.readers[state.String()] = ir
+ }
+ historyID, err := ir.readGreaterThan(stateID, lastID)
+ if err != nil {
+ return nil, err
+ }
+ // The state was not found in the state histories, as it has not been modified
+ // since stateID. Use the data from the associated disk layer instead.
+ if historyID == math.MaxUint64 {
+ return latestValue, nil
+ }
+ // Resolve data from the specified state history object. Notably, since the history
+ // reader operates completely asynchronously with the indexer/unindexer, it's possible
+ // that the associated state histories are no longer available due to a rollback.
+ // Such truncation should be captured by the state resolver below, rather than returning
+ // invalid data.
+ if state.account {
+ return r.readAccount(state.address, historyID)
+ }
+ return r.readStorage(state.address, state.storageKey, state.storageHash, historyID)
+}
diff --git a/triedb/pathdb/history_reader_test.go b/triedb/pathdb/history_reader_test.go
new file mode 100644
index 0000000000..4356490f23
--- /dev/null
+++ b/triedb/pathdb/history_reader_test.go
@@ -0,0 +1,159 @@
+// Copyright 2025 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 = db.tree.bottom().stateID() {
+ return
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+}
+
+func checkHistoricState(env *tester, root common.Hash, hr *historyReader) error {
+ // Short circuit if the historical state is no longer available
+ if rawdb.ReadStateID(env.db.diskdb, root) == nil {
+ return nil
+ }
+ var (
+ dl = env.db.tree.bottom()
+ stateID = rawdb.ReadStateID(env.db.diskdb, root)
+ accounts = env.snapAccounts[root]
+ storages = env.snapStorages[root]
+ )
+ for addrHash, accountData := range accounts {
+ latest, _ := dl.account(addrHash, 0)
+ blob, err := hr.read(newAccountIdentQuery(env.accountPreimage(addrHash), addrHash), *stateID, dl.stateID(), latest)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(accountData, blob) {
+ return fmt.Errorf("wrong account data, expected %x, got %x", accountData, blob)
+ }
+ }
+ for i := 0; i < len(env.roots); i++ {
+ if env.roots[i] == root {
+ break
+ }
+ // Find all accounts deleted in the past, ensure the associated data is null
+ for addrHash := range env.snapAccounts[env.roots[i]] {
+ if _, ok := accounts[addrHash]; !ok {
+ latest, _ := dl.account(addrHash, 0)
+ blob, err := hr.read(newAccountIdentQuery(env.accountPreimage(addrHash), addrHash), *stateID, dl.stateID(), latest)
+ if err != nil {
+ return err
+ }
+ if len(blob) != 0 {
+ return fmt.Errorf("wrong account data, expected null, got %x", blob)
+ }
+ }
+ }
+ }
+ for addrHash, slots := range storages {
+ for slotHash, slotData := range slots {
+ latest, _ := dl.storage(addrHash, slotHash, 0)
+ blob, err := hr.read(newStorageIdentQuery(env.accountPreimage(addrHash), addrHash, env.hashPreimage(slotHash), slotHash), *stateID, dl.stateID(), latest)
+ if err != nil {
+ return err
+ }
+ if !bytes.Equal(slotData, blob) {
+ return fmt.Errorf("wrong storage data, expected %x, got %x", slotData, blob)
+ }
+ }
+ }
+ for i := 0; i < len(env.roots); i++ {
+ if env.roots[i] == root {
+ break
+ }
+ // Find all storage slots deleted in the past, ensure the associated data is null
+ for addrHash, slots := range env.snapStorages[env.roots[i]] {
+ for slotHash := range slots {
+ _, ok := storages[addrHash]
+ if ok {
+ _, ok = storages[addrHash][slotHash]
+ }
+ if !ok {
+ latest, _ := dl.storage(addrHash, slotHash, 0)
+ blob, err := hr.read(newStorageIdentQuery(env.accountPreimage(addrHash), addrHash, env.hashPreimage(slotHash), slotHash), *stateID, dl.stateID(), latest)
+ if err != nil {
+ return err
+ }
+ if len(blob) != 0 {
+ return fmt.Errorf("wrong storage data, expected null, got %x", blob)
+ }
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func TestHistoryReader(t *testing.T) {
+ testHistoryReader(t, 0) // with all histories reserved
+ testHistoryReader(t, 10) // with latest 10 histories reserved
+}
+
+func testHistoryReader(t *testing.T, historyLimit uint64) {
+ maxDiffLayers = 4
+ defer func() {
+ maxDiffLayers = 128
+ }()
+ //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
+
+ env := newTester(t, historyLimit, false, 64, true)
+ defer env.release()
+ waitIndexing(env.db)
+
+ var (
+ roots = env.roots
+ dRoot = env.db.tree.bottom().rootHash()
+ hr = newHistoryReader(env.db.diskdb, env.db.freezer)
+ )
+ for _, root := range roots {
+ if root == dRoot {
+ break
+ }
+ if err := checkHistoricState(env, root, hr); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // Pile up more histories on top, ensuring the historic reader is not affected
+ env.extend(4)
+ waitIndexing(env.db)
+
+ for _, root := range roots {
+ if root == dRoot {
+ break
+ }
+ if err := checkHistoricState(env, root, hr); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
diff --git a/triedb/pathdb/iterator_binary.go b/triedb/pathdb/iterator_binary.go
index 0620081d0c..97a2918989 100644
--- a/triedb/pathdb/iterator_binary.go
+++ b/triedb/pathdb/iterator_binary.go
@@ -45,6 +45,10 @@ type binaryIterator struct {
// accounts in a slow, but easily verifiable way. Note this function is used
// for initialization, use `newBinaryAccountIterator` as the API.
func (dl *diskLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator {
+ // Block until the frozen buffer flushing is completed.
+ if err := dl.waitFlush(); err != nil {
+ panic(err)
+ }
// The state set in the disk layer is mutable, hold the lock before obtaining
// the account list to prevent concurrent map iteration and write.
dl.lock.RLock()
@@ -113,6 +117,10 @@ func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator
// storage slots in a slow, but easily verifiable way. Note this function is used
// for initialization, use `newBinaryStorageIterator` as the API.
func (dl *diskLayer) initBinaryStorageIterator(account common.Hash, seek common.Hash) *binaryIterator {
+ // Block until the frozen buffer flushing is completed.
+ if err := dl.waitFlush(); err != nil {
+ panic(err)
+ }
// The state set in the disk layer is mutable, hold the lock before obtaining
// the storage list to prevent concurrent map iteration and write.
dl.lock.RLock()
diff --git a/triedb/pathdb/iterator_fast.go b/triedb/pathdb/iterator_fast.go
index 87b2dd567a..04bf89e874 100644
--- a/triedb/pathdb/iterator_fast.go
+++ b/triedb/pathdb/iterator_fast.go
@@ -76,6 +76,11 @@ func newFastIterator(db *Database, root common.Hash, account common.Hash, seek c
if accountIterator {
switch dl := current.(type) {
case *diskLayer:
+ // Ensure no active background buffer flush is in progress, otherwise,
+ // part of the state data may become invisible.
+ if err := dl.waitFlush(); err != nil {
+ return nil, err
+ }
// The state set in the disk layer is mutable, hold the lock before obtaining
// the account list to prevent concurrent map iteration and write.
dl.lock.RLock()
@@ -113,6 +118,11 @@ func newFastIterator(db *Database, root common.Hash, account common.Hash, seek c
} else {
switch dl := current.(type) {
case *diskLayer:
+ // Ensure no active background buffer flush is in progress, otherwise,
+ // part of the state data may become invisible.
+ if err := dl.waitFlush(); err != nil {
+ return nil, err
+ }
// The state set in the disk layer is mutable, hold the lock before obtaining
// the storage list to prevent concurrent map iteration and write.
dl.lock.RLock()
diff --git a/triedb/pathdb/iterator_test.go b/triedb/pathdb/iterator_test.go
index 6517735d7e..b24575cb47 100644
--- a/triedb/pathdb/iterator_test.go
+++ b/triedb/pathdb/iterator_test.go
@@ -254,10 +254,9 @@ func TestFastIteratorBasics(t *testing.T) {
// TestAccountIteratorTraversal tests some simple multi-layer iteration.
func TestAccountIteratorTraversal(t *testing.T) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Stack three diff layers on top with various overlaps
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 0, trienode.NewMergedNodeSet(),
@@ -298,10 +297,9 @@ func TestAccountIteratorTraversal(t *testing.T) {
func TestStorageIteratorTraversal(t *testing.T) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Stack three diff layers on top with various overlaps
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 0, trienode.NewMergedNodeSet(),
@@ -311,14 +309,14 @@ func TestStorageIteratorTraversal(t *testing.T) {
NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil), nil, nil, false))
db.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), 0, trienode.NewMergedNodeSet(),
- NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil), nil, nil, false))
+ NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02"}}, nil), nil, nil, false))
// Verify the single and multi-layer iterators
head := db.tree.get(common.HexToHash("0x04"))
// singleLayer: 0x1, 0x2, 0x3
diffIter := newDiffStorageIterator(common.HexToHash("0xaa"), common.Hash{}, head.(*diffLayer).states.stateSet.storageList(common.HexToHash("0xaa")), nil)
- verifyIterator(t, 3, diffIter, verifyNothing)
+ verifyIterator(t, 2, diffIter, verifyNothing)
// binaryIterator: 0x1, 0x2, 0x3, 0x4, 0x5, 0x6
verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa"), common.Hash{}), verifyStorage)
@@ -342,10 +340,9 @@ func TestStorageIteratorTraversal(t *testing.T) {
// also expect the correct values to show up.
func TestAccountIteratorTraversalValues(t *testing.T) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Create a batch of account sets to seed subsequent layers with
var (
@@ -458,10 +455,9 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
func TestStorageIteratorTraversalValues(t *testing.T) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
wrapStorage := func(storage map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte {
return map[common.Hash]map[common.Hash][]byte{
@@ -591,10 +587,9 @@ func TestAccountIteratorLargeTraversal(t *testing.T) {
}
// Build up a large stack of snapshots
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
for i := 1; i < 128; i++ {
parent := types.EmptyRootHash
@@ -630,10 +625,10 @@ func TestAccountIteratorLargeTraversal(t *testing.T) {
// - continues iterating
func TestAccountIteratorFlattening(t *testing.T) {
config := &Config{
- WriteBufferSize: 10 * 1024,
+ WriteBufferSize: 10 * 1024,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Create a stack of diffs on top
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
@@ -662,11 +657,24 @@ func TestAccountIteratorFlattening(t *testing.T) {
}
func TestAccountIteratorSeek(t *testing.T) {
+ t.Run("fast", func(t *testing.T) {
+ testAccountIteratorSeek(t, func(db *Database, root, seek common.Hash) AccountIterator {
+ it, _ := db.AccountIterator(root, seek)
+ return it
+ })
+ })
+ t.Run("binary", func(t *testing.T) {
+ testAccountIteratorSeek(t, func(db *Database, root, seek common.Hash) AccountIterator {
+ return db.tree.get(root).(*diffLayer).newBinaryAccountIterator(seek)
+ })
+ })
+}
+
+func testAccountIteratorSeek(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
NewStateSetWithOrigin(randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil, nil, nil, false))
@@ -682,39 +690,39 @@ func TestAccountIteratorSeek(t *testing.T) {
// 03: aa, bb, dd, ee, f0 (, f0), ff
// 04: aa, bb, cc, dd, ee, f0 (, f0), ff (, ff)
// Construct various iterators and ensure their traversal is correct
- it, _ := db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd"))
+ it := newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xdd"))
defer it.Release()
verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff
- it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"))
+ it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xaa"))
defer it.Release()
verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff
- it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff"))
+ it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xff"))
defer it.Release()
verifyIterator(t, 1, it, verifyAccount) // expected: ff
- it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff1"))
+ it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xff1"))
defer it.Release()
verifyIterator(t, 0, it, verifyAccount) // expected: nothing
- it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb"))
+ it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xbb"))
defer it.Release()
verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff
- it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef"))
+ it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xef"))
defer it.Release()
verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
- it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0"))
+ it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xf0"))
defer it.Release()
verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
- it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff"))
+ it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xff"))
defer it.Release()
verifyIterator(t, 1, it, verifyAccount) // expected: ff
- it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff1"))
+ it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xff1"))
defer it.Release()
verifyIterator(t, 0, it, verifyAccount) // expected: nothing
}
@@ -735,10 +743,9 @@ func TestStorageIteratorSeek(t *testing.T) {
func testStorageIteratorSeek(t *testing.T, newIterator func(db *Database, root, account, seek common.Hash) StorageIterator) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Stack three diff layers on top with various overlaps
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
@@ -807,10 +814,9 @@ func TestAccountIteratorDeletions(t *testing.T) {
func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Stack three diff layers on top with various overlaps
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
@@ -847,10 +853,9 @@ func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, r
func TestStorageIteratorDeletions(t *testing.T) {
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// Stack three diff layers on top with various overlaps
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
@@ -915,10 +920,10 @@ func TestStaleIterator(t *testing.T) {
func testStaleIterator(t *testing.T, newIter func(db *Database, hash common.Hash) Iterator) {
config := &Config{
- WriteBufferSize: 16 * 1024 * 1024,
+ WriteBufferSize: 16 * 1024 * 1024,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
// [02 (disk), 03]
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(),
@@ -970,10 +975,9 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
return accounts
}
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
for i := 1; i <= 100; i++ {
parent := types.EmptyRootHash
@@ -1065,10 +1069,9 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
return accounts
}
config := &Config{
- WriteBufferSize: 0,
+ NoAsyncGeneration: true,
}
db := New(rawdb.NewMemoryDatabase(), config, false)
- db.waitGeneration()
db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), NewStateSetWithOrigin(makeAccounts(2000), nil, nil, nil, false))
for i := 2; i <= 100; i++ {
diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go
index 5dc6da92b8..e88b3e062f 100644
--- a/triedb/pathdb/journal.go
+++ b/triedb/pathdb/journal.go
@@ -160,7 +160,7 @@ func (db *Database) loadLayers() layer {
log.Info("Failed to load journal, discard it", "err", err)
}
// Return single layer with persistent state.
- return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0))
+ return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0), nil)
}
// loadDiskLayer reads the binary blob from the layer journal, reconstructing
@@ -192,7 +192,7 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
if err := states.decode(r); err != nil {
return nil, err
}
- return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored)), nil
+ return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored), nil), nil
}
// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
@@ -301,9 +301,10 @@ func (db *Database) Journal(root common.Hash) error {
} else { // disk layer only on noop runs (likely) or deep reorgs (unlikely)
log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers)
}
- // Terminate the background state generation if it's active
- if disk.generator != nil {
- disk.generator.stop()
+ // Block until the background flushing is finished and terminate
+ // the potential active state generator.
+ if err := disk.terminate(); err != nil {
+ return err
}
start := time.Now()
diff --git a/triedb/pathdb/layertree_test.go b/triedb/pathdb/layertree_test.go
index a72ff5899e..a76d60ba5b 100644
--- a/triedb/pathdb/layertree_test.go
+++ b/triedb/pathdb/layertree_test.go
@@ -27,7 +27,7 @@ import (
func newTestLayerTree() *layerTree {
db := New(rawdb.NewMemoryDatabase(), nil, false)
- l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0))
+ l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0), nil)
t := newLayerTree(l)
return t
}
diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go
index 6d40c5713b..779f9d813f 100644
--- a/triedb/pathdb/metrics.go
+++ b/triedb/pathdb/metrics.go
@@ -73,8 +73,14 @@ var (
historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil)
historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil)
+ indexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/index/time", nil)
+ unindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/unindex/time", nil)
+
lookupAddLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/add/time", nil)
lookupRemoveLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/remove/time", nil)
+
+ historicalAccountReadTimer = metrics.NewRegisteredResettingTimer("pathdb/history/account/reads", nil)
+ historicalStorageReadTimer = metrics.NewRegisteredResettingTimer("pathdb/history/storage/reads", nil)
)
// Metrics in generation
diff --git a/triedb/pathdb/reader.go b/triedb/pathdb/reader.go
index bc72db34e3..b7b18f13f9 100644
--- a/triedb/pathdb/reader.go
+++ b/triedb/pathdb/reader.go
@@ -19,10 +19,13 @@ package pathdb
import (
"errors"
"fmt"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/triedb/database"
@@ -192,3 +195,122 @@ func (db *Database) StateReader(root common.Hash) (database.StateReader, error)
layer: layer,
}, nil
}
+
+// HistoricalStateReader is a wrapper over history reader, providing access to
+// historical state.
+type HistoricalStateReader struct {
+ db *Database
+ reader *historyReader
+ id uint64
+}
+
+// HistoricReader constructs a reader for accessing the requested historic state.
+func (db *Database) HistoricReader(root common.Hash) (*HistoricalStateReader, error) {
+ // Bail out if the state history hasn't been fully indexed
+ if db.indexer == nil || !db.indexer.inited() {
+ return nil, errors.New("state histories haven't been fully indexed yet")
+ }
+ if db.freezer == nil {
+ return nil, errors.New("state histories are not available")
+ }
+ // States at the current disk layer or above are directly accessible via
+ // db.StateReader.
+ //
+ // States older than the current disk layer (including the disk layer
+ // itself) are available through historic state access.
+ //
+ // Note: the requested state may refer to a stale historic state that has
+ // already been pruned. This function does not validate availability, as
+ // underlying states may be pruned dynamically. Validity is checked during
+ // each actual state retrieval.
+ id := rawdb.ReadStateID(db.diskdb, root)
+ if id == nil {
+ return nil, fmt.Errorf("state %#x is not available", root)
+ }
+ return &HistoricalStateReader{
+ id: *id,
+ db: db,
+ reader: newHistoryReader(db.diskdb, db.freezer),
+ }, nil
+}
+
+// AccountRLP directly retrieves the account RLP associated with a particular
+// address in the slim data format. An error will be returned if the read
+// operation exits abnormally. Specifically, if the layer is already stale.
+//
+// Note:
+// - the returned account is not a copy, please don't modify it.
+// - no error will be returned if the requested account is not found in database.
+func (r *HistoricalStateReader) AccountRLP(address common.Address) ([]byte, error) {
+ defer func(start time.Time) {
+ historicalAccountReadTimer.UpdateSince(start)
+ }(time.Now())
+
+ // TODO(rjl493456442): Theoretically, the obtained disk layer could become stale
+ // within a very short time window.
+ //
+ // While reading the account data while holding `db.tree.lock` can resolve
+ // this issue, but it will introduce a heavy contention over the lock.
+ //
+ // Let's optimistically assume the situation is very unlikely to happen,
+ // and try to define a low granularity lock if the current approach doesn't
+ // work later.
+ dl := r.db.tree.bottom()
+ hash := crypto.Keccak256Hash(address.Bytes())
+ latest, err := dl.account(hash, 0)
+ if err != nil {
+ return nil, err
+ }
+ return r.reader.read(newAccountIdentQuery(address, hash), r.id, dl.stateID(), latest)
+}
+
+// Account directly retrieves the account associated with a particular address in
+// the slim data format. An error will be returned if the read operation exits
+// abnormally. Specifically, if the layer is already stale.
+//
+// No error will be returned if the requested account is not found in database
+func (r *HistoricalStateReader) Account(address common.Address) (*types.SlimAccount, error) {
+ blob, err := r.AccountRLP(address)
+ if err != nil {
+ return nil, err
+ }
+ if len(blob) == 0 {
+ return nil, nil
+ }
+ account := new(types.SlimAccount)
+ if err := rlp.DecodeBytes(blob, account); err != nil {
+ panic(err)
+ }
+ return account, nil
+}
+
+// Storage directly retrieves the storage data associated with a particular key,
+// within a particular account. An error will be returned if the read operation
+// exits abnormally. Specifically, if the layer is already stale.
+//
+// Note:
+// - the returned storage data is not a copy, please don't modify it.
+// - no error will be returned if the requested slot is not found in database.
+func (r *HistoricalStateReader) Storage(address common.Address, key common.Hash) ([]byte, error) {
+ defer func(start time.Time) {
+ historicalStorageReadTimer.UpdateSince(start)
+ }(time.Now())
+
+ // TODO(rjl493456442): Theoretically, the obtained disk layer could become stale
+ // within a very short time window.
+ //
+ // While reading the account data while holding `db.tree.lock` can resolve
+ // this issue, but it will introduce a heavy contention over the lock.
+ //
+ // Let's optimistically assume the situation is very unlikely to happen,
+ // and try to define a low granularity lock if the current approach doesn't
+ // work later.
+ dl := r.db.tree.bottom()
+ addrHash := crypto.Keccak256Hash(address.Bytes())
+ keyHash := crypto.Keccak256Hash(key.Bytes())
+ latest, err := dl.storage(addrHash, keyHash, 0)
+ if err != nil {
+ return nil, err
+ }
+ return r.reader.read(newStorageIdentQuery(address, addrHash, key, keyHash), r.id, dl.stateID(), latest)
+}
diff --git a/triedb/pathdb/verifier.go b/triedb/pathdb/verifier.go
new file mode 100644
index 0000000000..2d6f72925b
--- /dev/null
+++ b/triedb/pathdb/verifier.go
@@ -0,0 +1,355 @@
+// Copyright 2020 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 .
+
+package pathdb
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "math"
+ "runtime"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+// trieKV represents a trie key-value pair
+type trieKV struct {
+ key common.Hash
+ value []byte
+}
+
+type (
+ // trieHasherFn is the interface of trie hasher which can be implemented
+ // by different trie algorithm.
+ trieHasherFn func(in chan trieKV, out chan common.Hash)
+
+ // leafCallbackFn is the callback invoked at the leaves of the trie,
+ // returns the subtrie root with the specified subtrie identifier.
+ leafCallbackFn func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error)
+)
+
+// VerifyState traverses the flat states specified by the given state root and
+// ensures they are matched with each other.
+func (db *Database) VerifyState(root common.Hash) error {
+ acctIt, err := db.AccountIterator(root, common.Hash{})
+ if err != nil {
+ return err // The required snapshot might not exist.
+ }
+ defer acctIt.Release()
+
+ got, err := generateTrieRoot(acctIt, common.Hash{}, stackTrieHasher, func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
+ // Migrate the code first, commit the contract code into the tmp db.
+ if codeHash != types.EmptyCodeHash {
+ code := rawdb.ReadCode(db.diskdb, codeHash)
+ if len(code) == 0 {
+ return common.Hash{}, errors.New("failed to read contract code")
+ }
+ }
+ // Then migrate all storage trie nodes into the tmp db.
+ storageIt, err := db.StorageIterator(root, accountHash, common.Hash{})
+ if err != nil {
+ return common.Hash{}, err
+ }
+ defer storageIt.Release()
+
+ hash, err := generateTrieRoot(storageIt, accountHash, stackTrieHasher, nil, stat, false)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return hash, nil
+ }, newGenerateStats(), true)
+
+ if err != nil {
+ return err
+ }
+ if got != root {
+ return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root)
+ }
+ return nil
+}
+
+// generateStats is a collection of statistics gathered by the trie generator
+// for logging purposes.
+type generateStats struct {
+ head common.Hash
+ start time.Time
+
+ accounts uint64 // Number of accounts done (including those being crawled)
+ slots uint64 // Number of storage slots done (including those being crawled)
+
+ slotsStart map[common.Hash]time.Time // Start time for account slot crawling
+ slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled
+
+ lock sync.RWMutex
+}
+
+// newGenerateStats creates a new generator stats.
+func newGenerateStats() *generateStats {
+ return &generateStats{
+ slotsStart: make(map[common.Hash]time.Time),
+ slotsHead: make(map[common.Hash]common.Hash),
+ start: time.Now(),
+ }
+}
+
+// progressAccounts updates the generator stats for the account range.
+func (stat *generateStats) progressAccounts(account common.Hash, done uint64) {
+ stat.lock.Lock()
+ defer stat.lock.Unlock()
+
+ stat.accounts += done
+ stat.head = account
+}
+
+// finishAccounts updates the generator stats for the finished account range.
+func (stat *generateStats) finishAccounts(done uint64) {
+ stat.lock.Lock()
+ defer stat.lock.Unlock()
+
+ stat.accounts += done
+}
+
+// progressContract updates the generator stats for a specific in-progress contract.
+func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) {
+ stat.lock.Lock()
+ defer stat.lock.Unlock()
+
+ stat.slots += done
+ stat.slotsHead[account] = slot
+ if _, ok := stat.slotsStart[account]; !ok {
+ stat.slotsStart[account] = time.Now()
+ }
+}
+
+// finishContract updates the generator stats for a specific just-finished contract.
+func (stat *generateStats) finishContract(account common.Hash, done uint64) {
+ stat.lock.Lock()
+ defer stat.lock.Unlock()
+
+ stat.slots += done
+ delete(stat.slotsHead, account)
+ delete(stat.slotsStart, account)
+}
+
+// report prints the cumulative progress statistic smartly.
+func (stat *generateStats) report() {
+ stat.lock.RLock()
+ defer stat.lock.RUnlock()
+
+ ctx := []interface{}{
+ "accounts", stat.accounts,
+ "slots", stat.slots,
+ "elapsed", common.PrettyDuration(time.Since(stat.start)),
+ }
+ if stat.accounts > 0 {
+ // If there's progress on the account trie, estimate the time to finish crawling it
+ if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 {
+ var (
+ left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts
+ speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+ eta = time.Duration(left/speed) * time.Millisecond
+ )
+ // If there are large contract crawls in progress, estimate their finish time
+ for acc, head := range stat.slotsHead {
+ start := stat.slotsStart[acc]
+ if done := binary.BigEndian.Uint64(head[:8]); done > 0 {
+ var (
+ left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8])
+ speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+ )
+ // Override the ETA if larger than the largest until now
+ if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA {
+ eta = slotETA
+ }
+ }
+ }
+ ctx = append(ctx, []interface{}{
+ "eta", common.PrettyDuration(eta),
+ }...)
+ }
+ }
+ log.Info("Iterating state snapshot", ctx...)
+}
+
+// reportDone prints the last log when the whole generation is finished.
+func (stat *generateStats) reportDone() {
+ stat.lock.RLock()
+ defer stat.lock.RUnlock()
+
+ var ctx []interface{}
+ ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
+ if stat.slots != 0 {
+ ctx = append(ctx, []interface{}{"slots", stat.slots}...)
+ }
+ ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
+ log.Info("Iterated snapshot", ctx...)
+}
+
+// runReport periodically prints the progress information.
+func runReport(stats *generateStats, stop chan bool) {
+ timer := time.NewTimer(0)
+ defer timer.Stop()
+
+ for {
+ select {
+ case <-timer.C:
+ stats.report()
+ timer.Reset(time.Second * 8)
+ case success := <-stop:
+ if success {
+ stats.reportDone()
+ }
+ return
+ }
+ }
+}
+
+// generateTrieRoot generates the trie hash based on the snapshot iterator.
+// It can be used for generating account trie, storage trie or even the
+// whole state which connects the accounts and the corresponding storages.
+func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieHasherFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
+ var (
+ in = make(chan trieKV) // chan to pass leaves
+ out = make(chan common.Hash, 1) // chan to collect result
+ stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled
+ wg sync.WaitGroup
+ )
+ // Spin up a go-routine for trie hash re-generation
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ generatorFn(in, out)
+ }()
+ // Spin up a go-routine for progress logging
+ if report && stats != nil {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ runReport(stats, stoplog)
+ }()
+ }
+ // Create a semaphore to assign tasks and collect results through. We'll pre-
+ // fill it with nils, thus using the same channel for both limiting concurrent
+ // processing and gathering results.
+ threads := runtime.NumCPU()
+ results := make(chan error, threads)
+ for i := 0; i < threads; i++ {
+ results <- nil // fill the semaphore
+ }
+ // stop is a helper function to shutdown the background threads
+ // and return the re-generated trie hash.
+ stop := func(fail error) (common.Hash, error) {
+ close(in)
+ result := <-out
+ for i := 0; i < threads; i++ {
+ if err := <-results; err != nil && fail == nil {
+ fail = err
+ }
+ }
+ stoplog <- fail == nil
+
+ wg.Wait()
+ return result, fail
+ }
+ var (
+ logged = time.Now()
+ processed = uint64(0)
+ leaf trieKV
+ )
+ // Start to feed leaves
+ for it.Next() {
+ if account == (common.Hash{}) {
+ var (
+ err error
+ fullData []byte
+ )
+ if leafCallback == nil {
+ fullData, err = types.FullAccountRLP(it.(AccountIterator).Account())
+ if err != nil {
+ return stop(err)
+ }
+ } else {
+ // Wait until the semaphore allows us to continue, aborting if
+ // a sub-task failed
+ if err := <-results; err != nil {
+ results <- nil // stop will drain the results, add a noop back for this error we just consumed
+ return stop(err)
+ }
+ // Fetch the next account and process it concurrently
+ account, err := types.FullAccount(it.(AccountIterator).Account())
+ if err != nil {
+ return stop(err)
+ }
+ go func(hash common.Hash) {
+ subroot, err := leafCallback(hash, common.BytesToHash(account.CodeHash), stats)
+ if err != nil {
+ results <- err
+ return
+ }
+ if account.Root != subroot {
+ results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot)
+ return
+ }
+ results <- nil
+ }(it.Hash())
+ fullData, err = rlp.EncodeToBytes(account)
+ if err != nil {
+ return stop(err)
+ }
+ }
+ leaf = trieKV{it.Hash(), fullData}
+ } else {
+ leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())}
+ }
+ in <- leaf
+
+ // Accumulate the generation statistic if it's required.
+ processed++
+ if time.Since(logged) > 3*time.Second && stats != nil {
+ if account == (common.Hash{}) {
+ stats.progressAccounts(it.Hash(), processed)
+ } else {
+ stats.progressContract(account, it.Hash(), processed)
+ }
+ logged, processed = time.Now(), 0
+ }
+ }
+ // Commit the last part statistic.
+ if processed > 0 && stats != nil {
+ if account == (common.Hash{}) {
+ stats.finishAccounts(processed)
+ } else {
+ stats.finishContract(account, processed)
+ }
+ }
+ return stop(nil)
+}
+
+func stackTrieHasher(in chan trieKV, out chan common.Hash) {
+ t := trie.NewStackTrie(nil)
+ for leaf := range in {
+ t.Update(leaf.key[:], leaf.value)
+ }
+ out <- t.Hash()
+}