triedb/pathdb: configure different node hasher in pathdb (#31008)
As the node hash scheme in verkle and merkle are totally different, the original default node hasher in pathdb is no longer suitable. Therefore, this pull request configures different node hasher respectively.
This commit is contained in:
parent
033de2a05b
commit
82e963e5c9
|
@ -127,8 +127,12 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
|
|||
}
|
||||
// Create an ephemeral in-memory database for computing hash,
|
||||
// all the derived states will be discarded to not pollute disk.
|
||||
emptyRoot := types.EmptyRootHash
|
||||
if isVerkle {
|
||||
emptyRoot = types.EmptyVerkleHash
|
||||
}
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil))
|
||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
@ -148,7 +152,11 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
|
|||
// flushAlloc is very similar with hash, but the main difference is all the
|
||||
// generated states will be persisted into the given database.
|
||||
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
|
||||
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil))
|
||||
emptyRoot := types.EmptyRootHash
|
||||
if triedb.IsVerkle() {
|
||||
emptyRoot = types.EmptyVerkleHash
|
||||
}
|
||||
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
@ -133,8 +132,8 @@ func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common
|
|||
}
|
||||
}
|
||||
return &stateUpdate{
|
||||
originRoot: types.TrieRootHash(originRoot),
|
||||
root: types.TrieRootHash(root),
|
||||
originRoot: originRoot,
|
||||
root: root,
|
||||
accounts: accounts,
|
||||
accountsOrigin: accountsOrigin,
|
||||
storages: storages,
|
||||
|
|
|
@ -19,7 +19,6 @@ package types
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -47,13 +46,3 @@ var (
|
|||
// EmptyVerkleHash is the known hash of an empty verkle trie.
|
||||
EmptyVerkleHash = common.Hash{}
|
||||
)
|
||||
|
||||
// TrieRootHash returns the hash itself if it's non-empty or the predefined
|
||||
// emptyHash one instead.
|
||||
func TrieRootHash(hash common.Hash) common.Hash {
|
||||
if hash == (common.Hash{}) {
|
||||
log.Error("Zero trie root hash!")
|
||||
return EmptyRootHash
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
@ -36,7 +37,7 @@ func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil
|
|||
|
||||
func TestStateOverrideMovePrecompile(t *testing.T) {
|
||||
db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
|
||||
statedb, err := state.New(common.Hash{}, db)
|
||||
statedb, err := state.New(types.EmptyRootHash, db)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create statedb: %v", err)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package trie
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
)
|
||||
|
||||
|
@ -34,9 +33,6 @@ type trieReader struct {
|
|||
// newTrieReader initializes the trie reader with the given node reader.
|
||||
func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) {
|
||||
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
|
||||
if stateRoot == (common.Hash{}) {
|
||||
log.Error("Zero state root hash!")
|
||||
}
|
||||
return &trieReader{owner: owner}, nil
|
||||
}
|
||||
reader, err := db.NodeReader(stateRoot)
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-verkle"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -148,6 +149,29 @@ var Defaults = &Config{
|
|||
// ReadOnly is the config in order to open database in read only mode.
|
||||
var ReadOnly = &Config{ReadOnly: true}
|
||||
|
||||
// nodeHasher is the function to compute the hash of supplied node blob.
|
||||
type nodeHasher func([]byte) (common.Hash, error)
|
||||
|
||||
// merkleNodeHasher computes the hash of the given merkle node.
|
||||
func merkleNodeHasher(blob []byte) (common.Hash, error) {
|
||||
if len(blob) == 0 {
|
||||
return types.EmptyRootHash, nil
|
||||
}
|
||||
return crypto.Keccak256Hash(blob), nil
|
||||
}
|
||||
|
||||
// verkleNodeHasher computes the hash of the given verkle node.
|
||||
func verkleNodeHasher(blob []byte) (common.Hash, error) {
|
||||
if len(blob) == 0 {
|
||||
return types.EmptyVerkleHash, nil
|
||||
}
|
||||
n, err := verkle.ParseNode(blob, 0)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return n.Commit().Bytes(), nil
|
||||
}
|
||||
|
||||
// Database is a multiple-layered structure for maintaining in-memory states
|
||||
// along with its dirty trie nodes. It consists of one persistent base layer
|
||||
// backed by a key-value store, on top of which arbitrarily many in-memory diff
|
||||
|
@ -164,9 +188,10 @@ type Database struct {
|
|||
// readOnly is the flag whether the mutation is allowed to be applied.
|
||||
// It will be set automatically when the database is journaled during
|
||||
// the shutdown to reject all following unexpected mutations.
|
||||
readOnly bool // Flag if database is opened in read only mode
|
||||
waitSync bool // Flag if database is deactivated due to initial state sync
|
||||
isVerkle bool // Flag if database is used for verkle tree
|
||||
readOnly bool // Flag if database is opened in read only mode
|
||||
waitSync bool // Flag if database is deactivated due to initial state sync
|
||||
isVerkle bool // Flag if database is used for verkle tree
|
||||
hasher nodeHasher // Trie node hasher
|
||||
|
||||
config *Config // Configuration for database
|
||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||
|
@ -184,19 +209,21 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
|
|||
}
|
||||
config = config.sanitize()
|
||||
|
||||
db := &Database{
|
||||
readOnly: config.ReadOnly,
|
||||
isVerkle: isVerkle,
|
||||
config: config,
|
||||
diskdb: diskdb,
|
||||
hasher: merkleNodeHasher,
|
||||
}
|
||||
// Establish a dedicated database namespace tailored for verkle-specific
|
||||
// data, ensuring the isolation of both verkle and merkle 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 isVerkle {
|
||||
diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
|
||||
}
|
||||
db := &Database{
|
||||
readOnly: config.ReadOnly,
|
||||
isVerkle: isVerkle,
|
||||
config: config,
|
||||
diskdb: diskdb,
|
||||
db.diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
|
||||
db.hasher = verkleNodeHasher
|
||||
}
|
||||
// Construct the layer tree by resolving the in-disk singleton state
|
||||
// and in-memory layer journal.
|
||||
|
@ -277,6 +304,8 @@ func (db *Database) repairHistory() error {
|
|||
//
|
||||
// The passed in maps(nodes, states) will be retained to avoid copying everything.
|
||||
// Therefore, these maps must not be changed afterwards.
|
||||
//
|
||||
// The supplied parentRoot and root must be a valid trie hash value.
|
||||
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *StateSetWithOrigin) error {
|
||||
// Hold the lock to prevent concurrent mutations.
|
||||
db.lock.Lock()
|
||||
|
@ -350,10 +379,9 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
return errDatabaseReadOnly
|
||||
}
|
||||
// Ensure the provided state root matches the stored one.
|
||||
root = types.TrieRootHash(root)
|
||||
stored := types.EmptyRootHash
|
||||
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||
stored = crypto.Keccak256Hash(blob)
|
||||
stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stored != root {
|
||||
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
||||
|
@ -389,6 +417,8 @@ func (db *Database) Enable(root common.Hash) error {
|
|||
// Recover rollbacks the database to a specified historical point.
|
||||
// The state is supported as the rollback destination only if it's
|
||||
// canonical state and the corresponding trie histories are existent.
|
||||
//
|
||||
// The supplied root must be a valid trie hash value.
|
||||
func (db *Database) Recover(root common.Hash) error {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
@ -401,7 +431,6 @@ func (db *Database) Recover(root common.Hash) error {
|
|||
return errors.New("state rollback is non-supported")
|
||||
}
|
||||
// Short circuit if the target state is not recoverable
|
||||
root = types.TrieRootHash(root)
|
||||
if !db.Recoverable(root) {
|
||||
return errStateUnrecoverable
|
||||
}
|
||||
|
@ -434,9 +463,10 @@ func (db *Database) Recover(root common.Hash) error {
|
|||
}
|
||||
|
||||
// Recoverable returns the indicator if the specified state is recoverable.
|
||||
//
|
||||
// The supplied root must be a valid trie hash value.
|
||||
func (db *Database) Recoverable(root common.Hash) bool {
|
||||
// Ensure the requested state is a known state.
|
||||
root = types.TrieRootHash(root)
|
||||
id := rawdb.ReadStateID(db.diskdb, root)
|
||||
if id == nil {
|
||||
return false
|
||||
|
|
|
@ -458,8 +458,8 @@ func TestDatabaseRecoverable(t *testing.T) {
|
|||
// Initial state should be recoverable
|
||||
{types.EmptyRootHash, true},
|
||||
|
||||
// Initial state should be recoverable
|
||||
{common.Hash{}, true},
|
||||
// common.Hash{} is not a valid state root for revert
|
||||
{common.Hash{}, false},
|
||||
|
||||
// Layers below current disk layer are recoverable
|
||||
{tester.roots[index-1], true},
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
"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/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
@ -93,9 +92,9 @@ func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
|
|||
// loadLayers loads a pre-existing state layer backed by a key-value store.
|
||||
func (db *Database) loadLayers() layer {
|
||||
// Retrieve the root node of persistent state.
|
||||
var root = types.EmptyRootHash
|
||||
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||
root = crypto.Keccak256Hash(blob)
|
||||
root, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
||||
if err != nil {
|
||||
log.Crit("Failed to compute node hash", "err", err)
|
||||
}
|
||||
// Load the layers by resolving the journal
|
||||
head, err := db.loadJournal(root)
|
||||
|
@ -236,6 +235,8 @@ func (dl *diffLayer) journal(w io.Writer) error {
|
|||
// This is meant to be used during shutdown to persist the layer without
|
||||
// flattening everything down (bad for reorgs). And this function will mark the
|
||||
// database as read-only to prevent all following mutation to disk.
|
||||
//
|
||||
// The supplied root must be a valid trie hash value.
|
||||
func (db *Database) Journal(root common.Hash) error {
|
||||
// Retrieve the head layer to journal from.
|
||||
l := db.tree.get(root)
|
||||
|
@ -265,9 +266,9 @@ func (db *Database) Journal(root common.Hash) error {
|
|||
}
|
||||
// Secondly write out the state root in disk, ensure all layers
|
||||
// on top are continuous with disk.
|
||||
diskRoot := types.EmptyRootHash
|
||||
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
|
||||
diskRoot = crypto.Keccak256Hash(blob)
|
||||
diskRoot, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rlp.Encode(journal, diskRoot); err != nil {
|
||||
return err
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
)
|
||||
|
||||
|
@ -62,7 +61,7 @@ func (tree *layerTree) get(root common.Hash) layer {
|
|||
tree.lock.RLock()
|
||||
defer tree.lock.RUnlock()
|
||||
|
||||
return tree.layers[types.TrieRootHash(root)]
|
||||
return tree.layers[root]
|
||||
}
|
||||
|
||||
// forEach iterates the stored layers inside and applies the
|
||||
|
@ -92,7 +91,6 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
|
|||
//
|
||||
// Although we could silently ignore this internally, it should be the caller's
|
||||
// responsibility to avoid even attempting to insert such a layer.
|
||||
root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot)
|
||||
if root == parentRoot {
|
||||
return errors.New("layer cycle")
|
||||
}
|
||||
|
@ -112,7 +110,6 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
|
|||
// are crossed. All diffs beyond the permitted number are flattened downwards.
|
||||
func (tree *layerTree) cap(root common.Hash, layers int) error {
|
||||
// Retrieve the head layer to cap from
|
||||
root = types.TrieRootHash(root)
|
||||
l := tree.get(root)
|
||||
if l == nil {
|
||||
return fmt.Errorf("triedb layer [%#x] missing", root)
|
||||
|
|
Loading…
Reference in New Issue