mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
core, trie/triedb/pathdb: adjust pathdb for verkle
This commit is contained in:
parent
eaac53ec38
commit
b122acbc0d
24 changed files with 382 additions and 300 deletions
|
|
@ -220,11 +220,18 @@ func removeDB(ctx *cli.Context) error {
|
||||||
ancientDir = config.Node.ResolvePath(ancientDir)
|
ancientDir = config.Node.ResolvePath(ancientDir)
|
||||||
}
|
}
|
||||||
// Delete state data
|
// Delete state data
|
||||||
statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)}
|
statePaths := []string{
|
||||||
|
rootDir,
|
||||||
|
filepath.Join(ancientDir, rawdb.MerkleStateFreezerName),
|
||||||
|
filepath.Join(ancientDir, rawdb.VerkleStateFreezerName),
|
||||||
|
}
|
||||||
confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
|
confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
|
||||||
|
|
||||||
// Delete ancient chain
|
// Delete ancient chain
|
||||||
chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)}
|
chainPaths := []string{filepath.Join(
|
||||||
|
ancientDir,
|
||||||
|
rawdb.ChainFreezerName,
|
||||||
|
)}
|
||||||
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
|
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,16 +65,10 @@ func (h *hasher) release() {
|
||||||
hasherPool.Put(h)
|
hasherPool.Put(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAccountTrieNode retrieves the account trie node and the associated node
|
// ReadAccountTrieNode retrieves the account trie node with the specified node path.
|
||||||
// hash with the specified node path.
|
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
|
||||||
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) {
|
data, _ := db.Get(accountTrieNodeKey(path))
|
||||||
data, err := db.Get(accountTrieNodeKey(path))
|
return data
|
||||||
if err != nil {
|
|
||||||
return nil, common.Hash{}
|
|
||||||
}
|
|
||||||
h := newHasher()
|
|
||||||
defer h.release()
|
|
||||||
return data, h.hash(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAccountTrieNode checks the account trie node presence with the specified
|
// HasAccountTrieNode checks the account trie node presence with the specified
|
||||||
|
|
@ -113,16 +107,10 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStorageTrieNode retrieves the storage trie node and the associated node
|
// ReadStorageTrieNode retrieves the storage trie node with the specified node path.
|
||||||
// hash with the specified node path.
|
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
|
||||||
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) {
|
data, _ := db.Get(storageTrieNodeKey(accountHash, path))
|
||||||
data, err := db.Get(storageTrieNodeKey(accountHash, path))
|
return data
|
||||||
if err != nil {
|
|
||||||
return nil, common.Hash{}
|
|
||||||
}
|
|
||||||
h := newHasher()
|
|
||||||
defer h.release()
|
|
||||||
return data, h.hash(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasStorageTrieNode checks the storage trie node presence with the provided
|
// HasStorageTrieNode checks the storage trie node presence with the provided
|
||||||
|
|
@ -220,16 +208,15 @@ func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash
|
||||||
case HashScheme:
|
case HashScheme:
|
||||||
return ReadLegacyTrieNode(db, hash)
|
return ReadLegacyTrieNode(db, hash)
|
||||||
case PathScheme:
|
case PathScheme:
|
||||||
var (
|
var blob []byte
|
||||||
blob []byte
|
|
||||||
nHash common.Hash
|
|
||||||
)
|
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
blob, nHash = ReadAccountTrieNode(db, path)
|
blob = ReadAccountTrieNode(db, path)
|
||||||
} else {
|
} else {
|
||||||
blob, nHash = ReadStorageTrieNode(db, owner, path)
|
blob = ReadStorageTrieNode(db, owner, path)
|
||||||
}
|
}
|
||||||
if nHash != hash {
|
h := newHasher()
|
||||||
|
defer h.release()
|
||||||
|
if h.hash(blob) != hash {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return blob
|
return blob
|
||||||
|
|
@ -287,8 +274,12 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has
|
||||||
// ReadStateScheme reads the state scheme of persistent state, or none
|
// ReadStateScheme reads the state scheme of persistent state, or none
|
||||||
// if the state is not present in database.
|
// if the state is not present in database.
|
||||||
func ReadStateScheme(db ethdb.Reader) string {
|
func ReadStateScheme(db ethdb.Reader) string {
|
||||||
// Check if state in path-based scheme is present
|
// Check if state in path-based scheme is present, it can be either
|
||||||
blob, _ := ReadAccountTrieNode(db, nil)
|
// merkle tree or verkle tree.
|
||||||
|
blob := ReadAccountTrieNode(db, nil)
|
||||||
|
if len(blob) == 0 {
|
||||||
|
blob, _ = db.Get(verkleTrieNodeKey(nil)) // FIX HACK(rjl493456442)
|
||||||
|
}
|
||||||
if len(blob) != 0 {
|
if len(blob) != 0 {
|
||||||
return PathScheme
|
return PathScheme
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,20 @@ var stateFreezerNoSnappy = map[string]bool{
|
||||||
// The list of identifiers of ancient stores.
|
// The list of identifiers of ancient stores.
|
||||||
var (
|
var (
|
||||||
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
||||||
StateFreezerName = "state" // the folder name of reverse diff ancient store.
|
MerkleStateFreezerName = "state" // the folder name of reverse diff ancient store.
|
||||||
|
VerkleStateFreezerName = "state_verkle" // the folder name of reverse diff ancient store.
|
||||||
)
|
)
|
||||||
|
|
||||||
// freezers the collections of all builtin freezers.
|
// freezers the collections of all builtin freezers.
|
||||||
var freezers = []string{ChainFreezerName, StateFreezerName}
|
var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName}
|
||||||
|
|
||||||
// NewStateFreezer initializes the freezer for state history.
|
// NewStateFreezer initializes the freezer for state history.
|
||||||
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) {
|
func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (*ResettableFreezer, error) {
|
||||||
return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
|
var name string
|
||||||
|
if verkle {
|
||||||
|
name = filepath.Join(ancientDir, VerkleStateFreezerName)
|
||||||
|
} else {
|
||||||
|
name = filepath.Join(ancientDir, MerkleStateFreezerName)
|
||||||
|
}
|
||||||
|
return NewResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,21 +88,18 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
|
||||||
}
|
}
|
||||||
infos = append(infos, info)
|
infos = append(infos, info)
|
||||||
|
|
||||||
case StateFreezerName:
|
case MerkleStateFreezerName, VerkleStateFreezerName:
|
||||||
if ReadStateScheme(db) != PathScheme {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
datadir, err := db.AncientDatadir()
|
datadir, err := db.AncientDatadir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f, err := NewStateFreezer(datadir, true)
|
f, err := NewStateFreezer(datadir, freezer == VerkleStateFreezerName, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
continue // might be possible the state freezer is not existent
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f)
|
info, err := inspect(freezer, stateFreezerNoSnappy, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +124,7 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
|
||||||
switch freezerName {
|
switch freezerName {
|
||||||
case ChainFreezerName:
|
case ChainFreezerName:
|
||||||
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
|
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
|
||||||
case StateFreezerName:
|
case MerkleStateFreezerName, VerkleStateFreezerName:
|
||||||
path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy
|
path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
beaconHeaders stat
|
beaconHeaders stat
|
||||||
cliqueSnaps stat
|
cliqueSnaps stat
|
||||||
|
|
||||||
|
// Verkle statistics
|
||||||
|
verkleTries stat
|
||||||
|
verkleStateLookups stat
|
||||||
|
|
||||||
// Les statistic
|
// Les statistic
|
||||||
chtTrieNodes stat
|
chtTrieNodes stat
|
||||||
bloomTrieNodes stat
|
bloomTrieNodes stat
|
||||||
|
|
@ -549,6 +553,20 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
bytes.HasPrefix(key, BloomTrieIndexPrefix) ||
|
bytes.HasPrefix(key, BloomTrieIndexPrefix) ||
|
||||||
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub
|
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub
|
||||||
bloomTrieNodes.Add(size)
|
bloomTrieNodes.Add(size)
|
||||||
|
case bytes.HasPrefix(key, VerklePrefix):
|
||||||
|
remain := key[len(VerklePrefix):]
|
||||||
|
switch {
|
||||||
|
case IsAccountTrieNode(remain):
|
||||||
|
verkleTries.Add(size)
|
||||||
|
case bytes.HasPrefix(remain, stateIDPrefix) && len(remain) == len(stateIDPrefix)+common.HashLength:
|
||||||
|
verkleStateLookups.Add(size)
|
||||||
|
case bytes.Equal(remain, persistentStateIDKey):
|
||||||
|
metadata.Add(size)
|
||||||
|
case bytes.Equal(remain, trieJournalKey):
|
||||||
|
metadata.Add(size)
|
||||||
|
case bytes.Equal(remain, snapshotSyncStatusKey):
|
||||||
|
metadata.Add(size)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
var accounted bool
|
var accounted bool
|
||||||
for _, meta := range [][]byte{
|
for _, meta := range [][]byte{
|
||||||
|
|
@ -595,6 +613,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
|
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
|
||||||
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
|
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
|
||||||
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
|
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
|
||||||
|
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()},
|
||||||
|
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()},
|
||||||
{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
|
{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
|
||||||
{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
|
{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,10 @@ var (
|
||||||
trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node
|
trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node
|
||||||
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
|
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
|
||||||
|
|
||||||
|
// VerklePrefix is the prefix of verkle states(verkle trie nodes,
|
||||||
|
// trie journal, persistent state id, state id lookups).
|
||||||
|
VerklePrefix = []byte("v")
|
||||||
|
|
||||||
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
||||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||||
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
||||||
|
|
@ -270,6 +274,11 @@ func accountTrieNodeKey(path []byte) []byte {
|
||||||
return append(trieNodeAccountPrefix, path...)
|
return append(trieNodeAccountPrefix, path...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verkleTrieNodeKey = verklePrefix + trieNodeAccountPrefix + nodePath.
|
||||||
|
func verkleTrieNodeKey(path []byte) []byte {
|
||||||
|
return append(VerklePrefix, append(trieNodeAccountPrefix, path...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// storageTrieNodeKey = trieNodeStoragePrefix + accountHash + nodePath.
|
// storageTrieNodeKey = trieNodeStoragePrefix + accountHash + nodePath.
|
||||||
func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte {
|
func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte {
|
||||||
buf := make([]byte, len(trieNodeStoragePrefix)+common.HashLength+len(path))
|
buf := make([]byte, len(trieNodeStoragePrefix)+common.HashLength+len(path))
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
|
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
|
||||||
|
|
@ -28,6 +29,23 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Reader wraps the Node method of a backing trie store.
|
||||||
|
type Reader interface {
|
||||||
|
// Node retrieves the trie node blob with the provided trie identifier, node path and
|
||||||
|
// the corresponding node hash. No error will be returned if the node is not found.
|
||||||
|
//
|
||||||
|
// When looking up nodes in the account trie, 'owner' is the zero hash. For contract
|
||||||
|
// storage trie nodes, 'owner' is the hash of the account address that containing the
|
||||||
|
// storage.
|
||||||
|
//
|
||||||
|
// Notably, the provided hash might be useless in path mode if hash check is
|
||||||
|
// configured as disabled, e.g. in the verkle context.
|
||||||
|
//
|
||||||
|
// Don't modify the returned byte slice since it's not deep-copied and still
|
||||||
|
// be referenced by database.
|
||||||
|
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Config defines all necessary options for database.
|
// Config defines all necessary options for database.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Preimages bool // Flag whether the preimage of node key is recorded
|
Preimages bool // Flag whether the preimage of node key is recorded
|
||||||
|
|
@ -40,15 +58,21 @@ type Config struct {
|
||||||
// default settings.
|
// default settings.
|
||||||
var HashDefaults = &Config{
|
var HashDefaults = &Config{
|
||||||
Preimages: false,
|
Preimages: false,
|
||||||
|
IsVerkle: false,
|
||||||
HashDB: hashdb.Defaults,
|
HashDB: hashdb.Defaults,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PathDefaults represents a config for using path-based scheme with
|
||||||
|
// default settings.
|
||||||
|
var PathDefaults = &Config{
|
||||||
|
Preimages: false,
|
||||||
|
IsVerkle: false,
|
||||||
|
PathDB: pathdb.Defaults,
|
||||||
|
}
|
||||||
|
|
||||||
// backend defines the methods needed to access/update trie nodes in different
|
// backend defines the methods needed to access/update trie nodes in different
|
||||||
// state scheme.
|
// state scheme.
|
||||||
type backend interface {
|
type backend interface {
|
||||||
// Scheme returns the identifier of used storage scheme.
|
|
||||||
Scheme() string
|
|
||||||
|
|
||||||
// Initialized returns an indicator if the state data is already initialized
|
// Initialized returns an indicator if the state data is already initialized
|
||||||
// according to the state scheme.
|
// according to the state scheme.
|
||||||
Initialized(genesisRoot common.Hash) bool
|
Initialized(genesisRoot common.Hash) bool
|
||||||
|
|
@ -106,7 +130,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
|
||||||
log.Crit("Both 'hash' and 'path' mode are configured")
|
log.Crit("Both 'hash' and 'path' mode are configured")
|
||||||
}
|
}
|
||||||
if config.PathDB != nil {
|
if config.PathDB != nil {
|
||||||
db.backend = pathdb.New(diskdb, config.PathDB)
|
db.backend = pathdb.New(diskdb, config.PathDB, config.IsVerkle)
|
||||||
} else {
|
} else {
|
||||||
db.backend = hashdb.New(diskdb, config.HashDB, mptResolver{})
|
db.backend = hashdb.New(diskdb, config.HashDB, mptResolver{})
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +196,10 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool {
|
||||||
|
|
||||||
// Scheme returns the node scheme used in the database.
|
// Scheme returns the node scheme used in the database.
|
||||||
func (db *Database) Scheme() string {
|
func (db *Database) Scheme() string {
|
||||||
return db.backend.Scheme()
|
if db.config.PathDB != nil {
|
||||||
|
return rawdb.PathScheme
|
||||||
|
}
|
||||||
|
return rawdb.HashScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close flushes the dangling preimages to disk and closes the trie database.
|
// Close flushes the dangling preimages to disk and closes the trie database.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package trie
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -691,13 +692,12 @@ func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists
|
||||||
}
|
}
|
||||||
// If node is running with path scheme, check the presence with node path.
|
// If node is running with path scheme, check the presence with node path.
|
||||||
var blob []byte
|
var blob []byte
|
||||||
var dbHash common.Hash
|
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path)
|
blob = rawdb.ReadAccountTrieNode(s.database, path)
|
||||||
} else {
|
} else {
|
||||||
blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path)
|
blob = rawdb.ReadStorageTrieNode(s.database, owner, path)
|
||||||
}
|
}
|
||||||
exists = hash == dbHash
|
exists = hash == crypto.Keccak256Hash(blob)
|
||||||
inconsistent = !exists && len(blob) != 0
|
inconsistent = !exists && len(blob) != 0
|
||||||
return exists, inconsistent
|
return exists, inconsistent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader wraps the Node method of a backing trie store.
|
|
||||||
type Reader interface {
|
|
||||||
// Node retrieves the trie node blob with the provided trie identifier, node path and
|
|
||||||
// the corresponding node hash. No error will be returned if the node is not found.
|
|
||||||
//
|
|
||||||
// When looking up nodes in the account trie, 'owner' is the zero hash. For contract
|
|
||||||
// storage trie nodes, 'owner' is the hash of the account address that containing the
|
|
||||||
// storage.
|
|
||||||
//
|
|
||||||
// TODO(rjl493456442): remove the 'hash' parameter, it's redundant in PBSS.
|
|
||||||
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trieReader is a wrapper of the underlying node reader. It's not safe
|
// trieReader is a wrapper of the underlying node reader. It's not safe
|
||||||
// for concurrent usage.
|
// for concurrent usage.
|
||||||
type trieReader struct {
|
type trieReader struct {
|
||||||
|
|
|
||||||
|
|
@ -624,11 +624,6 @@ func (db *Database) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheme returns the node scheme used in the database.
|
|
||||||
func (db *Database) Scheme() string {
|
|
||||||
return rawdb.HashScheme
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader retrieves a node reader belonging to the given state root.
|
// Reader retrieves a node reader belonging to the given state root.
|
||||||
// An error will be returned if the requested state is not available.
|
// An error will be returned if the requested state is not available.
|
||||||
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,34 @@ const (
|
||||||
DefaultBufferSize = 64 * 1024 * 1024
|
DefaultBufferSize = 64 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
locDirtyCache = "dirty"
|
||||||
|
locCleanCache = "clean"
|
||||||
|
locDisk = "disk"
|
||||||
|
locDiffLayer = "diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodeLoc is a helpful structure that contains the location where the node
|
||||||
|
// is found, as it's useful for debugging purposes.
|
||||||
|
type nodeLoc struct {
|
||||||
|
loc string
|
||||||
|
depth int
|
||||||
|
}
|
||||||
|
|
||||||
|
// string returns the string representation of node location.
|
||||||
|
func (loc *nodeLoc) string() string {
|
||||||
|
return fmt.Sprintf("loc: %s, depth: %d", loc.loc, loc.depth)
|
||||||
|
}
|
||||||
|
|
||||||
// layer is the interface implemented by all state layers which includes some
|
// layer is the interface implemented by all state layers which includes some
|
||||||
// public methods and some additional methods for internal usage.
|
// public methods and some additional methods for internal usage.
|
||||||
type layer interface {
|
type layer interface {
|
||||||
// Node retrieves the trie node with the node info. An error will be returned
|
// Node retrieves the trie node with the node info. An error will be returned
|
||||||
// if the read operation exits abnormally. For example, if the layer is already
|
// if the read operation exits abnormally. Specifically, if the layer is
|
||||||
// stale, or the associated state is regarded as corrupted. Notably, no error
|
// already stale.
|
||||||
// will be returned if the requested node is not found in database.
|
//
|
||||||
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
|
// Note, no error will be returned if the requested node is not found in database.
|
||||||
|
node(owner common.Hash, path []byte, depth int) ([]byte, *nodeLoc, error)
|
||||||
|
|
||||||
// rootHash returns the root hash for which this layer was made.
|
// rootHash returns the root hash for which this layer was made.
|
||||||
rootHash() common.Hash
|
rootHash() common.Hash
|
||||||
|
|
@ -76,7 +96,7 @@ type layer interface {
|
||||||
// the provided dirty trie nodes along with the state change set.
|
// the provided dirty trie nodes along with the state change set.
|
||||||
//
|
//
|
||||||
// Note, the maps are retained by the method to avoid copying everything.
|
// Note, the maps are retained by the method to avoid copying everything.
|
||||||
update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer
|
update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string][]byte, states *triestate.Set) *diffLayer
|
||||||
|
|
||||||
// journal commits an entire diff hierarchy to disk into a single journal entry.
|
// journal commits an entire diff hierarchy to disk into a single journal entry.
|
||||||
// This is meant to be used during shutdown to persist the layer without
|
// This is meant to be used during shutdown to persist the layer without
|
||||||
|
|
@ -90,6 +110,7 @@ type Config struct {
|
||||||
CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
|
CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
|
||||||
DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes
|
DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes
|
||||||
ReadOnly bool // Flag whether the database is opened in read only mode.
|
ReadOnly bool // Flag whether the database is opened in read only mode.
|
||||||
|
Hasher func([]byte) common.Hash // Function to compute the hash of node
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitize checks the provided user configurations and changes anything that's
|
// sanitize checks the provided user configurations and changes anything that's
|
||||||
|
|
@ -100,6 +121,9 @@ func (c *Config) sanitize() *Config {
|
||||||
log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize))
|
log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize))
|
||||||
conf.DirtyCacheSize = maxBufferSize
|
conf.DirtyCacheSize = maxBufferSize
|
||||||
}
|
}
|
||||||
|
if conf.Hasher == nil {
|
||||||
|
conf.Hasher = hashNode
|
||||||
|
}
|
||||||
return &conf
|
return &conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +137,12 @@ var Defaults = &Config{
|
||||||
// ReadOnly is the config in order to open database in read only mode.
|
// ReadOnly is the config in order to open database in read only mode.
|
||||||
var ReadOnly = &Config{ReadOnly: true}
|
var ReadOnly = &Config{ReadOnly: true}
|
||||||
|
|
||||||
|
// readOption contains the configurations for reader.
|
||||||
|
type readOption struct {
|
||||||
|
checkHash bool
|
||||||
|
hasher func([]byte) common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
// Database is a multiple-layered structure for maintaining in-memory trie nodes.
|
// Database is a multiple-layered structure for maintaining in-memory trie nodes.
|
||||||
// It consists of one persistent base layer backed by a key-value store, on top
|
// It consists of one persistent base layer backed by a key-value store, on top
|
||||||
// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
|
// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
|
||||||
|
|
@ -135,23 +165,33 @@ type Database struct {
|
||||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||||
tree *layerTree // The group for all known layers
|
tree *layerTree // The group for all known layers
|
||||||
freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests
|
freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests
|
||||||
|
readOption *readOption // Options for constructing reader.
|
||||||
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
||||||
}
|
}
|
||||||
|
|
||||||
// New attempts to load an already existing layer from a persistent key-value
|
// New attempts to load an already existing layer from a persistent key-value
|
||||||
// store (with a number of memory layers from a journal). If the journal is not
|
// store (with a number of memory layers from a journal). If the journal is not
|
||||||
// matched with the base persistent layer, all the recorded diff layers are discarded.
|
// matched with the base persistent layer, all the recorded diff layers are discarded.
|
||||||
func New(diskdb ethdb.Database, config *Config) *Database {
|
func New(diskdb ethdb.Database, config *Config, verkle bool) *Database {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = Defaults
|
config = Defaults
|
||||||
}
|
}
|
||||||
config = config.sanitize()
|
config = config.sanitize()
|
||||||
|
|
||||||
|
// Establish a dedicated database namespace tailored for verkle-specific
|
||||||
|
// data, ensuring the isolation of both verkle and mpt tree data. It's
|
||||||
|
// important to note that the introduction of a prefix won't lead to
|
||||||
|
// substantial storage overhead, as the underlying database will efficiently
|
||||||
|
// compress the shared key prefix.
|
||||||
|
if verkle {
|
||||||
|
diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
|
||||||
|
}
|
||||||
db := &Database{
|
db := &Database{
|
||||||
readOnly: config.ReadOnly,
|
readOnly: config.ReadOnly,
|
||||||
bufferSize: config.DirtyCacheSize,
|
bufferSize: config.DirtyCacheSize,
|
||||||
config: config,
|
config: config,
|
||||||
diskdb: diskdb,
|
diskdb: diskdb,
|
||||||
|
readOption: &readOption{checkHash: !verkle, hasher: config.Hasher},
|
||||||
}
|
}
|
||||||
// Construct the layer tree by resolving the in-disk singleton state
|
// Construct the layer tree by resolving the in-disk singleton state
|
||||||
// and in-memory layer journal.
|
// and in-memory layer journal.
|
||||||
|
|
@ -164,7 +204,7 @@ func New(diskdb ethdb.Database, config *Config) *Database {
|
||||||
// mechanism also ensures that at most one **non-readOnly** database
|
// mechanism also ensures that at most one **non-readOnly** database
|
||||||
// is opened at the same time to prevent accidental mutation.
|
// is opened at the same time to prevent accidental mutation.
|
||||||
if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly {
|
if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly {
|
||||||
freezer, err := rawdb.NewStateFreezer(ancient, false)
|
freezer, err := rawdb.NewStateFreezer(ancient, verkle, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Failed to open state history freezer", "err", err)
|
log.Crit("Failed to open state history freezer", "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -207,13 +247,50 @@ func New(diskdb ethdb.Database, config *Config) *Database {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reader implements the Reader interface, providing the functionalities to
|
||||||
|
// retrieve trie nodes by wrapping the internal state layer.
|
||||||
|
type reader struct {
|
||||||
|
layer layer
|
||||||
|
option *readOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node implements trie.Reader interface, retrieving the node with specified
|
||||||
|
// node info. Don't modify the returned byte slice since it's not deep-copied
|
||||||
|
// and still be referenced by database.
|
||||||
|
func (r *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
|
||||||
|
blob, loc, err := r.layer.node(owner, path, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Skip the hash comparison if it's disabled. Normally it will be configured
|
||||||
|
// in the verkle context because slim format is used in verkle which doesn't
|
||||||
|
// store the node hash at all to reduce read/write amplification.
|
||||||
|
if !r.option.checkHash {
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
|
if got := r.option.hasher(blob); got != hash {
|
||||||
|
switch loc.loc {
|
||||||
|
case locCleanCache:
|
||||||
|
cleanFalseMeter.Mark(1)
|
||||||
|
case locDirtyCache:
|
||||||
|
dirtyFalseMeter.Mark(1)
|
||||||
|
case locDiffLayer:
|
||||||
|
diffFalseMeter.Mark(1)
|
||||||
|
case locDisk:
|
||||||
|
diskFalseMeter.Mark(1)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unexpected node: (%x %v), %x!=%x, %s", owner, path, hash, got, loc.string())
|
||||||
|
}
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Reader retrieves a layer belonging to the given state root.
|
// Reader retrieves a layer belonging to the given state root.
|
||||||
func (db *Database) Reader(root common.Hash) (layer, error) {
|
func (db *Database) Reader(root common.Hash) (*reader, error) {
|
||||||
l := db.tree.get(root)
|
layer := db.tree.get(root)
|
||||||
if l == nil {
|
if layer == nil {
|
||||||
return nil, fmt.Errorf("state %#x is not available", root)
|
return nil, fmt.Errorf("state %#x is not available", root)
|
||||||
}
|
}
|
||||||
return l, nil
|
return &reader{layer: layer, option: db.readOption}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update adds a new layer into the tree, if that can be linked to an existing
|
// Update adds a new layer into the tree, if that can be linked to an existing
|
||||||
|
|
@ -297,7 +374,7 @@ func (db *Database) Enable(root common.Hash) error {
|
||||||
}
|
}
|
||||||
// Ensure the provided state root matches the stored one.
|
// Ensure the provided state root matches the stored one.
|
||||||
root = types.TrieRootHash(root)
|
root = types.TrieRootHash(root)
|
||||||
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
stored := db.config.Hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
||||||
if stored != root {
|
if stored != root {
|
||||||
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
||||||
}
|
}
|
||||||
|
|
@ -467,11 +544,6 @@ func (db *Database) SetBufferSize(size int) error {
|
||||||
return db.tree.bottom().setBufferSize(db.bufferSize)
|
return db.tree.bottom().setBufferSize(db.bufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheme returns the node scheme used in the database.
|
|
||||||
func (db *Database) Scheme() string {
|
|
||||||
return rawdb.PathScheme
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifyAllowed returns the indicator if mutation is allowed. This function
|
// modifyAllowed returns the indicator if mutation is allowed. This function
|
||||||
// assumes the db.lock is already held.
|
// assumes the db.lock is already held.
|
||||||
func (db *Database) modifyAllowed() error {
|
func (db *Database) modifyAllowed() error {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ func newTester(t *testing.T, historyLimit uint64) *tester {
|
||||||
StateHistory: historyLimit,
|
StateHistory: historyLimit,
|
||||||
CleanCacheSize: 256 * 1024,
|
CleanCacheSize: 256 * 1024,
|
||||||
DirtyCacheSize: 256 * 1024,
|
DirtyCacheSize: 256 * 1024,
|
||||||
})
|
}, false)
|
||||||
obj = &tester{
|
obj = &tester{
|
||||||
db: db,
|
db: db,
|
||||||
preimages: make(map[common.Hash]common.Address),
|
preimages: make(map[common.Hash]common.Address),
|
||||||
|
|
@ -447,7 +447,7 @@ func TestDisable(t *testing.T) {
|
||||||
tester := newTester(t, 0)
|
tester := newTester(t, 0)
|
||||||
defer tester.release()
|
defer tester.release()
|
||||||
|
|
||||||
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil))
|
||||||
if err := tester.db.Disable(); err != nil {
|
if err := tester.db.Disable(); err != nil {
|
||||||
t.Fatal("Failed to deactivate database")
|
t.Fatal("Failed to deactivate database")
|
||||||
}
|
}
|
||||||
|
|
@ -511,7 +511,7 @@ func TestJournal(t *testing.T) {
|
||||||
t.Errorf("Failed to journal, err: %v", err)
|
t.Errorf("Failed to journal, err: %v", err)
|
||||||
}
|
}
|
||||||
tester.db.Close()
|
tester.db.Close()
|
||||||
tester.db = New(tester.db.diskdb, nil)
|
tester.db = New(tester.db.diskdb, nil, false)
|
||||||
|
|
||||||
// Verify states including disk layer and all diff on top.
|
// Verify states including disk layer and all diff on top.
|
||||||
for i := 0; i < len(tester.roots); i++ {
|
for i := 0; i < len(tester.roots); i++ {
|
||||||
|
|
@ -535,7 +535,9 @@ func TestCorruptedJournal(t *testing.T) {
|
||||||
t.Errorf("Failed to journal, err: %v", err)
|
t.Errorf("Failed to journal, err: %v", err)
|
||||||
}
|
}
|
||||||
tester.db.Close()
|
tester.db.Close()
|
||||||
_, root := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
|
||||||
|
rootBlob := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
||||||
|
root := crypto.Keccak256Hash(rootBlob)
|
||||||
|
|
||||||
// Mutate the journal in disk, it should be regarded as invalid
|
// Mutate the journal in disk, it should be regarded as invalid
|
||||||
blob := rawdb.ReadTrieJournal(tester.db.diskdb)
|
blob := rawdb.ReadTrieJournal(tester.db.diskdb)
|
||||||
|
|
@ -543,7 +545,7 @@ func TestCorruptedJournal(t *testing.T) {
|
||||||
rawdb.WriteTrieJournal(tester.db.diskdb, blob)
|
rawdb.WriteTrieJournal(tester.db.diskdb, blob)
|
||||||
|
|
||||||
// Verify states, all not-yet-written states should be discarded
|
// Verify states, all not-yet-written states should be discarded
|
||||||
tester.db = New(tester.db.diskdb, nil)
|
tester.db = New(tester.db.diskdb, nil, false)
|
||||||
for i := 0; i < len(tester.roots); i++ {
|
for i := 0; i < len(tester.roots); i++ {
|
||||||
if tester.roots[i] == root {
|
if tester.roots[i] == root {
|
||||||
if err := tester.verifyState(root); err != nil {
|
if err := tester.verifyState(root); err != nil {
|
||||||
|
|
@ -574,7 +576,7 @@ func TestTailTruncateHistory(t *testing.T) {
|
||||||
defer tester.release()
|
defer tester.release()
|
||||||
|
|
||||||
tester.db.Close()
|
tester.db.Close()
|
||||||
tester.db = New(tester.db.diskdb, &Config{StateHistory: 10})
|
tester.db = New(tester.db.diskdb, &Config{StateHistory: 10}, false)
|
||||||
|
|
||||||
head, err := tester.db.freezer.Ancients()
|
head, err := tester.db.freezer.Ancients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ type diffLayer struct {
|
||||||
root common.Hash // Root hash to which this layer diff belongs to
|
root common.Hash // Root hash to which this layer diff belongs to
|
||||||
id uint64 // Corresponding state id
|
id uint64 // Corresponding state id
|
||||||
block uint64 // Associated block number
|
block uint64 // Associated block number
|
||||||
nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path
|
nodes map[common.Hash]map[string][]byte // Cached trie nodes indexed by owner and path
|
||||||
states *triestate.Set // Associated state change set for building history
|
states *triestate.Set // Associated state change set for building history
|
||||||
memory uint64 // Approximate guess as to how much memory we use
|
memory uint64 // Approximate guess as to how much memory we use
|
||||||
|
|
||||||
|
|
@ -45,7 +44,7 @@ type diffLayer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDiffLayer creates a new diff layer on top of an existing layer.
|
// newDiffLayer creates a new diff layer on top of an existing layer.
|
||||||
func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer {
|
func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string][]byte, states *triestate.Set) *diffLayer {
|
||||||
var (
|
var (
|
||||||
size int64
|
size int64
|
||||||
count int
|
count int
|
||||||
|
|
@ -60,11 +59,11 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
|
||||||
}
|
}
|
||||||
for _, subset := range nodes {
|
for _, subset := range nodes {
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
dl.memory += uint64(n.Size() + len(path))
|
size += int64(len(n) + len(path))
|
||||||
size += int64(len(n.Blob) + len(path))
|
|
||||||
}
|
}
|
||||||
count += len(subset)
|
count += len(subset)
|
||||||
}
|
}
|
||||||
|
dl.memory = uint64(size)
|
||||||
if states != nil {
|
if states != nil {
|
||||||
dl.memory += uint64(states.Size())
|
dl.memory += uint64(states.Size())
|
||||||
}
|
}
|
||||||
|
|
@ -95,10 +94,9 @@ func (dl *diffLayer) parentLayer() layer {
|
||||||
return dl.parent
|
return dl.parent
|
||||||
}
|
}
|
||||||
|
|
||||||
// node retrieves the node with provided node information. It's the internal
|
// node implements the layer interface, retrieving the trie node blob with the
|
||||||
// version of Node function with additional accessed layer tracked. No error
|
// provided node information. No error will be returned if the node is not found.
|
||||||
// will be returned if node is not found.
|
func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, *nodeLoc, error) {
|
||||||
func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, depth int) ([]byte, error) {
|
|
||||||
// Hold the lock, ensure the parent won't be changed during the
|
// Hold the lock, ensure the parent won't be changed during the
|
||||||
// state accessing.
|
// state accessing.
|
||||||
dl.lock.RLock()
|
dl.lock.RLock()
|
||||||
|
|
@ -109,36 +107,19 @@ func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, dept
|
||||||
if ok {
|
if ok {
|
||||||
n, ok := subset[string(path)]
|
n, ok := subset[string(path)]
|
||||||
if ok {
|
if ok {
|
||||||
// If the trie node is not hash matched, or marked as removed,
|
|
||||||
// bubble up an error here. It shouldn't happen at all.
|
|
||||||
if n.Hash != hash {
|
|
||||||
dirtyFalseMeter.Mark(1)
|
|
||||||
log.Error("Unexpected trie node in diff layer", "owner", owner, "path", path, "expect", hash, "got", n.Hash)
|
|
||||||
return nil, newUnexpectedNodeError("diff", hash, n.Hash, owner, path, n.Blob)
|
|
||||||
}
|
|
||||||
dirtyHitMeter.Mark(1)
|
dirtyHitMeter.Mark(1)
|
||||||
dirtyNodeHitDepthHist.Update(int64(depth))
|
dirtyNodeHitDepthHist.Update(int64(depth))
|
||||||
dirtyReadMeter.Mark(int64(len(n.Blob)))
|
dirtyReadMeter.Mark(int64(len(n)))
|
||||||
return n.Blob, nil
|
return n, &nodeLoc{loc: locDiffLayer, depth: depth}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Trie node unknown to this layer, resolve from parent
|
// Trie node unknown to this layer, resolve from parent
|
||||||
if diff, ok := dl.parent.(*diffLayer); ok {
|
return dl.parent.node(owner, path, depth+1)
|
||||||
return diff.node(owner, path, hash, depth+1)
|
|
||||||
}
|
|
||||||
// Failed to resolve through diff layers, fallback to disk layer
|
|
||||||
return dl.parent.Node(owner, path, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node implements the layer interface, retrieving the trie node blob with the
|
|
||||||
// provided node information. No error will be returned if the node is not found.
|
|
||||||
func (dl *diffLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
|
|
||||||
return dl.node(owner, path, hash, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update implements the layer interface, creating a new layer on top of the
|
// update implements the layer interface, creating a new layer on top of the
|
||||||
// existing layer tree with the specified data items.
|
// existing layer tree with the specified data items.
|
||||||
func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer {
|
func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string][]byte, states *triestate.Set) *diffLayer {
|
||||||
return newDiffLayer(dl, root, id, block, nodes, states)
|
return newDiffLayer(dl, root, id, block, nodes, states)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/trie/testutil"
|
"github.com/ethereum/go-ethereum/trie/testutil"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func emptyLayer() *diskLayer {
|
func emptyLayer() *diskLayer {
|
||||||
return &diskLayer{
|
return &diskLayer{
|
||||||
db: New(rawdb.NewMemoryDatabase(), nil),
|
db: New(rawdb.NewMemoryDatabase(), nil, false),
|
||||||
buffer: newNodeBuffer(DefaultBufferSize, nil, 0),
|
buffer: newNodeBuffer(DefaultBufferSize, nil, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,23 +56,21 @@ func BenchmarkSearch1Layer(b *testing.B) { benchmarkSearch(b, 127, 128) }
|
||||||
func benchmarkSearch(b *testing.B, depth int, total int) {
|
func benchmarkSearch(b *testing.B, depth int, total int) {
|
||||||
var (
|
var (
|
||||||
npath []byte
|
npath []byte
|
||||||
nhash common.Hash
|
|
||||||
nblob []byte
|
nblob []byte
|
||||||
)
|
)
|
||||||
// First, we set up 128 diff layers, with 3K items each
|
// First, we set up 128 diff layers, with 3K items each
|
||||||
fill := func(parent layer, index int) *diffLayer {
|
fill := func(parent layer, index int) *diffLayer {
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
nodes[common.Hash{}] = make(map[string]*trienode.Node)
|
nodes[common.Hash{}] = make(map[string][]byte)
|
||||||
for i := 0; i < 3000; i++ {
|
for i := 0; i < 3000; i++ {
|
||||||
var (
|
var (
|
||||||
path = testutil.RandBytes(32)
|
path = testutil.RandBytes(32)
|
||||||
node = testutil.RandomNode()
|
blob = testutil.RandBytes(100)
|
||||||
)
|
)
|
||||||
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
|
nodes[common.Hash{}][string(path)] = blob
|
||||||
if npath == nil && depth == index {
|
if npath == nil && depth == index {
|
||||||
npath = common.CopyBytes(path)
|
npath = common.CopyBytes(path)
|
||||||
nblob = common.CopyBytes(node.Blob)
|
nblob = common.CopyBytes(blob)
|
||||||
nhash = node.Hash
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
||||||
|
|
@ -90,7 +87,7 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
have, err = layer.Node(common.Hash{}, npath, nhash)
|
have, _, err = layer.node(common.Hash{}, npath, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -108,14 +105,14 @@ func benchmarkSearch(b *testing.B, depth int, total int) {
|
||||||
func BenchmarkPersist(b *testing.B) {
|
func BenchmarkPersist(b *testing.B) {
|
||||||
// First, we set up 128 diff layers, with 3K items each
|
// First, we set up 128 diff layers, with 3K items each
|
||||||
fill := func(parent layer) *diffLayer {
|
fill := func(parent layer) *diffLayer {
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
nodes[common.Hash{}] = make(map[string]*trienode.Node)
|
nodes[common.Hash{}] = make(map[string][]byte)
|
||||||
for i := 0; i < 3000; i++ {
|
for i := 0; i < 3000; i++ {
|
||||||
var (
|
var (
|
||||||
path = testutil.RandBytes(32)
|
path = testutil.RandBytes(32)
|
||||||
node = testutil.RandomNode()
|
blob = testutil.RandBytes(100)
|
||||||
)
|
)
|
||||||
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
|
nodes[common.Hash{}][string(path)] = blob
|
||||||
}
|
}
|
||||||
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -145,14 +142,14 @@ func BenchmarkJournal(b *testing.B) {
|
||||||
|
|
||||||
// First, we set up 128 diff layers, with 3K items each
|
// First, we set up 128 diff layers, with 3K items each
|
||||||
fill := func(parent layer) *diffLayer {
|
fill := func(parent layer) *diffLayer {
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
nodes[common.Hash{}] = make(map[string]*trienode.Node)
|
nodes[common.Hash{}] = make(map[string][]byte)
|
||||||
for i := 0; i < 3000; i++ {
|
for i := 0; i < 3000; i++ {
|
||||||
var (
|
var (
|
||||||
path = testutil.RandBytes(32)
|
path = testutil.RandBytes(32)
|
||||||
node = testutil.RandomNode()
|
blob = testutil.RandBytes(100)
|
||||||
)
|
)
|
||||||
nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
|
nodes[common.Hash{}][string(path)] = blob
|
||||||
}
|
}
|
||||||
// TODO(rjl493456442) a non-nil state set is expected.
|
// TODO(rjl493456442) a non-nil state set is expected.
|
||||||
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,8 @@ import (
|
||||||
"github.com/VictoriaMetrics/fastcache"
|
"github.com/VictoriaMetrics/fastcache"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
"golang.org/x/crypto/sha3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// diskLayer is a low level persistent layer built on top of a key-value store.
|
// diskLayer is a low level persistent layer built on top of a key-value store.
|
||||||
|
|
@ -95,27 +92,25 @@ func (dl *diskLayer) markStale() {
|
||||||
dl.stale = true
|
dl.stale = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node implements the layer interface, retrieving the trie node with the
|
// node implements the layer interface, retrieving the trie node with the
|
||||||
// provided node info. No error will be returned if the node is not found.
|
// provided node info. No error will be returned if the node is not found.
|
||||||
func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
|
func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, *nodeLoc, error) {
|
||||||
dl.lock.RLock()
|
dl.lock.RLock()
|
||||||
defer dl.lock.RUnlock()
|
defer dl.lock.RUnlock()
|
||||||
|
|
||||||
if dl.stale {
|
if dl.stale {
|
||||||
return nil, errSnapshotStale
|
return nil, nil, errSnapshotStale
|
||||||
}
|
}
|
||||||
// Try to retrieve the trie node from the not-yet-written
|
// Try to retrieve the trie node from the not-yet-written
|
||||||
// node buffer first. Note the buffer is lock free since
|
// node buffer first. Note the buffer is lock free since
|
||||||
// it's impossible to mutate the buffer before tagging the
|
// it's impossible to mutate the buffer before tagging the
|
||||||
// layer as stale.
|
// layer as stale.
|
||||||
n, err := dl.buffer.node(owner, path, hash)
|
n, found := dl.buffer.node(owner, path)
|
||||||
if err != nil {
|
if found {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n != nil {
|
|
||||||
dirtyHitMeter.Mark(1)
|
dirtyHitMeter.Mark(1)
|
||||||
dirtyReadMeter.Mark(int64(len(n.Blob)))
|
dirtyReadMeter.Mark(int64(len(n)))
|
||||||
return n.Blob, nil
|
dirtyNodeHitDepthHist.Update(int64(depth))
|
||||||
|
return n, &nodeLoc{loc: locDirtyCache, depth: depth}, nil
|
||||||
}
|
}
|
||||||
dirtyMissMeter.Mark(1)
|
dirtyMissMeter.Mark(1)
|
||||||
|
|
||||||
|
|
@ -123,45 +118,31 @@ func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]b
|
||||||
key := cacheKey(owner, path)
|
key := cacheKey(owner, path)
|
||||||
if dl.cleans != nil {
|
if dl.cleans != nil {
|
||||||
if blob := dl.cleans.Get(nil, key); len(blob) > 0 {
|
if blob := dl.cleans.Get(nil, key); len(blob) > 0 {
|
||||||
h := newHasher()
|
|
||||||
defer h.release()
|
|
||||||
|
|
||||||
got := h.hash(blob)
|
|
||||||
if got == hash {
|
|
||||||
cleanHitMeter.Mark(1)
|
cleanHitMeter.Mark(1)
|
||||||
cleanReadMeter.Mark(int64(len(blob)))
|
cleanReadMeter.Mark(int64(len(blob)))
|
||||||
return blob, nil
|
dirtyNodeHitDepthHist.Update(int64(depth))
|
||||||
}
|
return blob, &nodeLoc{loc: locCleanCache, depth: depth}, nil
|
||||||
cleanFalseMeter.Mark(1)
|
|
||||||
log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got)
|
|
||||||
}
|
}
|
||||||
cleanMissMeter.Mark(1)
|
cleanMissMeter.Mark(1)
|
||||||
}
|
}
|
||||||
// Try to retrieve the trie node from the disk.
|
// Try to retrieve the trie node from the disk.
|
||||||
var (
|
var blob []byte
|
||||||
nBlob []byte
|
|
||||||
nHash common.Hash
|
|
||||||
)
|
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
|
blob = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
|
||||||
} else {
|
} else {
|
||||||
nBlob, nHash = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
|
blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
|
||||||
}
|
}
|
||||||
if nHash != hash {
|
if dl.cleans != nil && len(blob) > 0 {
|
||||||
diskFalseMeter.Mark(1)
|
dl.cleans.Set(key, blob)
|
||||||
log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash)
|
cleanWriteMeter.Mark(int64(len(blob)))
|
||||||
return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path, nBlob)
|
dirtyNodeHitDepthHist.Update(int64(depth))
|
||||||
}
|
}
|
||||||
if dl.cleans != nil && len(nBlob) > 0 {
|
return blob, &nodeLoc{loc: locDisk, depth: depth}, nil
|
||||||
dl.cleans.Set(key, nBlob)
|
|
||||||
cleanWriteMeter.Mark(int64(len(nBlob)))
|
|
||||||
}
|
|
||||||
return nBlob, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update implements the layer interface, returning a new diff layer on top
|
// update implements the layer interface, returning a new diff layer on top
|
||||||
// with the given state set.
|
// with the given state set.
|
||||||
func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer {
|
func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string][]byte, states *triestate.Set) *diffLayer {
|
||||||
return newDiffLayer(dl, root, id, block, nodes, states)
|
return newDiffLayer(dl, root, id, block, nodes, states)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,22 +298,3 @@ func (dl *diskLayer) resetCache() {
|
||||||
dl.cleans.Reset()
|
dl.cleans.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasher is used to compute the sha256 hash of the provided data.
|
|
||||||
type hasher struct{ sha crypto.KeccakState }
|
|
||||||
|
|
||||||
var hasherPool = sync.Pool{
|
|
||||||
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHasher() *hasher {
|
|
||||||
return hasherPool.Get().(*hasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hasher) hash(data []byte) common.Hash {
|
|
||||||
return crypto.HashData(h.sha, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *hasher) release() {
|
|
||||||
hasherPool.Put(h)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,6 @@ package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -45,16 +41,4 @@ var (
|
||||||
// errStateUnrecoverable is returned if state is required to be reverted to
|
// errStateUnrecoverable is returned if state is required to be reverted to
|
||||||
// a destination without associated state history available.
|
// a destination without associated state history available.
|
||||||
errStateUnrecoverable = errors.New("state is unrecoverable")
|
errStateUnrecoverable = errors.New("state is unrecoverable")
|
||||||
|
|
||||||
// errUnexpectedNode is returned if the requested node with specified path is
|
|
||||||
// not hash matched with expectation.
|
|
||||||
errUnexpectedNode = errors.New("unexpected node")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUnexpectedNodeError(loc string, expHash common.Hash, gotHash common.Hash, owner common.Hash, path []byte, blob []byte) error {
|
|
||||||
blobHex := "nil"
|
|
||||||
if len(blob) > 0 {
|
|
||||||
blobHex = hexutil.Encode(blob)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w, loc: %s, node: (%x %v), %x!=%x, blob: %s", errUnexpectedNode, loc, owner, path, expHash, gotHash, blobHex)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
51
trie/triedb/pathdb/hasher.go
Normal file
51
trie/triedb/pathdb/hasher.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package pathdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasher is used to compute the sha256 hash of the provided data.
|
||||||
|
type hasher struct{ sha crypto.KeccakState }
|
||||||
|
|
||||||
|
var hasherPool = sync.Pool{
|
||||||
|
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHasher() *hasher {
|
||||||
|
return hasherPool.Get().(*hasher)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hasher) hash(data []byte) common.Hash {
|
||||||
|
return crypto.HashData(h.sha, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hasher) release() {
|
||||||
|
hasherPool.Put(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashNode(node []byte) common.Hash {
|
||||||
|
h := newHasher()
|
||||||
|
defer h.release()
|
||||||
|
|
||||||
|
return h.hash(node)
|
||||||
|
}
|
||||||
|
|
@ -270,7 +270,7 @@ func TestTruncateOutOfRange(t *testing.T) {
|
||||||
|
|
||||||
// openFreezer initializes the freezer instance for storing state histories.
|
// openFreezer initializes the freezer instance for storing state histories.
|
||||||
func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) {
|
func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) {
|
||||||
return rawdb.NewStateFreezer(datadir, readOnly)
|
return rawdb.NewStateFreezer(datadir, false, readOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareSet[k comparable](a, b map[k][]byte) bool {
|
func compareSet[k comparable](a, b map[k][]byte) bool {
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -115,9 +113,10 @@ func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
|
||||||
// loadLayers loads a pre-existing state layer backed by a key-value store.
|
// loadLayers loads a pre-existing state layer backed by a key-value store.
|
||||||
func (db *Database) loadLayers() layer {
|
func (db *Database) loadLayers() layer {
|
||||||
// Retrieve the root node of persistent state.
|
// Retrieve the root node of persistent state.
|
||||||
_, root := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
var root = types.EmptyRootHash
|
||||||
root = types.TrieRootHash(root)
|
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||||
|
root = db.config.Hasher(blob)
|
||||||
|
}
|
||||||
// Load the layers by resolving the journal
|
// Load the layers by resolving the journal
|
||||||
head, err := db.loadJournal(root)
|
head, err := db.loadJournal(root)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -157,14 +156,14 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
|
||||||
if err := r.Decode(&encoded); err != nil {
|
if err := r.Decode(&encoded); err != nil {
|
||||||
return nil, fmt.Errorf("load disk nodes: %v", err)
|
return nil, fmt.Errorf("load disk nodes: %v", err)
|
||||||
}
|
}
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
for _, entry := range encoded {
|
for _, entry := range encoded {
|
||||||
subset := make(map[string]*trienode.Node)
|
subset := make(map[string][]byte)
|
||||||
for _, n := range entry.Nodes {
|
for _, n := range entry.Nodes {
|
||||||
if len(n.Blob) > 0 {
|
if len(n.Blob) > 0 {
|
||||||
subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
|
subset[string(n.Path)] = n.Blob
|
||||||
} else {
|
} else {
|
||||||
subset[string(n.Path)] = trienode.NewDeleted()
|
subset[string(n.Path)] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes[entry.Owner] = subset
|
nodes[entry.Owner] = subset
|
||||||
|
|
@ -195,14 +194,14 @@ func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) {
|
||||||
if err := r.Decode(&encoded); err != nil {
|
if err := r.Decode(&encoded); err != nil {
|
||||||
return nil, fmt.Errorf("load diff nodes: %v", err)
|
return nil, fmt.Errorf("load diff nodes: %v", err)
|
||||||
}
|
}
|
||||||
nodes := make(map[common.Hash]map[string]*trienode.Node)
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
for _, entry := range encoded {
|
for _, entry := range encoded {
|
||||||
subset := make(map[string]*trienode.Node)
|
subset := make(map[string][]byte)
|
||||||
for _, n := range entry.Nodes {
|
for _, n := range entry.Nodes {
|
||||||
if len(n.Blob) > 0 {
|
if len(n.Blob) > 0 {
|
||||||
subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
|
subset[string(n.Path)] = n.Blob
|
||||||
} else {
|
} else {
|
||||||
subset[string(n.Path)] = trienode.NewDeleted()
|
subset[string(n.Path)] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes[entry.Owner] = subset
|
nodes[entry.Owner] = subset
|
||||||
|
|
@ -264,7 +263,7 @@ func (dl *diskLayer) journal(w io.Writer) error {
|
||||||
for owner, subset := range dl.buffer.nodes {
|
for owner, subset := range dl.buffer.nodes {
|
||||||
entry := journalNodes{Owner: owner}
|
entry := journalNodes{Owner: owner}
|
||||||
for path, node := range subset {
|
for path, node := range subset {
|
||||||
entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob})
|
entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node})
|
||||||
}
|
}
|
||||||
nodes = append(nodes, entry)
|
nodes = append(nodes, entry)
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +296,7 @@ func (dl *diffLayer) journal(w io.Writer) error {
|
||||||
for owner, subset := range dl.nodes {
|
for owner, subset := range dl.nodes {
|
||||||
entry := journalNodes{Owner: owner}
|
entry := journalNodes{Owner: owner}
|
||||||
for path, node := range subset {
|
for path, node := range subset {
|
||||||
entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob})
|
entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node})
|
||||||
}
|
}
|
||||||
nodes = append(nodes, entry)
|
nodes = append(nodes, entry)
|
||||||
}
|
}
|
||||||
|
|
@ -363,14 +362,13 @@ func (db *Database) Journal(root common.Hash) error {
|
||||||
if err := rlp.Encode(journal, journalVersion); err != nil {
|
if err := rlp.Encode(journal, journalVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// The stored state in disk might be empty, convert the
|
|
||||||
// root to emptyRoot in this case.
|
|
||||||
_, diskroot := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
|
||||||
diskroot = types.TrieRootHash(diskroot)
|
|
||||||
|
|
||||||
// Secondly write out the state root in disk, ensure all layers
|
// Secondly write out the state root in disk, ensure all layers
|
||||||
// on top are continuous with disk.
|
// on top are continuous with disk.
|
||||||
if err := rlp.Encode(journal, diskroot); err != nil {
|
diskRoot := types.EmptyRootHash
|
||||||
|
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||||
|
diskRoot = db.config.Hasher(blob)
|
||||||
|
}
|
||||||
|
if err := rlp.Encode(journal, diskRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Finally write out the journal of each layer in reverse order.
|
// Finally write out the journal of each layer in reverse order.
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot)
|
return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot)
|
||||||
}
|
}
|
||||||
l := parent.update(root, parent.stateID()+1, block, nodes.Flatten(), states)
|
l := parent.update(root, parent.stateID()+1, block, nodes.Slim(), states)
|
||||||
|
|
||||||
tree.lock.Lock()
|
tree.lock.Lock()
|
||||||
tree.layers[l.rootHash()] = l
|
tree.layers[l.rootHash()] = l
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ var (
|
||||||
cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil)
|
cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil)
|
||||||
dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil)
|
dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil)
|
||||||
diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil)
|
diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil)
|
||||||
|
diffFalseMeter = metrics.NewRegisteredMeter("pathdb/diff/false", nil)
|
||||||
|
|
||||||
commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil)
|
commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil)
|
||||||
commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil)
|
commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil)
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,15 @@
|
||||||
package pathdb
|
package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/fastcache"
|
"github.com/VictoriaMetrics/fastcache"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// nodebuffer is a collection of modified trie nodes to aggregate the disk
|
// nodebuffer is a collection of modified trie nodes to aggregate the disk
|
||||||
|
|
@ -36,18 +35,18 @@ type nodebuffer struct {
|
||||||
layers uint64 // The number of diff layers aggregated inside
|
layers uint64 // The number of diff layers aggregated inside
|
||||||
size uint64 // The size of aggregated writes
|
size uint64 // The size of aggregated writes
|
||||||
limit uint64 // The maximum memory allowance in bytes
|
limit uint64 // The maximum memory allowance in bytes
|
||||||
nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path
|
nodes map[common.Hash]map[string][]byte // The dirty node set, mapped by owner and path
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNodeBuffer initializes the node buffer with the provided nodes.
|
// newNodeBuffer initializes the node buffer with the provided nodes.
|
||||||
func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer {
|
func newNodeBuffer(limit int, nodes map[common.Hash]map[string][]byte, layers uint64) *nodebuffer {
|
||||||
if nodes == nil {
|
if nodes == nil {
|
||||||
nodes = make(map[common.Hash]map[string]*trienode.Node)
|
nodes = make(map[common.Hash]map[string][]byte)
|
||||||
}
|
}
|
||||||
var size uint64
|
var size uint64
|
||||||
for _, subset := range nodes {
|
for _, subset := range nodes {
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
size += uint64(len(n.Blob) + len(path))
|
size += uint64(len(n) + len(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &nodebuffer{
|
return &nodebuffer{
|
||||||
|
|
@ -59,28 +58,22 @@ func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, l
|
||||||
}
|
}
|
||||||
|
|
||||||
// node retrieves the trie node with given node info.
|
// node retrieves the trie node with given node info.
|
||||||
func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*trienode.Node, error) {
|
func (b *nodebuffer) node(owner common.Hash, path []byte) ([]byte, bool) {
|
||||||
subset, ok := b.nodes[owner]
|
subset, ok := b.nodes[owner]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, false
|
||||||
}
|
}
|
||||||
n, ok := subset[string(path)]
|
n, ok := subset[string(path)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, false
|
||||||
}
|
}
|
||||||
if n.Hash != hash {
|
return n, true
|
||||||
dirtyFalseMeter.Mark(1)
|
|
||||||
log.Error("Unexpected trie node in node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash)
|
|
||||||
return nil, newUnexpectedNodeError("dirty", hash, n.Hash, owner, path, n.Blob)
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit merges the dirty nodes into the nodebuffer. This operation won't take
|
// commit merges the dirty nodes into the nodebuffer. This operation won't take
|
||||||
// the ownership of the nodes map which belongs to the bottom-most diff layer.
|
// the ownership of the nodes map which belongs to the bottom-most diff layer.
|
||||||
// It will just hold the node references from the given map which are safe to
|
// It will hold the node references from the given map which are safe to copy.
|
||||||
// copy.
|
func (b *nodebuffer) commit(nodes map[common.Hash]map[string][]byte) *nodebuffer {
|
||||||
func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer {
|
|
||||||
var (
|
var (
|
||||||
delta int64
|
delta int64
|
||||||
overwrite int64
|
overwrite int64
|
||||||
|
|
@ -94,21 +87,21 @@ func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *no
|
||||||
// The nodes belong to original diff layer are still accessible even
|
// The nodes belong to original diff layer are still accessible even
|
||||||
// after merging, thus the ownership of nodes map should still belong
|
// after merging, thus the ownership of nodes map should still belong
|
||||||
// to original layer and any mutation on it should be prevented.
|
// to original layer and any mutation on it should be prevented.
|
||||||
current = make(map[string]*trienode.Node)
|
current = make(map[string][]byte)
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
current[path] = n
|
current[path] = n
|
||||||
delta += int64(len(n.Blob) + len(path))
|
delta += int64(len(n) + len(path))
|
||||||
}
|
}
|
||||||
b.nodes[owner] = current
|
b.nodes[owner] = current
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
if orig, exist := current[path]; !exist {
|
if orig, exist := current[path]; !exist {
|
||||||
delta += int64(len(n.Blob) + len(path))
|
delta += int64(len(n) + len(path))
|
||||||
} else {
|
} else {
|
||||||
delta += int64(len(n.Blob) - len(orig.Blob))
|
delta += int64(len(n) - len(orig))
|
||||||
overwrite++
|
overwrite++
|
||||||
overwriteSize += int64(len(orig.Blob) + len(path))
|
overwriteSize += int64(len(orig) + len(path))
|
||||||
}
|
}
|
||||||
current[path] = n
|
current[path] = n
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +117,7 @@ func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *no
|
||||||
// revert is the reverse operation of commit. It also merges the provided nodes
|
// revert is the reverse operation of commit. It also merges the provided nodes
|
||||||
// into the nodebuffer, the difference is that the provided node set should
|
// into the nodebuffer, the difference is that the provided node set should
|
||||||
// revert the changes made by the last state transition.
|
// revert the changes made by the last state transition.
|
||||||
func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error {
|
func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string][]byte) error {
|
||||||
// Short circuit if no embedded state transition to revert.
|
// Short circuit if no embedded state transition to revert.
|
||||||
if b.layers == 0 {
|
if b.layers == 0 {
|
||||||
return errStateUnrecoverable
|
return errStateUnrecoverable
|
||||||
|
|
@ -153,20 +146,20 @@ func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[s
|
||||||
//
|
//
|
||||||
// In case of database rollback, don't panic if this "clean"
|
// In case of database rollback, don't panic if this "clean"
|
||||||
// node occurs which is not present in buffer.
|
// node occurs which is not present in buffer.
|
||||||
var nhash common.Hash
|
var blob []byte
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
_, nhash = rawdb.ReadAccountTrieNode(db, []byte(path))
|
blob = rawdb.ReadAccountTrieNode(db, []byte(path))
|
||||||
} else {
|
} else {
|
||||||
_, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
|
blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
|
||||||
}
|
}
|
||||||
// Ignore the clean node in the case described above.
|
// Ignore the clean node in the case described above.
|
||||||
if nhash == n.Hash {
|
if bytes.Equal(blob, n) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
|
panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, n))
|
||||||
}
|
}
|
||||||
current[path] = n
|
current[path] = n
|
||||||
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
|
delta += int64(len(n)) - int64(len(orig))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.updateSize(delta)
|
b.updateSize(delta)
|
||||||
|
|
@ -189,7 +182,7 @@ func (b *nodebuffer) updateSize(delta int64) {
|
||||||
func (b *nodebuffer) reset() {
|
func (b *nodebuffer) reset() {
|
||||||
b.layers = 0
|
b.layers = 0
|
||||||
b.size = 0
|
b.size = 0
|
||||||
b.nodes = make(map[common.Hash]map[string]*trienode.Node)
|
b.nodes = make(map[common.Hash]map[string][]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns an indicator if nodebuffer contains any state transition inside.
|
// empty returns an indicator if nodebuffer contains any state transition inside.
|
||||||
|
|
@ -238,10 +231,10 @@ func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id ui
|
||||||
// writeNodes writes the trie nodes into the provided database batch.
|
// writeNodes writes the trie nodes into the provided database batch.
|
||||||
// Note this function will also inject all the newly written nodes
|
// Note this function will also inject all the newly written nodes
|
||||||
// into clean cache.
|
// into clean cache.
|
||||||
func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) {
|
func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string][]byte, clean *fastcache.Cache) (total int) {
|
||||||
for owner, subset := range nodes {
|
for owner, subset := range nodes {
|
||||||
for path, n := range subset {
|
for path, n := range subset {
|
||||||
if n.IsDeleted() {
|
if len(n) == 0 {
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
rawdb.DeleteAccountTrieNode(batch, []byte(path))
|
rawdb.DeleteAccountTrieNode(batch, []byte(path))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -252,12 +245,12 @@ func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.No
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob)
|
rawdb.WriteAccountTrieNode(batch, []byte(path), n)
|
||||||
} else {
|
} else {
|
||||||
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob)
|
rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n)
|
||||||
}
|
}
|
||||||
if clean != nil {
|
if clean != nil {
|
||||||
clean.Set(cacheKey(owner, []byte(path)), n.Blob)
|
clean.Set(cacheKey(owner, []byte(path)), n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,10 @@ import (
|
||||||
// node hash. It is general enough that can be used to represent trie node
|
// node hash. It is general enough that can be used to represent trie node
|
||||||
// corresponding to different trie implementations.
|
// corresponding to different trie implementations.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Hash common.Hash // Node hash, empty for deleted node
|
Hash common.Hash // Node hash, empty for deleted node or verkle node
|
||||||
Blob []byte // Encoded node blob, nil for the deleted node
|
Blob []byte // Encoded node blob, nil for the deleted node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the total memory size used by this node.
|
|
||||||
func (n *Node) Size() int {
|
|
||||||
return len(n.Blob) + common.HashLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDeleted returns the indicator if the node is marked as deleted.
|
// IsDeleted returns the indicator if the node is marked as deleted.
|
||||||
func (n *Node) IsDeleted() bool {
|
func (n *Node) IsDeleted() bool {
|
||||||
return len(n.Blob) == 0
|
return len(n.Blob) == 0
|
||||||
|
|
@ -189,11 +184,17 @@ func (set *MergedNodeSet) Merge(other *NodeSet) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten returns a two-dimensional map for internal nodes.
|
// Slim creates a node map by assembling nodes from the given set while excluding
|
||||||
func (set *MergedNodeSet) Flatten() map[common.Hash]map[string]*Node {
|
// any other fields present in the MergedNodeSet. The node blobs are referenced
|
||||||
nodes := make(map[common.Hash]map[string]*Node)
|
// directly without deep-copied due to the fact that they are immutable.
|
||||||
|
func (set *MergedNodeSet) Slim() map[common.Hash]map[string][]byte {
|
||||||
|
nodes := make(map[common.Hash]map[string][]byte)
|
||||||
for owner, set := range set.Sets {
|
for owner, set := range set.Sets {
|
||||||
nodes[owner] = set.Nodes
|
subset := make(map[string][]byte)
|
||||||
|
for path, node := range set.Nodes {
|
||||||
|
subset[path] = node.Blob
|
||||||
|
}
|
||||||
|
nodes[owner] = subset
|
||||||
}
|
}
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ type context struct {
|
||||||
// Apply traverses the provided state diffs, apply them in the associated
|
// Apply traverses the provided state diffs, apply them in the associated
|
||||||
// post-state and return the generated dirty trie nodes. The state can be
|
// post-state and return the generated dirty trie nodes. The state can be
|
||||||
// loaded via the provided trie loader.
|
// loaded via the provided trie loader.
|
||||||
func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string]*trienode.Node, error) {
|
func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string][]byte, error) {
|
||||||
tr, err := loader.OpenTrie(postRoot)
|
tr, err := loader.OpenTrie(postRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -139,7 +139,7 @@ func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Addre
|
||||||
if err := ctx.nodes.Merge(result); err != nil {
|
if err := ctx.nodes.Merge(result); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ctx.nodes.Flatten(), nil
|
return ctx.nodes.Slim(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateAccount the account was present in prev-state, and may or may not
|
// updateAccount the account was present in prev-state, and may or may not
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue