From ff12c4b147c50de401253471f724f4cb60683949 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Isabel=20Sch=C3=B6ps=20Thiel?=
<155141998+IST-Github@users.noreply.github.com>
Date: Mon, 1 Jan 2024 00:30:59 +0100
Subject: [PATCH] Delete trie directory
---
trie/committer.go | 182 ----
trie/database.go | 315 -------
trie/database_test.go | 40 -
trie/encoding.go | 144 ---
trie/encoding_test.go | 146 ---
trie/errors.go | 52 --
trie/hasher.go | 208 -----
trie/iterator.go | 791 -----------------
trie/iterator_test.go | 630 -------------
trie/node.go | 254 ------
trie/node_enc.go | 64 --
trie/node_test.go | 215 -----
trie/preimages.go | 95 --
trie/proof.go | 616 -------------
trie/proof_test.go | 1002 ---------------------
trie/secure_trie.go | 290 ------
trie/secure_trie_test.go | 149 ----
trie/stacktrie.go | 479 ----------
trie/stacktrie_fuzzer_test.go | 155 ----
trie/stacktrie_test.go | 487 ----------
trie/sync.go | 714 ---------------
trie/sync_test.go | 1015 ---------------------
trie/testutil/utils.go | 61 --
trie/tracer.go | 130 ---
trie/tracer_test.go | 375 --------
trie/trie.go | 672 --------------
trie/trie_id.go | 55 --
trie/trie_reader.go | 101 ---
trie/trie_test.go | 1224 --------------------------
trie/triedb/hashdb/database.go | 651 --------------
trie/triedb/pathdb/database.go | 485 ----------
trie/triedb/pathdb/database_test.go | 608 -------------
trie/triedb/pathdb/difflayer.go | 174 ----
trie/triedb/pathdb/difflayer_test.go | 170 ----
trie/triedb/pathdb/disklayer.go | 338 -------
trie/triedb/pathdb/errors.go | 60 --
trie/triedb/pathdb/history.go | 649 --------------
trie/triedb/pathdb/history_test.go | 334 -------
trie/triedb/pathdb/journal.go | 387 --------
trie/triedb/pathdb/layertree.go | 214 -----
trie/triedb/pathdb/metrics.go | 50 --
trie/triedb/pathdb/nodebuffer.go | 275 ------
trie/triedb/pathdb/testutils.go | 156 ----
trie/trienode/node.go | 199 -----
trie/trienode/proof.go | 162 ----
trie/triestate/state.go | 276 ------
trie/utils/verkle.go | 342 -------
trie/utils/verkle_test.go | 139 ---
trie/verkle.go | 375 --------
trie/verkle_test.go | 97 --
50 files changed, 16802 deletions(-)
delete mode 100644 trie/committer.go
delete mode 100644 trie/database.go
delete mode 100644 trie/database_test.go
delete mode 100644 trie/encoding.go
delete mode 100644 trie/encoding_test.go
delete mode 100644 trie/errors.go
delete mode 100644 trie/hasher.go
delete mode 100644 trie/iterator.go
delete mode 100644 trie/iterator_test.go
delete mode 100644 trie/node.go
delete mode 100644 trie/node_enc.go
delete mode 100644 trie/node_test.go
delete mode 100644 trie/preimages.go
delete mode 100644 trie/proof.go
delete mode 100644 trie/proof_test.go
delete mode 100644 trie/secure_trie.go
delete mode 100644 trie/secure_trie_test.go
delete mode 100644 trie/stacktrie.go
delete mode 100644 trie/stacktrie_fuzzer_test.go
delete mode 100644 trie/stacktrie_test.go
delete mode 100644 trie/sync.go
delete mode 100644 trie/sync_test.go
delete mode 100644 trie/testutil/utils.go
delete mode 100644 trie/tracer.go
delete mode 100644 trie/tracer_test.go
delete mode 100644 trie/trie.go
delete mode 100644 trie/trie_id.go
delete mode 100644 trie/trie_reader.go
delete mode 100644 trie/trie_test.go
delete mode 100644 trie/triedb/hashdb/database.go
delete mode 100644 trie/triedb/pathdb/database.go
delete mode 100644 trie/triedb/pathdb/database_test.go
delete mode 100644 trie/triedb/pathdb/difflayer.go
delete mode 100644 trie/triedb/pathdb/difflayer_test.go
delete mode 100644 trie/triedb/pathdb/disklayer.go
delete mode 100644 trie/triedb/pathdb/errors.go
delete mode 100644 trie/triedb/pathdb/history.go
delete mode 100644 trie/triedb/pathdb/history_test.go
delete mode 100644 trie/triedb/pathdb/journal.go
delete mode 100644 trie/triedb/pathdb/layertree.go
delete mode 100644 trie/triedb/pathdb/metrics.go
delete mode 100644 trie/triedb/pathdb/nodebuffer.go
delete mode 100644 trie/triedb/pathdb/testutils.go
delete mode 100644 trie/trienode/node.go
delete mode 100644 trie/trienode/proof.go
delete mode 100644 trie/triestate/state.go
delete mode 100644 trie/utils/verkle.go
delete mode 100644 trie/utils/verkle_test.go
delete mode 100644 trie/verkle.go
delete mode 100644 trie/verkle_test.go
diff --git a/trie/committer.go b/trie/committer.go
deleted file mode 100644
index 92163cdb3b..0000000000
--- a/trie/committer.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// committer is the tool used for the trie Commit operation. The committer will
-// capture all dirty nodes during the commit process and keep them cached in
-// insertion order.
-type committer struct {
- nodes *trienode.NodeSet
- tracer *tracer
- collectLeaf bool
-}
-
-// newCommitter creates a new committer or picks one from the pool.
-func newCommitter(nodeset *trienode.NodeSet, tracer *tracer, collectLeaf bool) *committer {
- return &committer{
- nodes: nodeset,
- tracer: tracer,
- collectLeaf: collectLeaf,
- }
-}
-
-// Commit collapses a node down into a hash node.
-func (c *committer) Commit(n node) hashNode {
- return c.commit(nil, n).(hashNode)
-}
-
-// commit collapses a node down into a hash node and returns it.
-func (c *committer) commit(path []byte, n node) node {
- // if this path is clean, use available cached data
- hash, dirty := n.cache()
- if hash != nil && !dirty {
- return hash
- }
- // Commit children, then parent, and remove the dirty flag.
- switch cn := n.(type) {
- case *shortNode:
- // Commit child
- collapsed := cn.copy()
-
- // If the child is fullNode, recursively commit,
- // otherwise it can only be hashNode or valueNode.
- if _, ok := cn.Val.(*fullNode); ok {
- collapsed.Val = c.commit(append(path, cn.Key...), cn.Val)
- }
- // The key needs to be copied, since we're adding it to the
- // modified nodeset.
- collapsed.Key = hexToCompact(cn.Key)
- hashedNode := c.store(path, collapsed)
- if hn, ok := hashedNode.(hashNode); ok {
- return hn
- }
- return collapsed
- case *fullNode:
- hashedKids := c.commitChildren(path, cn)
- collapsed := cn.copy()
- collapsed.Children = hashedKids
-
- hashedNode := c.store(path, collapsed)
- if hn, ok := hashedNode.(hashNode); ok {
- return hn
- }
- return collapsed
- case hashNode:
- return cn
- default:
- // nil, valuenode shouldn't be committed
- panic(fmt.Sprintf("%T: invalid node: %v", n, n))
- }
-}
-
-// commitChildren commits the children of the given fullnode
-func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
- var children [17]node
- for i := 0; i < 16; i++ {
- child := n.Children[i]
- if child == nil {
- continue
- }
- // If it's the hashed child, save the hash value directly.
- // Note: it's impossible that the child in range [0, 15]
- // is a valueNode.
- if hn, ok := child.(hashNode); ok {
- children[i] = hn
- continue
- }
- // Commit the child recursively and store the "hashed" value.
- // Note the returned node can be some embedded nodes, so it's
- // possible the type is not hashNode.
- children[i] = c.commit(append(path, byte(i)), child)
- }
- // For the 17th child, it's possible the type is valuenode.
- if n.Children[16] != nil {
- children[16] = n.Children[16]
- }
- return children
-}
-
-// store hashes the node n and adds it to the modified nodeset. If leaf collection
-// is enabled, leaf nodes will be tracked in the modified nodeset as well.
-func (c *committer) store(path []byte, n node) node {
- // Larger nodes are replaced by their hash and stored in the database.
- var hash, _ = n.cache()
-
- // This was not generated - must be a small node stored in the parent.
- // In theory, we should check if the node is leaf here (embedded node
- // usually is leaf node). But small value (less than 32bytes) is not
- // our target (leaves in account trie only).
- if hash == nil {
- // The node is embedded in its parent, in other words, this node
- // will not be stored in the database independently, mark it as
- // deleted only if the node was existent in database before.
- _, ok := c.tracer.accessList[string(path)]
- if ok {
- c.nodes.AddNode(path, trienode.NewDeleted())
- }
- return n
- }
- // Collect the dirty node to nodeset for return.
- nhash := common.BytesToHash(hash)
- c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n)))
-
- // Collect the corresponding leaf node if it's required. We don't check
- // full node since it's impossible to store value in fullNode. The key
- // length of leaves should be exactly same.
- if c.collectLeaf {
- if sn, ok := n.(*shortNode); ok {
- if val, ok := sn.Val.(valueNode); ok {
- c.nodes.AddLeaf(nhash, val)
- }
- }
- }
- return hash
-}
-
-// mptResolver the children resolver in merkle-patricia-tree.
-type mptResolver struct{}
-
-// ForEach implements childResolver, decodes the provided node and
-// traverses the children inside.
-func (resolver mptResolver) ForEach(node []byte, onChild func(common.Hash)) {
- forGatherChildren(mustDecodeNodeUnsafe(nil, node), onChild)
-}
-
-// forGatherChildren traverses the node hierarchy and invokes the callback
-// for all the hashnode children.
-func forGatherChildren(n node, onChild func(hash common.Hash)) {
- switch n := n.(type) {
- case *shortNode:
- forGatherChildren(n.Val, onChild)
- case *fullNode:
- for i := 0; i < 16; i++ {
- forGatherChildren(n.Children[i], onChild)
- }
- case hashNode:
- onChild(common.BytesToHash(n))
- case valueNode, nil:
- default:
- panic(fmt.Sprintf("unknown node type: %T", n))
- }
-}
diff --git a/trie/database.go b/trie/database.go
deleted file mode 100644
index e20f7ef903..0000000000
--- a/trie/database.go
+++ /dev/null
@@ -1,315 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "errors"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/triedb/hashdb"
- "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-// Config defines all necessary options for database.
-type Config struct {
- Preimages bool // Flag whether the preimage of node key is recorded
- IsVerkle bool // Flag whether the db is holding a verkle tree
- HashDB *hashdb.Config // Configs for hash-based scheme
- PathDB *pathdb.Config // Configs for experimental path-based scheme
-}
-
-// HashDefaults represents a config for using hash-based scheme with
-// default settings.
-var HashDefaults = &Config{
- Preimages: false,
- HashDB: hashdb.Defaults,
-}
-
-// backend defines the methods needed to access/update trie nodes in different
-// state scheme.
-type backend interface {
- // Scheme returns the identifier of used storage scheme.
- Scheme() string
-
- // Initialized returns an indicator if the state data is already initialized
- // according to the state scheme.
- Initialized(genesisRoot common.Hash) bool
-
- // Size returns the current storage size of the diff layers on top of the
- // disk layer and the storage size of the nodes cached in the disk layer.
- //
- // For hash scheme, there is no differentiation between diff layer nodes
- // and dirty disk layer nodes, so both are merged into the second return.
- Size() (common.StorageSize, common.StorageSize)
-
- // Update performs a state transition by committing dirty nodes contained
- // in the given set in order to update state from the specified parent to
- // the specified root.
- //
- // The passed in maps(nodes, states) will be retained to avoid copying
- // everything. Therefore, these maps must not be changed afterwards.
- Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error
-
- // Commit writes all relevant trie nodes belonging to the specified state
- // to disk. Report specifies whether logs will be displayed in info level.
- Commit(root common.Hash, report bool) error
-
- // Close closes the trie database backend and releases all held resources.
- Close() error
-}
-
-// Database is the wrapper of the underlying backend which is shared by different
-// types of node backend as an entrypoint. It's responsible for all interactions
-// relevant with trie nodes and node preimages.
-type Database struct {
- config *Config // Configuration for trie database
- diskdb ethdb.Database // Persistent database to store the snapshot
- preimages *preimageStore // The store for caching preimages
- backend backend // The backend for managing trie nodes
-}
-
-// NewDatabase initializes the trie database with default settings, note
-// the legacy hash-based scheme is used by default.
-func NewDatabase(diskdb ethdb.Database, config *Config) *Database {
- // Sanitize the config and use the default one if it's not specified.
- if config == nil {
- config = HashDefaults
- }
- var preimages *preimageStore
- if config.Preimages {
- preimages = newPreimageStore(diskdb)
- }
- db := &Database{
- config: config,
- diskdb: diskdb,
- preimages: preimages,
- }
- if config.HashDB != nil && config.PathDB != nil {
- log.Crit("Both 'hash' and 'path' mode are configured")
- }
- if config.PathDB != nil {
- db.backend = pathdb.New(diskdb, config.PathDB)
- } else {
- db.backend = hashdb.New(diskdb, config.HashDB, mptResolver{})
- }
- return db
-}
-
-// Reader returns a reader for accessing all trie nodes with provided state root.
-// An error will be returned if the requested state is not available.
-func (db *Database) Reader(blockRoot common.Hash) (Reader, error) {
- switch b := db.backend.(type) {
- case *hashdb.Database:
- return b.Reader(blockRoot)
- case *pathdb.Database:
- return b.Reader(blockRoot)
- }
- return nil, errors.New("unknown backend")
-}
-
-// Update performs a state transition by committing dirty nodes contained in the
-// given set in order to update state from the specified parent to the specified
-// root. The held pre-images accumulated up to this point will be flushed in case
-// the size exceeds the threshold.
-//
-// The passed in maps(nodes, states) will be retained to avoid copying everything.
-// Therefore, these maps must not be changed afterwards.
-func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
- if db.preimages != nil {
- db.preimages.commit(false)
- }
- return db.backend.Update(root, parent, block, nodes, states)
-}
-
-// Commit iterates over all the children of a particular node, writes them out
-// to disk. As a side effect, all pre-images accumulated up to this point are
-// also written.
-func (db *Database) Commit(root common.Hash, report bool) error {
- if db.preimages != nil {
- db.preimages.commit(true)
- }
- return db.backend.Commit(root, report)
-}
-
-// Size returns the storage size of diff layer nodes above the persistent disk
-// layer, the dirty nodes buffered within the disk layer, and the size of cached
-// preimages.
-func (db *Database) Size() (common.StorageSize, common.StorageSize, common.StorageSize) {
- var (
- diffs, nodes common.StorageSize
- preimages common.StorageSize
- )
- diffs, nodes = db.backend.Size()
- if db.preimages != nil {
- preimages = db.preimages.size()
- }
- return diffs, nodes, preimages
-}
-
-// Initialized returns an indicator if the state data is already initialized
-// according to the state scheme.
-func (db *Database) Initialized(genesisRoot common.Hash) bool {
- return db.backend.Initialized(genesisRoot)
-}
-
-// Scheme returns the node scheme used in the database.
-func (db *Database) Scheme() string {
- return db.backend.Scheme()
-}
-
-// Close flushes the dangling preimages to disk and closes the trie database.
-// It is meant to be called when closing the blockchain object, so that all
-// resources held can be released correctly.
-func (db *Database) Close() error {
- db.WritePreimages()
- return db.backend.Close()
-}
-
-// WritePreimages flushes all accumulated preimages to disk forcibly.
-func (db *Database) WritePreimages() {
- if db.preimages != nil {
- db.preimages.commit(true)
- }
-}
-
-// Preimage retrieves a cached trie node pre-image from memory. If it cannot be
-// found cached, the method queries the persistent database for the content.
-func (db *Database) Preimage(hash common.Hash) []byte {
- if db.preimages == nil {
- return nil
- }
- return db.preimages.preimage(hash)
-}
-
-// Cap iteratively flushes old but still referenced trie nodes until the total
-// memory usage goes below the given threshold. The held pre-images accumulated
-// up to this point will be flushed in case the size exceeds the threshold.
-//
-// It's only supported by hash-based database and will return an error for others.
-func (db *Database) Cap(limit common.StorageSize) error {
- hdb, ok := db.backend.(*hashdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- if db.preimages != nil {
- db.preimages.commit(false)
- }
- return hdb.Cap(limit)
-}
-
-// Reference adds a new reference from a parent node to a child node. This function
-// is used to add reference between internal trie node and external node(e.g. storage
-// trie root), all internal trie nodes are referenced together by database itself.
-//
-// It's only supported by hash-based database and will return an error for others.
-func (db *Database) Reference(root common.Hash, parent common.Hash) error {
- hdb, ok := db.backend.(*hashdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- hdb.Reference(root, parent)
- return nil
-}
-
-// Dereference removes an existing reference from a root node. It's only
-// supported by hash-based database and will return an error for others.
-func (db *Database) Dereference(root common.Hash) error {
- hdb, ok := db.backend.(*hashdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- hdb.Dereference(root)
- return nil
-}
-
-// 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. It's only supported by path-based
-// database and will return an error for others.
-func (db *Database) Recover(target common.Hash) error {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- return pdb.Recover(target, &trieLoader{db: db})
-}
-
-// Recoverable returns the indicator if the specified state is enabled to be
-// recovered. It's only supported by path-based database and will return an
-// error for others.
-func (db *Database) Recoverable(root common.Hash) (bool, error) {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return false, errors.New("not supported")
- }
- return pdb.Recoverable(root), nil
-}
-
-// Disable deactivates the database and invalidates all available state layers
-// as stale to prevent access to the persistent state, which is in the syncing
-// stage.
-//
-// It's only supported by path-based database and will return an error for others.
-func (db *Database) Disable() error {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- return pdb.Disable()
-}
-
-// Enable activates database and resets the state tree with the provided persistent
-// state root once the state sync is finished.
-func (db *Database) Enable(root common.Hash) error {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- return pdb.Enable(root)
-}
-
-// Journal commits an entire diff hierarchy to disk into a single journal entry.
-// This is meant to be used during shutdown to persist the snapshot without
-// flattening everything down (bad for reorgs). It's only supported by path-based
-// database and will return an error for others.
-func (db *Database) Journal(root common.Hash) error {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- return pdb.Journal(root)
-}
-
-// SetBufferSize sets the node buffer size to the provided value(in bytes).
-// It's only supported by path-based database and will return an error for
-// others.
-func (db *Database) SetBufferSize(size int) error {
- pdb, ok := db.backend.(*pathdb.Database)
- if !ok {
- return errors.New("not supported")
- }
- return pdb.SetBufferSize(size)
-}
-
-// IsVerkle returns the indicator if the database is holding a verkle tree.
-func (db *Database) IsVerkle() bool {
- return db.config.IsVerkle
-}
diff --git a/trie/database_test.go b/trie/database_test.go
deleted file mode 100644
index d508c65533..0000000000
--- a/trie/database_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/trie/triedb/hashdb"
- "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
-)
-
-// newTestDatabase initializes the trie database with specified scheme.
-func newTestDatabase(diskdb ethdb.Database, scheme string) *Database {
- config := &Config{Preimages: false}
- if scheme == rawdb.HashScheme {
- config.HashDB = &hashdb.Config{
- CleanCacheSize: 0,
- } // disable clean cache
- } else {
- config.PathDB = &pathdb.Config{
- CleanCacheSize: 0,
- DirtyCacheSize: 0,
- } // disable clean/dirty cache
- }
- return NewDatabase(diskdb, config)
-}
diff --git a/trie/encoding.go b/trie/encoding.go
deleted file mode 100644
index 3284d3f8f0..0000000000
--- a/trie/encoding.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-// Trie keys are dealt with in three distinct encodings:
-//
-// KEYBYTES encoding contains the actual key and nothing else. This encoding is the
-// input to most API functions.
-//
-// HEX encoding contains one byte for each nibble of the key and an optional trailing
-// 'terminator' byte of value 0x10 which indicates whether or not the node at the key
-// contains a value. Hex key encoding is used for nodes loaded in memory because it's
-// convenient to access.
-//
-// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix
-// encoding" there) and contains the bytes of the key and a flag. The high nibble of the
-// first byte contains the flag; the lowest bit encoding the oddness of the length and
-// the second-lowest encoding whether the node at the key is a value node. The low nibble
-// of the first byte is zero in the case of an even number of nibbles and the first nibble
-// in the case of an odd number. All remaining nibbles (now an even number) fit properly
-// into the remaining bytes. Compact encoding is used for nodes stored on disk.
-
-func hexToCompact(hex []byte) []byte {
- terminator := byte(0)
- if hasTerm(hex) {
- terminator = 1
- hex = hex[:len(hex)-1]
- }
- buf := make([]byte, len(hex)/2+1)
- buf[0] = terminator << 5 // the flag byte
- if len(hex)&1 == 1 {
- buf[0] |= 1 << 4 // odd flag
- buf[0] |= hex[0] // first nibble is contained in the first byte
- hex = hex[1:]
- }
- decodeNibbles(hex, buf[1:])
- return buf
-}
-
-// hexToCompactInPlace places the compact key in input buffer, returning the compacted key.
-func hexToCompactInPlace(hex []byte) []byte {
- var (
- hexLen = len(hex) // length of the hex input
- firstByte = byte(0)
- )
- // Check if we have a terminator there
- if hexLen > 0 && hex[hexLen-1] == 16 {
- firstByte = 1 << 5
- hexLen-- // last part was the terminator, ignore that
- }
- var (
- binLen = hexLen/2 + 1
- ni = 0 // index in hex
- bi = 1 // index in bin (compact)
- )
- if hexLen&1 == 1 {
- firstByte |= 1 << 4 // odd flag
- firstByte |= hex[0] // first nibble is contained in the first byte
- ni++
- }
- for ; ni < hexLen; bi, ni = bi+1, ni+2 {
- hex[bi] = hex[ni]<<4 | hex[ni+1]
- }
- hex[0] = firstByte
- return hex[:binLen]
-}
-
-func compactToHex(compact []byte) []byte {
- if len(compact) == 0 {
- return compact
- }
- base := keybytesToHex(compact)
- // delete terminator flag
- if base[0] < 2 {
- base = base[:len(base)-1]
- }
- // apply odd flag
- chop := 2 - base[0]&1
- return base[chop:]
-}
-
-func keybytesToHex(str []byte) []byte {
- l := len(str)*2 + 1
- var nibbles = make([]byte, l)
- for i, b := range str {
- nibbles[i*2] = b / 16
- nibbles[i*2+1] = b % 16
- }
- nibbles[l-1] = 16
- return nibbles
-}
-
-// hexToKeybytes turns hex nibbles into key bytes.
-// This can only be used for keys of even length.
-func hexToKeybytes(hex []byte) []byte {
- if hasTerm(hex) {
- hex = hex[:len(hex)-1]
- }
- if len(hex)&1 != 0 {
- panic("can't convert hex key of odd length")
- }
- key := make([]byte, len(hex)/2)
- decodeNibbles(hex, key)
- return key
-}
-
-func decodeNibbles(nibbles []byte, bytes []byte) {
- for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 {
- bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1]
- }
-}
-
-// prefixLen returns the length of the common prefix of a and b.
-func prefixLen(a, b []byte) int {
- var i, length = 0, len(a)
- if len(b) < length {
- length = len(b)
- }
- for ; i < length; i++ {
- if a[i] != b[i] {
- break
- }
- }
- return i
-}
-
-// hasTerm returns whether a hex key has the terminator flag.
-func hasTerm(s []byte) bool {
- return len(s) > 0 && s[len(s)-1] == 16
-}
diff --git a/trie/encoding_test.go b/trie/encoding_test.go
deleted file mode 100644
index ac50b5d025..0000000000
--- a/trie/encoding_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- crand "crypto/rand"
- "encoding/hex"
- "math/rand"
- "testing"
-)
-
-func TestHexCompact(t *testing.T) {
- tests := []struct{ hex, compact []byte }{
- // empty keys, with and without terminator.
- {hex: []byte{}, compact: []byte{0x00}},
- {hex: []byte{16}, compact: []byte{0x20}},
- // odd length, no terminator
- {hex: []byte{1, 2, 3, 4, 5}, compact: []byte{0x11, 0x23, 0x45}},
- // even length, no terminator
- {hex: []byte{0, 1, 2, 3, 4, 5}, compact: []byte{0x00, 0x01, 0x23, 0x45}},
- // odd length, terminator
- {hex: []byte{15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x3f, 0x1c, 0xb8}},
- // even length, terminator
- {hex: []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x20, 0x0f, 0x1c, 0xb8}},
- }
- for _, test := range tests {
- if c := hexToCompact(test.hex); !bytes.Equal(c, test.compact) {
- t.Errorf("hexToCompact(%x) -> %x, want %x", test.hex, c, test.compact)
- }
- if h := compactToHex(test.compact); !bytes.Equal(h, test.hex) {
- t.Errorf("compactToHex(%x) -> %x, want %x", test.compact, h, test.hex)
- }
- }
-}
-
-func TestHexKeybytes(t *testing.T) {
- tests := []struct{ key, hexIn, hexOut []byte }{
- {key: []byte{}, hexIn: []byte{16}, hexOut: []byte{16}},
- {key: []byte{}, hexIn: []byte{}, hexOut: []byte{16}},
- {
- key: []byte{0x12, 0x34, 0x56},
- hexIn: []byte{1, 2, 3, 4, 5, 6, 16},
- hexOut: []byte{1, 2, 3, 4, 5, 6, 16},
- },
- {
- key: []byte{0x12, 0x34, 0x5},
- hexIn: []byte{1, 2, 3, 4, 0, 5, 16},
- hexOut: []byte{1, 2, 3, 4, 0, 5, 16},
- },
- {
- key: []byte{0x12, 0x34, 0x56},
- hexIn: []byte{1, 2, 3, 4, 5, 6},
- hexOut: []byte{1, 2, 3, 4, 5, 6, 16},
- },
- }
- for _, test := range tests {
- if h := keybytesToHex(test.key); !bytes.Equal(h, test.hexOut) {
- t.Errorf("keybytesToHex(%x) -> %x, want %x", test.key, h, test.hexOut)
- }
- if k := hexToKeybytes(test.hexIn); !bytes.Equal(k, test.key) {
- t.Errorf("hexToKeybytes(%x) -> %x, want %x", test.hexIn, k, test.key)
- }
- }
-}
-
-func TestHexToCompactInPlace(t *testing.T) {
- for i, key := range []string{
- "00",
- "060a040c0f000a090b040803010801010900080d090a0a0d0903000b10",
- "10",
- } {
- hexBytes, _ := hex.DecodeString(key)
- exp := hexToCompact(hexBytes)
- got := hexToCompactInPlace(hexBytes)
- if !bytes.Equal(exp, got) {
- t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, key, got, exp)
- }
- }
-}
-
-func TestHexToCompactInPlaceRandom(t *testing.T) {
- for i := 0; i < 10000; i++ {
- l := rand.Intn(128)
- key := make([]byte, l)
- crand.Read(key)
- hexBytes := keybytesToHex(key)
- hexOrig := []byte(string(hexBytes))
- exp := hexToCompact(hexBytes)
- got := hexToCompactInPlace(hexBytes)
-
- if !bytes.Equal(exp, got) {
- t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n",
- key, hexOrig, got, exp)
- }
- }
-}
-
-func BenchmarkHexToCompact(b *testing.B) {
- testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
- for i := 0; i < b.N; i++ {
- hexToCompact(testBytes)
- }
-}
-
-func BenchmarkHexToCompactInPlace(b *testing.B) {
- testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
- for i := 0; i < b.N; i++ {
- hexToCompactInPlace(testBytes)
- }
-}
-
-func BenchmarkCompactToHex(b *testing.B) {
- testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
- for i := 0; i < b.N; i++ {
- compactToHex(testBytes)
- }
-}
-
-func BenchmarkKeybytesToHex(b *testing.B) {
- testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16}
- for i := 0; i < b.N; i++ {
- keybytesToHex(testBytes)
- }
-}
-
-func BenchmarkHexToKeybytes(b *testing.B) {
- testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16}
- for i := 0; i < b.N; i++ {
- hexToKeybytes(testBytes)
- }
-}
diff --git a/trie/errors.go b/trie/errors.go
deleted file mode 100644
index 7be7041c7f..0000000000
--- a/trie/errors.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-// ErrCommitted is returned when a already committed trie is requested for usage.
-// The potential usages can be `Get`, `Update`, `Delete`, `NodeIterator`, `Prove`
-// and so on.
-var ErrCommitted = errors.New("trie is already committed")
-
-// MissingNodeError is returned by the trie functions (Get, Update, Delete)
-// in the case where a trie node is not present in the local database. It contains
-// information necessary for retrieving the missing node.
-type MissingNodeError struct {
- Owner common.Hash // owner of the trie if it's 2-layered trie
- NodeHash common.Hash // hash of the missing node
- Path []byte // hex-encoded path to the missing node
- err error // concrete error for missing trie node
-}
-
-// Unwrap returns the concrete error for missing trie node which
-// allows us for further analysis outside.
-func (err *MissingNodeError) Unwrap() error {
- return err.err
-}
-
-func (err *MissingNodeError) Error() string {
- if err.Owner == (common.Hash{}) {
- return fmt.Sprintf("missing trie node %x (path %x) %v", err.NodeHash, err.Path, err.err)
- }
- return fmt.Sprintf("missing trie node %x (owner %x) (path %x) %v", err.NodeHash, err.Owner, err.Path, err.err)
-}
diff --git a/trie/hasher.go b/trie/hasher.go
deleted file mode 100644
index 1e063d8020..0000000000
--- a/trie/hasher.go
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/rlp"
- "golang.org/x/crypto/sha3"
-)
-
-// hasher is a type used for the trie Hash operation. A hasher has some
-// internal preallocated temp space
-type hasher struct {
- sha crypto.KeccakState
- tmp []byte
- encbuf rlp.EncoderBuffer
- parallel bool // Whether to use parallel threads when hashing
-}
-
-// hasherPool holds pureHashers
-var hasherPool = sync.Pool{
- New: func() interface{} {
- return &hasher{
- tmp: make([]byte, 0, 550), // cap is as large as a full fullNode.
- sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
- encbuf: rlp.NewEncoderBuffer(nil),
- }
- },
-}
-
-func newHasher(parallel bool) *hasher {
- h := hasherPool.Get().(*hasher)
- h.parallel = parallel
- return h
-}
-
-func returnHasherToPool(h *hasher) {
- hasherPool.Put(h)
-}
-
-// hash collapses a node down into a hash node, also returning a copy of the
-// original node initialized with the computed hash to replace the original one.
-func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
- // Return the cached hash if it's available
- if hash, _ := n.cache(); hash != nil {
- return hash, n
- }
- // Trie not processed yet, walk the children
- switch n := n.(type) {
- case *shortNode:
- collapsed, cached := h.hashShortNodeChildren(n)
- hashed := h.shortnodeToHash(collapsed, force)
- // We need to retain the possibly _not_ hashed node, in case it was too
- // small to be hashed
- if hn, ok := hashed.(hashNode); ok {
- cached.flags.hash = hn
- } else {
- cached.flags.hash = nil
- }
- return hashed, cached
- case *fullNode:
- collapsed, cached := h.hashFullNodeChildren(n)
- hashed = h.fullnodeToHash(collapsed, force)
- if hn, ok := hashed.(hashNode); ok {
- cached.flags.hash = hn
- } else {
- cached.flags.hash = nil
- }
- return hashed, cached
- default:
- // Value and hash nodes don't have children, so they're left as were
- return n, n
- }
-}
-
-// hashShortNodeChildren collapses the short node. The returned collapsed node
-// holds a live reference to the Key, and must not be modified.
-func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) {
- // Hash the short node's child, caching the newly hashed subtree
- collapsed, cached = n.copy(), n.copy()
- // Previously, we did copy this one. We don't seem to need to actually
- // do that, since we don't overwrite/reuse keys
- // cached.Key = common.CopyBytes(n.Key)
- collapsed.Key = hexToCompact(n.Key)
- // Unless the child is a valuenode or hashnode, hash it
- switch n.Val.(type) {
- case *fullNode, *shortNode:
- collapsed.Val, cached.Val = h.hash(n.Val, false)
- }
- return collapsed, cached
-}
-
-func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached *fullNode) {
- // Hash the full node's children, caching the newly hashed subtrees
- cached = n.copy()
- collapsed = n.copy()
- if h.parallel {
- var wg sync.WaitGroup
- wg.Add(16)
- for i := 0; i < 16; i++ {
- go func(i int) {
- hasher := newHasher(false)
- if child := n.Children[i]; child != nil {
- collapsed.Children[i], cached.Children[i] = hasher.hash(child, false)
- } else {
- collapsed.Children[i] = nilValueNode
- }
- returnHasherToPool(hasher)
- wg.Done()
- }(i)
- }
- wg.Wait()
- } else {
- for i := 0; i < 16; i++ {
- if child := n.Children[i]; child != nil {
- collapsed.Children[i], cached.Children[i] = h.hash(child, false)
- } else {
- collapsed.Children[i] = nilValueNode
- }
- }
- }
- return collapsed, cached
-}
-
-// shortnodeToHash creates a hashNode from a shortNode. The supplied shortnode
-// should have hex-type Key, which will be converted (without modification)
-// into compact form for RLP encoding.
-// If the rlp data is smaller than 32 bytes, `nil` is returned.
-func (h *hasher) shortnodeToHash(n *shortNode, force bool) node {
- n.encode(h.encbuf)
- enc := h.encodedBytes()
-
- if len(enc) < 32 && !force {
- return n // Nodes smaller than 32 bytes are stored inside their parent
- }
- return h.hashData(enc)
-}
-
-// fullnodeToHash is used to create a hashNode from a fullNode, (which
-// may contain nil values)
-func (h *hasher) fullnodeToHash(n *fullNode, force bool) node {
- n.encode(h.encbuf)
- enc := h.encodedBytes()
-
- if len(enc) < 32 && !force {
- return n // Nodes smaller than 32 bytes are stored inside their parent
- }
- return h.hashData(enc)
-}
-
-// encodedBytes returns the result of the last encoding operation on h.encbuf.
-// This also resets the encoder buffer.
-//
-// All node encoding must be done like this:
-//
-// node.encode(h.encbuf)
-// enc := h.encodedBytes()
-//
-// This convention exists because node.encode can only be inlined/escape-analyzed when
-// called on a concrete receiver type.
-func (h *hasher) encodedBytes() []byte {
- h.tmp = h.encbuf.AppendToBytes(h.tmp[:0])
- h.encbuf.Reset(nil)
- return h.tmp
-}
-
-// hashData hashes the provided data
-func (h *hasher) hashData(data []byte) hashNode {
- n := make(hashNode, 32)
- h.sha.Reset()
- h.sha.Write(data)
- h.sha.Read(n)
- return n
-}
-
-// proofHash is used to construct trie proofs, and returns the 'collapsed'
-// node (for later RLP encoding) as well as the hashed node -- unless the
-// node is smaller than 32 bytes, in which case it will be returned as is.
-// This method does not do anything on value- or hash-nodes.
-func (h *hasher) proofHash(original node) (collapsed, hashed node) {
- switch n := original.(type) {
- case *shortNode:
- sn, _ := h.hashShortNodeChildren(n)
- return sn, h.shortnodeToHash(sn, false)
- case *fullNode:
- fn, _ := h.hashFullNodeChildren(n)
- return fn, h.fullnodeToHash(fn, false)
- default:
- // Value and hash nodes don't have children, so they're left as were
- return n, n
- }
-}
diff --git a/trie/iterator.go b/trie/iterator.go
deleted file mode 100644
index 83ccc0740f..0000000000
--- a/trie/iterator.go
+++ /dev/null
@@ -1,791 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "container/heap"
- "errors"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-// NodeResolver is used for looking up trie nodes before reaching into the real
-// persistent layer. This is not mandatory, rather is an optimization for cases
-// where trie nodes can be recovered from some external mechanism without reading
-// from disk. In those cases, this resolver allows short circuiting accesses and
-// returning them from memory.
-type NodeResolver func(owner common.Hash, path []byte, hash common.Hash) []byte
-
-// Iterator is a key-value trie iterator that traverses a Trie.
-type Iterator struct {
- nodeIt NodeIterator
-
- Key []byte // Current data key on which the iterator is positioned on
- Value []byte // Current data value on which the iterator is positioned on
- Err error
-}
-
-// NewIterator creates a new key-value iterator from a node iterator.
-// Note that the value returned by the iterator is raw. If the content is encoded
-// (e.g. storage value is RLP-encoded), it's caller's duty to decode it.
-func NewIterator(it NodeIterator) *Iterator {
- return &Iterator{
- nodeIt: it,
- }
-}
-
-// Next moves the iterator forward one key-value entry.
-func (it *Iterator) Next() bool {
- for it.nodeIt.Next(true) {
- if it.nodeIt.Leaf() {
- it.Key = it.nodeIt.LeafKey()
- it.Value = it.nodeIt.LeafBlob()
- return true
- }
- }
- it.Key = nil
- it.Value = nil
- it.Err = it.nodeIt.Error()
- return false
-}
-
-// Prove generates the Merkle proof for the leaf node the iterator is currently
-// positioned on.
-func (it *Iterator) Prove() [][]byte {
- return it.nodeIt.LeafProof()
-}
-
-// NodeIterator is an iterator to traverse the trie pre-order.
-type NodeIterator interface {
- // Next moves the iterator to the next node. If the parameter is false, any child
- // nodes will be skipped.
- Next(bool) bool
-
- // Error returns the error status of the iterator.
- Error() error
-
- // Hash returns the hash of the current node.
- Hash() common.Hash
-
- // Parent returns the hash of the parent of the current node. The hash may be the one
- // grandparent if the immediate parent is an internal node with no hash.
- Parent() common.Hash
-
- // Path returns the hex-encoded path to the current node.
- // Callers must not retain references to the return value after calling Next.
- // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
- Path() []byte
-
- // NodeBlob returns the rlp-encoded value of the current iterated node.
- // If the node is an embedded node in its parent, nil is returned then.
- NodeBlob() []byte
-
- // Leaf returns true iff the current node is a leaf node.
- Leaf() bool
-
- // LeafKey returns the key of the leaf. The method panics if the iterator is not
- // positioned at a leaf. Callers must not retain references to the value after
- // calling Next.
- LeafKey() []byte
-
- // LeafBlob returns the content of the leaf. The method panics if the iterator
- // is not positioned at a leaf. Callers must not retain references to the value
- // after calling Next.
- LeafBlob() []byte
-
- // LeafProof returns the Merkle proof of the leaf. The method panics if the
- // iterator is not positioned at a leaf. Callers must not retain references
- // to the value after calling Next.
- LeafProof() [][]byte
-
- // AddResolver sets a node resolver to use for looking up trie nodes before
- // reaching into the real persistent layer.
- //
- // This is not required for normal operation, rather is an optimization for
- // cases where trie nodes can be recovered from some external mechanism without
- // reading from disk. In those cases, this resolver allows short circuiting
- // accesses and returning them from memory.
- //
- // Before adding a similar mechanism to any other place in Geth, consider
- // making trie.Database an interface and wrapping at that level. It's a huge
- // refactor, but it could be worth it if another occurrence arises.
- AddResolver(NodeResolver)
-}
-
-// nodeIteratorState represents the iteration state at one particular node of the
-// trie, which can be resumed at a later invocation.
-type nodeIteratorState struct {
- hash common.Hash // Hash of the node being iterated (nil if not standalone)
- node node // Trie node being iterated
- parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
- index int // Child to be processed next
- pathlen int // Length of the path to this node
-}
-
-type nodeIterator struct {
- trie *Trie // Trie being iterated
- stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
- path []byte // Path to the current node
- err error // Failure set in case of an internal error in the iterator
-
- resolver NodeResolver // optional node resolver for avoiding disk hits
- pool []*nodeIteratorState // local pool for iteratorstates
-}
-
-// errIteratorEnd is stored in nodeIterator.err when iteration is done.
-var errIteratorEnd = errors.New("end of iteration")
-
-// seekError is stored in nodeIterator.err if the initial seek has failed.
-type seekError struct {
- key []byte
- err error
-}
-
-func (e seekError) Error() string {
- return "seek error: " + e.err.Error()
-}
-
-func newNodeIterator(trie *Trie, start []byte) NodeIterator {
- if trie.Hash() == types.EmptyRootHash {
- return &nodeIterator{
- trie: trie,
- err: errIteratorEnd,
- }
- }
- it := &nodeIterator{trie: trie}
- it.err = it.seek(start)
- return it
-}
-
-func (it *nodeIterator) putInPool(item *nodeIteratorState) {
- if len(it.pool) < 40 {
- item.node = nil
- it.pool = append(it.pool, item)
- }
-}
-
-func (it *nodeIterator) getFromPool() *nodeIteratorState {
- idx := len(it.pool) - 1
- if idx < 0 {
- return new(nodeIteratorState)
- }
- el := it.pool[idx]
- it.pool[idx] = nil
- it.pool = it.pool[:idx]
- return el
-}
-
-func (it *nodeIterator) AddResolver(resolver NodeResolver) {
- it.resolver = resolver
-}
-
-func (it *nodeIterator) Hash() common.Hash {
- if len(it.stack) == 0 {
- return common.Hash{}
- }
- return it.stack[len(it.stack)-1].hash
-}
-
-func (it *nodeIterator) Parent() common.Hash {
- if len(it.stack) == 0 {
- return common.Hash{}
- }
- return it.stack[len(it.stack)-1].parent
-}
-
-func (it *nodeIterator) Leaf() bool {
- return hasTerm(it.path)
-}
-
-func (it *nodeIterator) LeafKey() []byte {
- if len(it.stack) > 0 {
- if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
- return hexToKeybytes(it.path)
- }
- }
- panic("not at leaf")
-}
-
-func (it *nodeIterator) LeafBlob() []byte {
- if len(it.stack) > 0 {
- if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
- return node
- }
- }
- panic("not at leaf")
-}
-
-func (it *nodeIterator) LeafProof() [][]byte {
- if len(it.stack) > 0 {
- if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
- hasher := newHasher(false)
- defer returnHasherToPool(hasher)
- proofs := make([][]byte, 0, len(it.stack))
-
- for i, item := range it.stack[:len(it.stack)-1] {
- // Gather nodes that end up as hash nodes (or the root)
- node, hashed := hasher.proofHash(item.node)
- if _, ok := hashed.(hashNode); ok || i == 0 {
- proofs = append(proofs, nodeToBytes(node))
- }
- }
- return proofs
- }
- }
- panic("not at leaf")
-}
-
-func (it *nodeIterator) Path() []byte {
- return it.path
-}
-
-func (it *nodeIterator) NodeBlob() []byte {
- if it.Hash() == (common.Hash{}) {
- return nil // skip the non-standalone node
- }
- blob, err := it.resolveBlob(it.Hash().Bytes(), it.Path())
- if err != nil {
- it.err = err
- return nil
- }
- return blob
-}
-
-func (it *nodeIterator) Error() error {
- if it.err == errIteratorEnd {
- return nil
- }
- if seek, ok := it.err.(seekError); ok {
- return seek.err
- }
- return it.err
-}
-
-// Next moves the iterator to the next node, returning whether there are any
-// further nodes. In case of an internal error this method returns false and
-// sets the Error field to the encountered failure. If `descend` is false,
-// skips iterating over any subnodes of the current node.
-func (it *nodeIterator) Next(descend bool) bool {
- if it.err == errIteratorEnd {
- return false
- }
- if seek, ok := it.err.(seekError); ok {
- if it.err = it.seek(seek.key); it.err != nil {
- return false
- }
- }
- // Otherwise step forward with the iterator and report any errors.
- state, parentIndex, path, err := it.peek(descend)
- it.err = err
- if it.err != nil {
- return false
- }
- it.push(state, parentIndex, path)
- return true
-}
-
-func (it *nodeIterator) seek(prefix []byte) error {
- // The path we're looking for is the hex encoded key without terminator.
- key := keybytesToHex(prefix)
- key = key[:len(key)-1]
- // Move forward until we're just before the closest match to key.
- for {
- state, parentIndex, path, err := it.peekSeek(key)
- if err == errIteratorEnd {
- return errIteratorEnd
- } else if err != nil {
- return seekError{prefix, err}
- } else if bytes.Compare(path, key) >= 0 {
- return nil
- }
- it.push(state, parentIndex, path)
- }
-}
-
-// init initializes the iterator.
-func (it *nodeIterator) init() (*nodeIteratorState, error) {
- root := it.trie.Hash()
- state := &nodeIteratorState{node: it.trie.root, index: -1}
- if root != types.EmptyRootHash {
- state.hash = root
- }
- return state, state.resolve(it, nil)
-}
-
-// peek creates the next state of the iterator.
-func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) {
- // Initialize the iterator if we've just started.
- if len(it.stack) == 0 {
- state, err := it.init()
- return state, nil, nil, err
- }
- if !descend {
- // If we're skipping children, pop the current node first
- it.pop()
- }
-
- // Continue iteration to the next child
- for len(it.stack) > 0 {
- parent := it.stack[len(it.stack)-1]
- ancestor := parent.hash
- if (ancestor == common.Hash{}) {
- ancestor = parent.parent
- }
- state, path, ok := it.nextChild(parent, ancestor)
- if ok {
- if err := state.resolve(it, path); err != nil {
- return parent, &parent.index, path, err
- }
- return state, &parent.index, path, nil
- }
- // No more child nodes, move back up.
- it.pop()
- }
- return nil, nil, nil, errIteratorEnd
-}
-
-// peekSeek is like peek, but it also tries to skip resolving hashes by skipping
-// over the siblings that do not lead towards the desired seek position.
-func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []byte, error) {
- // Initialize the iterator if we've just started.
- if len(it.stack) == 0 {
- state, err := it.init()
- return state, nil, nil, err
- }
- if !bytes.HasPrefix(seekKey, it.path) {
- // If we're skipping children, pop the current node first
- it.pop()
- }
-
- // Continue iteration to the next child
- for len(it.stack) > 0 {
- parent := it.stack[len(it.stack)-1]
- ancestor := parent.hash
- if (ancestor == common.Hash{}) {
- ancestor = parent.parent
- }
- state, path, ok := it.nextChildAt(parent, ancestor, seekKey)
- if ok {
- if err := state.resolve(it, path); err != nil {
- return parent, &parent.index, path, err
- }
- return state, &parent.index, path, nil
- }
- // No more child nodes, move back up.
- it.pop()
- }
- return nil, nil, nil, errIteratorEnd
-}
-
-func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
- if it.resolver != nil {
- if blob := it.resolver(it.trie.owner, path, common.BytesToHash(hash)); len(blob) > 0 {
- if resolved, err := decodeNode(hash, blob); err == nil {
- return resolved, nil
- }
- }
- }
- // Retrieve the specified node from the underlying node reader.
- // it.trie.resolveAndTrack is not used since in that function the
- // loaded blob will be tracked, while it's not required here since
- // all loaded nodes won't be linked to trie at all and track nodes
- // may lead to out-of-memory issue.
- blob, err := it.trie.reader.node(path, common.BytesToHash(hash))
- if err != nil {
- return nil, err
- }
- // The raw-blob format nodes are loaded either from the
- // clean cache or the database, they are all in their own
- // copy and safe to use unsafe decoder.
- return mustDecodeNodeUnsafe(hash, blob), nil
-}
-
-func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) {
- if it.resolver != nil {
- if blob := it.resolver(it.trie.owner, path, common.BytesToHash(hash)); len(blob) > 0 {
- return blob, nil
- }
- }
- // Retrieve the specified node from the underlying node reader.
- // it.trie.resolveAndTrack is not used since in that function the
- // loaded blob will be tracked, while it's not required here since
- // all loaded nodes won't be linked to trie at all and track nodes
- // may lead to out-of-memory issue.
- return it.trie.reader.node(path, common.BytesToHash(hash))
-}
-
-func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
- if hash, ok := st.node.(hashNode); ok {
- resolved, err := it.resolveHash(hash, path)
- if err != nil {
- return err
- }
- st.node = resolved
- st.hash = common.BytesToHash(hash)
- }
- return nil
-}
-
-func (it *nodeIterator) findChild(n *fullNode, index int, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) {
- var (
- path = it.path
- child node
- state *nodeIteratorState
- childPath []byte
- )
- for ; index < len(n.Children); index++ {
- if n.Children[index] != nil {
- child = n.Children[index]
- hash, _ := child.cache()
- state = it.getFromPool()
- state.hash = common.BytesToHash(hash)
- state.node = child
- state.parent = ancestor
- state.index = -1
- state.pathlen = len(path)
- childPath = append(childPath, path...)
- childPath = append(childPath, byte(index))
- return child, state, childPath, index
- }
- }
- return nil, nil, nil, 0
-}
-
-func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) {
- switch node := parent.node.(type) {
- case *fullNode:
- // Full node, move to the first non-nil child.
- if child, state, path, index := it.findChild(node, parent.index+1, ancestor); child != nil {
- parent.index = index - 1
- return state, path, true
- }
- case *shortNode:
- // Short node, return the pointer singleton child
- if parent.index < 0 {
- hash, _ := node.Val.cache()
- state := it.getFromPool()
- state.hash = common.BytesToHash(hash)
- state.node = node.Val
- state.parent = ancestor
- state.index = -1
- state.pathlen = len(it.path)
- path := append(it.path, node.Key...)
- return state, path, true
- }
- }
- return parent, it.path, false
-}
-
-// nextChildAt is similar to nextChild, except that it targets a child as close to the
-// target key as possible, thus skipping siblings.
-func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.Hash, key []byte) (*nodeIteratorState, []byte, bool) {
- switch n := parent.node.(type) {
- case *fullNode:
- // Full node, move to the first non-nil child before the desired key position
- child, state, path, index := it.findChild(n, parent.index+1, ancestor)
- if child == nil {
- // No more children in this fullnode
- return parent, it.path, false
- }
- // If the child we found is already past the seek position, just return it.
- if bytes.Compare(path, key) >= 0 {
- parent.index = index - 1
- return state, path, true
- }
- // The child is before the seek position. Try advancing
- for {
- nextChild, nextState, nextPath, nextIndex := it.findChild(n, index+1, ancestor)
- // If we run out of children, or skipped past the target, return the
- // previous one
- if nextChild == nil || bytes.Compare(nextPath, key) >= 0 {
- parent.index = index - 1
- return state, path, true
- }
- // We found a better child closer to the target
- state, path, index = nextState, nextPath, nextIndex
- }
- case *shortNode:
- // Short node, return the pointer singleton child
- if parent.index < 0 {
- hash, _ := n.Val.cache()
- state := it.getFromPool()
- state.hash = common.BytesToHash(hash)
- state.node = n.Val
- state.parent = ancestor
- state.index = -1
- state.pathlen = len(it.path)
- path := append(it.path, n.Key...)
- return state, path, true
- }
- }
- return parent, it.path, false
-}
-
-func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) {
- it.path = path
- it.stack = append(it.stack, state)
- if parentIndex != nil {
- *parentIndex++
- }
-}
-
-func (it *nodeIterator) pop() {
- last := it.stack[len(it.stack)-1]
- it.path = it.path[:last.pathlen]
- it.stack[len(it.stack)-1] = nil
- it.stack = it.stack[:len(it.stack)-1]
- // last is now unused
- it.putInPool(last)
-}
-
-func compareNodes(a, b NodeIterator) int {
- if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 {
- return cmp
- }
- if a.Leaf() && !b.Leaf() {
- return -1
- } else if b.Leaf() && !a.Leaf() {
- return 1
- }
- if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 {
- return cmp
- }
- if a.Leaf() && b.Leaf() {
- return bytes.Compare(a.LeafBlob(), b.LeafBlob())
- }
- return 0
-}
-
-type differenceIterator struct {
- a, b NodeIterator // Nodes returned are those in b - a.
- eof bool // Indicates a has run out of elements
- count int // Number of nodes scanned on either trie
-}
-
-// NewDifferenceIterator constructs a NodeIterator that iterates over elements in b that
-// are not in a. Returns the iterator, and a pointer to an integer recording the number
-// of nodes seen.
-func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) {
- a.Next(true)
- it := &differenceIterator{
- a: a,
- b: b,
- }
- return it, &it.count
-}
-
-func (it *differenceIterator) Hash() common.Hash {
- return it.b.Hash()
-}
-
-func (it *differenceIterator) Parent() common.Hash {
- return it.b.Parent()
-}
-
-func (it *differenceIterator) Leaf() bool {
- return it.b.Leaf()
-}
-
-func (it *differenceIterator) LeafKey() []byte {
- return it.b.LeafKey()
-}
-
-func (it *differenceIterator) LeafBlob() []byte {
- return it.b.LeafBlob()
-}
-
-func (it *differenceIterator) LeafProof() [][]byte {
- return it.b.LeafProof()
-}
-
-func (it *differenceIterator) Path() []byte {
- return it.b.Path()
-}
-
-func (it *differenceIterator) NodeBlob() []byte {
- return it.b.NodeBlob()
-}
-
-func (it *differenceIterator) AddResolver(resolver NodeResolver) {
- panic("not implemented")
-}
-
-func (it *differenceIterator) Next(bool) bool {
- // Invariants:
- // - We always advance at least one element in b.
- // - At the start of this function, a's path is lexically greater than b's.
- if !it.b.Next(true) {
- return false
- }
- it.count++
-
- if it.eof {
- // a has reached eof, so we just return all elements from b
- return true
- }
-
- for {
- switch compareNodes(it.a, it.b) {
- case -1:
- // b jumped past a; advance a
- if !it.a.Next(true) {
- it.eof = true
- return true
- }
- it.count++
- case 1:
- // b is before a
- return true
- case 0:
- // a and b are identical; skip this whole subtree if the nodes have hashes
- hasHash := it.a.Hash() == common.Hash{}
- if !it.b.Next(hasHash) {
- return false
- }
- it.count++
- if !it.a.Next(hasHash) {
- it.eof = true
- return true
- }
- it.count++
- }
- }
-}
-
-func (it *differenceIterator) Error() error {
- if err := it.a.Error(); err != nil {
- return err
- }
- return it.b.Error()
-}
-
-type nodeIteratorHeap []NodeIterator
-
-func (h nodeIteratorHeap) Len() int { return len(h) }
-func (h nodeIteratorHeap) Less(i, j int) bool { return compareNodes(h[i], h[j]) < 0 }
-func (h nodeIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
-func (h *nodeIteratorHeap) Push(x interface{}) { *h = append(*h, x.(NodeIterator)) }
-func (h *nodeIteratorHeap) Pop() interface{} {
- n := len(*h)
- x := (*h)[n-1]
- *h = (*h)[0 : n-1]
- return x
-}
-
-type unionIterator struct {
- items *nodeIteratorHeap // Nodes returned are the union of the ones in these iterators
- count int // Number of nodes scanned across all tries
-}
-
-// NewUnionIterator constructs a NodeIterator that iterates over elements in the union
-// of the provided NodeIterators. Returns the iterator, and a pointer to an integer
-// recording the number of nodes visited.
-func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) {
- h := make(nodeIteratorHeap, len(iters))
- copy(h, iters)
- heap.Init(&h)
-
- ui := &unionIterator{items: &h}
- return ui, &ui.count
-}
-
-func (it *unionIterator) Hash() common.Hash {
- return (*it.items)[0].Hash()
-}
-
-func (it *unionIterator) Parent() common.Hash {
- return (*it.items)[0].Parent()
-}
-
-func (it *unionIterator) Leaf() bool {
- return (*it.items)[0].Leaf()
-}
-
-func (it *unionIterator) LeafKey() []byte {
- return (*it.items)[0].LeafKey()
-}
-
-func (it *unionIterator) LeafBlob() []byte {
- return (*it.items)[0].LeafBlob()
-}
-
-func (it *unionIterator) LeafProof() [][]byte {
- return (*it.items)[0].LeafProof()
-}
-
-func (it *unionIterator) Path() []byte {
- return (*it.items)[0].Path()
-}
-
-func (it *unionIterator) NodeBlob() []byte {
- return (*it.items)[0].NodeBlob()
-}
-
-func (it *unionIterator) AddResolver(resolver NodeResolver) {
- panic("not implemented")
-}
-
-// Next returns the next node in the union of tries being iterated over.
-//
-// It does this by maintaining a heap of iterators, sorted by the iteration
-// order of their next elements, with one entry for each source trie. Each
-// time Next() is called, it takes the least element from the heap to return,
-// advancing any other iterators that also point to that same element. These
-// iterators are called with descend=false, since we know that any nodes under
-// these nodes will also be duplicates, found in the currently selected iterator.
-// Whenever an iterator is advanced, it is pushed back into the heap if it still
-// has elements remaining.
-//
-// In the case that descend=false - eg, we're asked to ignore all subnodes of the
-// current node - we also advance any iterators in the heap that have the current
-// path as a prefix.
-func (it *unionIterator) Next(descend bool) bool {
- if len(*it.items) == 0 {
- return false
- }
-
- // Get the next key from the union
- least := heap.Pop(it.items).(NodeIterator)
-
- // Skip over other nodes as long as they're identical, or, if we're not descending, as
- // long as they have the same prefix as the current node.
- for len(*it.items) > 0 && ((!descend && bytes.HasPrefix((*it.items)[0].Path(), least.Path())) || compareNodes(least, (*it.items)[0]) == 0) {
- skipped := heap.Pop(it.items).(NodeIterator)
- // Skip the whole subtree if the nodes have hashes; otherwise just skip this node
- if skipped.Next(skipped.Hash() == common.Hash{}) {
- it.count++
- // If there are more elements, push the iterator back on the heap
- heap.Push(it.items, skipped)
- }
- }
- if least.Next(descend) {
- it.count++
- heap.Push(it.items, least)
- }
- return len(*it.items) > 0
-}
-
-func (it *unionIterator) Error() error {
- for i := 0; i < len(*it.items); i++ {
- if err := (*it.items)[i].Error(); err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
deleted file mode 100644
index 9679b49ca7..0000000000
--- a/trie/iterator_test.go
+++ /dev/null
@@ -1,630 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "testing"
-
- "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/trie/trienode"
-)
-
-func TestEmptyIterator(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- iter := trie.MustNodeIterator(nil)
-
- seen := make(map[string]struct{})
- for iter.Next(true) {
- seen[string(iter.Path())] = struct{}{}
- }
- if len(seen) != 0 {
- t.Fatal("Unexpected trie node iterated")
- }
-}
-
-func TestIterator(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(db)
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- all := make(map[string]string)
- for _, val := range vals {
- all[val.k] = val.v
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- found := make(map[string]string)
- it := NewIterator(trie.MustNodeIterator(nil))
- for it.Next() {
- found[string(it.Key)] = string(it.Value)
- }
-
- for k, v := range all {
- if found[k] != v {
- t.Errorf("iterator value mismatch for %s: got %q want %q", k, found[k], v)
- }
- }
-}
-
-type kv struct {
- k, v []byte
- t bool
-}
-
-func (k *kv) cmp(other *kv) int {
- return bytes.Compare(k.k, other.k)
-}
-
-func TestIteratorLargeData(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- vals := make(map[string]*kv)
-
- for i := byte(0); i < 255; i++ {
- value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
- value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false}
- trie.MustUpdate(value.k, value.v)
- trie.MustUpdate(value2.k, value2.v)
- vals[string(value.k)] = value
- vals[string(value2.k)] = value2
- }
-
- it := NewIterator(trie.MustNodeIterator(nil))
- for it.Next() {
- vals[string(it.Key)].t = true
- }
-
- var untouched []*kv
- for _, value := range vals {
- if !value.t {
- untouched = append(untouched, value)
- }
- }
-
- if len(untouched) > 0 {
- t.Errorf("Missed %d nodes", len(untouched))
- for _, value := range untouched {
- t.Error(value)
- }
- }
-}
-
-type iterationElement struct {
- hash common.Hash
- path []byte
- blob []byte
-}
-
-// Tests that the node iterator indeed walks over the entire database contents.
-func TestNodeIteratorCoverage(t *testing.T) {
- testNodeIteratorCoverage(t, rawdb.HashScheme)
- testNodeIteratorCoverage(t, rawdb.PathScheme)
-}
-
-func testNodeIteratorCoverage(t *testing.T, scheme string) {
- // Create some arbitrary test trie to iterate
- db, nodeDb, trie, _ := makeTestTrie(scheme)
-
- // Gather all the node hashes found by the iterator
- var elements = make(map[common.Hash]iterationElement)
- for it := trie.MustNodeIterator(nil); it.Next(true); {
- if it.Hash() != (common.Hash{}) {
- elements[it.Hash()] = iterationElement{
- hash: it.Hash(),
- path: common.CopyBytes(it.Path()),
- blob: common.CopyBytes(it.NodeBlob()),
- }
- }
- }
- // Cross check the hashes and the database itself
- reader, err := nodeDb.Reader(trie.Hash())
- if err != nil {
- t.Fatalf("state is not available %x", trie.Hash())
- }
- for _, element := range elements {
- if blob, err := reader.Node(common.Hash{}, element.path, element.hash); err != nil {
- t.Errorf("failed to retrieve reported node %x: %v", element.hash, err)
- } else if !bytes.Equal(blob, element.blob) {
- t.Errorf("node blob is different, want %v got %v", element.blob, blob)
- }
- }
- var (
- count int
- it = db.NewIterator(nil, nil)
- )
- for it.Next() {
- res, _, _ := isTrieNode(nodeDb.Scheme(), it.Key(), it.Value())
- if !res {
- continue
- }
- count += 1
- if elem, ok := elements[crypto.Keccak256Hash(it.Value())]; !ok {
- t.Error("state entry not reported")
- } else if !bytes.Equal(it.Value(), elem.blob) {
- t.Errorf("node blob is different, want %v got %v", elem.blob, it.Value())
- }
- }
- it.Release()
- if count != len(elements) {
- t.Errorf("state entry is mismatched %d %d", count, len(elements))
- }
-}
-
-type kvs struct{ k, v string }
-
-var testdata1 = []kvs{
- {"barb", "ba"},
- {"bard", "bc"},
- {"bars", "bb"},
- {"bar", "b"},
- {"fab", "z"},
- {"food", "ab"},
- {"foos", "aa"},
- {"foo", "a"},
-}
-
-var testdata2 = []kvs{
- {"aardvark", "c"},
- {"bar", "b"},
- {"barb", "bd"},
- {"bars", "be"},
- {"fab", "z"},
- {"foo", "a"},
- {"foos", "aa"},
- {"food", "ab"},
- {"jars", "d"},
-}
-
-func TestIteratorSeek(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for _, val := range testdata1 {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
-
- // Seek to the middle.
- it := NewIterator(trie.MustNodeIterator([]byte("fab")))
- if err := checkIteratorOrder(testdata1[4:], it); err != nil {
- t.Fatal(err)
- }
-
- // Seek to a non-existent key.
- it = NewIterator(trie.MustNodeIterator([]byte("barc")))
- if err := checkIteratorOrder(testdata1[1:], it); err != nil {
- t.Fatal(err)
- }
-
- // Seek beyond the end.
- it = NewIterator(trie.MustNodeIterator([]byte("z")))
- if err := checkIteratorOrder(nil, it); err != nil {
- t.Fatal(err)
- }
-}
-
-func checkIteratorOrder(want []kvs, it *Iterator) error {
- for it.Next() {
- if len(want) == 0 {
- return fmt.Errorf("didn't expect any more values, got key %q", it.Key)
- }
- if !bytes.Equal(it.Key, []byte(want[0].k)) {
- return fmt.Errorf("wrong key: got %q, want %q", it.Key, want[0].k)
- }
- want = want[1:]
- }
- if len(want) > 0 {
- return fmt.Errorf("iterator ended early, want key %q", want[0])
- }
- return nil
-}
-
-func TestDifferenceIterator(t *testing.T) {
- dba := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- triea := NewEmpty(dba)
- for _, val := range testdata1 {
- triea.MustUpdate([]byte(val.k), []byte(val.v))
- }
- rootA, nodesA, _ := triea.Commit(false)
- dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil)
- triea, _ = New(TrieID(rootA), dba)
-
- dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trieb := NewEmpty(dbb)
- for _, val := range testdata2 {
- trieb.MustUpdate([]byte(val.k), []byte(val.v))
- }
- rootB, nodesB, _ := trieb.Commit(false)
- dbb.Update(rootB, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesB), nil)
- trieb, _ = New(TrieID(rootB), dbb)
-
- found := make(map[string]string)
- di, _ := NewDifferenceIterator(triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil))
- it := NewIterator(di)
- for it.Next() {
- found[string(it.Key)] = string(it.Value)
- }
-
- all := []struct{ k, v string }{
- {"aardvark", "c"},
- {"barb", "bd"},
- {"bars", "be"},
- {"jars", "d"},
- }
- for _, item := range all {
- if found[item.k] != item.v {
- t.Errorf("iterator value mismatch for %s: got %v want %v", item.k, found[item.k], item.v)
- }
- }
- if len(found) != len(all) {
- t.Errorf("iterator count mismatch: got %d values, want %d", len(found), len(all))
- }
-}
-
-func TestUnionIterator(t *testing.T) {
- dba := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- triea := NewEmpty(dba)
- for _, val := range testdata1 {
- triea.MustUpdate([]byte(val.k), []byte(val.v))
- }
- rootA, nodesA, _ := triea.Commit(false)
- dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil)
- triea, _ = New(TrieID(rootA), dba)
-
- dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trieb := NewEmpty(dbb)
- for _, val := range testdata2 {
- trieb.MustUpdate([]byte(val.k), []byte(val.v))
- }
- rootB, nodesB, _ := trieb.Commit(false)
- dbb.Update(rootB, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesB), nil)
- trieb, _ = New(TrieID(rootB), dbb)
-
- di, _ := NewUnionIterator([]NodeIterator{triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)})
- it := NewIterator(di)
-
- all := []struct{ k, v string }{
- {"aardvark", "c"},
- {"barb", "ba"},
- {"barb", "bd"},
- {"bard", "bc"},
- {"bars", "bb"},
- {"bars", "be"},
- {"bar", "b"},
- {"fab", "z"},
- {"food", "ab"},
- {"foos", "aa"},
- {"foo", "a"},
- {"jars", "d"},
- }
-
- for i, kv := range all {
- if !it.Next() {
- t.Errorf("Iterator ends prematurely at element %d", i)
- }
- if kv.k != string(it.Key) {
- t.Errorf("iterator value mismatch for element %d: got key %s want %s", i, it.Key, kv.k)
- }
- if kv.v != string(it.Value) {
- t.Errorf("iterator value mismatch for element %d: got value %s want %s", i, it.Value, kv.v)
- }
- }
- if it.Next() {
- t.Errorf("Iterator returned extra values.")
- }
-}
-
-func TestIteratorNoDups(t *testing.T) {
- tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for _, val := range testdata1 {
- tr.MustUpdate([]byte(val.k), []byte(val.v))
- }
- checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil)
-}
-
-// This test checks that nodeIterator.Next can be retried after inserting missing trie nodes.
-func TestIteratorContinueAfterError(t *testing.T) {
- testIteratorContinueAfterError(t, false, rawdb.HashScheme)
- testIteratorContinueAfterError(t, true, rawdb.HashScheme)
- testIteratorContinueAfterError(t, false, rawdb.PathScheme)
- testIteratorContinueAfterError(t, true, rawdb.PathScheme)
-}
-
-func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) {
- diskdb := rawdb.NewMemoryDatabase()
- tdb := newTestDatabase(diskdb, scheme)
-
- tr := NewEmpty(tdb)
- for _, val := range testdata1 {
- tr.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := tr.Commit(false)
- tdb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- if !memonly {
- tdb.Commit(root, false)
- }
- tr, _ = New(TrieID(root), tdb)
- wantNodeCount := checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil)
-
- var (
- paths [][]byte
- hashes []common.Hash
- )
- if memonly {
- for path, n := range nodes.Nodes {
- paths = append(paths, []byte(path))
- hashes = append(hashes, n.Hash)
- }
- } else {
- it := diskdb.NewIterator(nil, nil)
- for it.Next() {
- ok, path, hash := isTrieNode(tdb.Scheme(), it.Key(), it.Value())
- if !ok {
- continue
- }
- paths = append(paths, path)
- hashes = append(hashes, hash)
- }
- it.Release()
- }
- for i := 0; i < 20; i++ {
- // Create trie that will load all nodes from DB.
- tr, _ := New(TrieID(tr.Hash()), tdb)
-
- // Remove a random node from the database. It can't be the root node
- // because that one is already loaded.
- var (
- rval []byte
- rpath []byte
- rhash common.Hash
- )
- for {
- if memonly {
- rpath = paths[rand.Intn(len(paths))]
- n := nodes.Nodes[string(rpath)]
- if n == nil {
- continue
- }
- rhash = n.Hash
- } else {
- index := rand.Intn(len(paths))
- rpath = paths[index]
- rhash = hashes[index]
- }
- if rhash != tr.Hash() {
- break
- }
- }
- if memonly {
- tr.reader.banned = map[string]struct{}{string(rpath): {}}
- } else {
- rval = rawdb.ReadTrieNode(diskdb, common.Hash{}, rpath, rhash, tdb.Scheme())
- rawdb.DeleteTrieNode(diskdb, common.Hash{}, rpath, rhash, tdb.Scheme())
- }
- // Iterate until the error is hit.
- seen := make(map[string]bool)
- it := tr.MustNodeIterator(nil)
- checkIteratorNoDups(t, it, seen)
- missing, ok := it.Error().(*MissingNodeError)
- if !ok || missing.NodeHash != rhash {
- t.Fatal("didn't hit missing node, got", it.Error())
- }
-
- // Add the node back and continue iteration.
- if memonly {
- delete(tr.reader.banned, string(rpath))
- } else {
- rawdb.WriteTrieNode(diskdb, common.Hash{}, rpath, rhash, rval, tdb.Scheme())
- }
- checkIteratorNoDups(t, it, seen)
- if it.Error() != nil {
- t.Fatal("unexpected error", it.Error())
- }
- if len(seen) != wantNodeCount {
- t.Fatal("wrong node iteration count, got", len(seen), "want", wantNodeCount)
- }
- }
-}
-
-// Similar to the test above, this one checks that failure to create nodeIterator at a
-// certain key prefix behaves correctly when Next is called. The expectation is that Next
-// should retry seeking before returning true for the first time.
-func TestIteratorContinueAfterSeekError(t *testing.T) {
- testIteratorContinueAfterSeekError(t, false, rawdb.HashScheme)
- testIteratorContinueAfterSeekError(t, true, rawdb.HashScheme)
- testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme)
- testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme)
-}
-
-func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme string) {
- // Commit test trie to db, then remove the node containing "bars".
- var (
- barNodePath []byte
- barNodeHash = common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e")
- )
- diskdb := rawdb.NewMemoryDatabase()
- triedb := newTestDatabase(diskdb, scheme)
- ctr := NewEmpty(triedb)
- for _, val := range testdata1 {
- ctr.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := ctr.Commit(false)
- for path, n := range nodes.Nodes {
- if n.Hash == barNodeHash {
- barNodePath = []byte(path)
- break
- }
- }
- triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- if !memonly {
- triedb.Commit(root, false)
- }
- var (
- barNodeBlob []byte
- )
- tr, _ := New(TrieID(root), triedb)
- if memonly {
- tr.reader.banned = map[string]struct{}{string(barNodePath): {}}
- } else {
- barNodeBlob = rawdb.ReadTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, triedb.Scheme())
- rawdb.DeleteTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, triedb.Scheme())
- }
- // Create a new iterator that seeks to "bars". Seeking can't proceed because
- // the node is missing.
- it := tr.MustNodeIterator([]byte("bars"))
- missing, ok := it.Error().(*MissingNodeError)
- if !ok {
- t.Fatal("want MissingNodeError, got", it.Error())
- } else if missing.NodeHash != barNodeHash {
- t.Fatal("wrong node missing")
- }
- // Reinsert the missing node.
- if memonly {
- delete(tr.reader.banned, string(barNodePath))
- } else {
- rawdb.WriteTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, barNodeBlob, triedb.Scheme())
- }
- // Check that iteration produces the right set of values.
- if err := checkIteratorOrder(testdata1[2:], NewIterator(it)); err != nil {
- t.Fatal(err)
- }
-}
-
-func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) int {
- if seen == nil {
- seen = make(map[string]bool)
- }
- for it.Next(true) {
- if seen[string(it.Path())] {
- t.Fatalf("iterator visited node path %x twice", it.Path())
- }
- seen[string(it.Path())] = true
- }
- return len(seen)
-}
-
-func TestIteratorNodeBlob(t *testing.T) {
- testIteratorNodeBlob(t, rawdb.HashScheme)
- testIteratorNodeBlob(t, rawdb.PathScheme)
-}
-
-func testIteratorNodeBlob(t *testing.T, scheme string) {
- var (
- db = rawdb.NewMemoryDatabase()
- triedb = newTestDatabase(db, scheme)
- trie = NewEmpty(triedb)
- )
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- all := make(map[string]string)
- for _, val := range vals {
- all[val.k] = val.v
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := trie.Commit(false)
- triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- triedb.Commit(root, false)
-
- var found = make(map[common.Hash][]byte)
- trie, _ = New(TrieID(root), triedb)
- it := trie.MustNodeIterator(nil)
- for it.Next(true) {
- if it.Hash() == (common.Hash{}) {
- continue
- }
- found[it.Hash()] = it.NodeBlob()
- }
-
- dbIter := db.NewIterator(nil, nil)
- defer dbIter.Release()
-
- var count int
- for dbIter.Next() {
- ok, _, _ := isTrieNode(triedb.Scheme(), dbIter.Key(), dbIter.Value())
- if !ok {
- continue
- }
- got, present := found[crypto.Keccak256Hash(dbIter.Value())]
- if !present {
- t.Fatal("Miss trie node")
- }
- if !bytes.Equal(got, dbIter.Value()) {
- t.Fatalf("Unexpected trie node want %v got %v", dbIter.Value(), got)
- }
- count += 1
- }
- if count != len(found) {
- t.Fatal("Find extra trie node via iterator")
- }
-}
-
-// isTrieNode is a helper function which reports if the provided
-// database entry belongs to a trie node or not. Note in tests
-// only single layer trie is used, namely storage trie is not
-// considered at all.
-func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) {
- var (
- path []byte
- hash common.Hash
- )
- if scheme == rawdb.HashScheme {
- ok := rawdb.IsLegacyTrieNode(key, val)
- if !ok {
- return false, nil, common.Hash{}
- }
- hash = common.BytesToHash(key)
- } else {
- ok, remain := rawdb.ResolveAccountTrieNodeKey(key)
- if !ok {
- return false, nil, common.Hash{}
- }
- path = common.CopyBytes(remain)
- hash = crypto.Keccak256Hash(val)
- }
- return true, path, hash
-}
-
-func BenchmarkIterator(b *testing.B) {
- diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme)
- root := tr.Hash()
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- if err := checkTrieConsistency(diskDb, srcDb.Scheme(), root, false); err != nil {
- b.Fatal(err)
- }
- }
-}
diff --git a/trie/node.go b/trie/node.go
deleted file mode 100644
index 15bbf62f1c..0000000000
--- a/trie/node.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "fmt"
- "io"
- "strings"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"}
-
-type node interface {
- cache() (hashNode, bool)
- encode(w rlp.EncoderBuffer)
- fstring(string) string
-}
-
-type (
- fullNode struct {
- Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
- flags nodeFlag
- }
- shortNode struct {
- Key []byte
- Val node
- flags nodeFlag
- }
- hashNode []byte
- valueNode []byte
-)
-
-// nilValueNode is used when collapsing internal trie nodes for hashing, since
-// unset children need to serialize correctly.
-var nilValueNode = valueNode(nil)
-
-// EncodeRLP encodes a full node into the consensus RLP format.
-func (n *fullNode) EncodeRLP(w io.Writer) error {
- eb := rlp.NewEncoderBuffer(w)
- n.encode(eb)
- return eb.Flush()
-}
-
-func (n *fullNode) copy() *fullNode { copy := *n; return © }
-func (n *shortNode) copy() *shortNode { copy := *n; return © }
-
-// nodeFlag contains caching-related metadata about a node.
-type nodeFlag struct {
- hash hashNode // cached hash of the node (may be nil)
- dirty bool // whether the node has changes that must be written to the database
-}
-
-func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
-func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
-func (n hashNode) cache() (hashNode, bool) { return nil, true }
-func (n valueNode) cache() (hashNode, bool) { return nil, true }
-
-// Pretty printing.
-func (n *fullNode) String() string { return n.fstring("") }
-func (n *shortNode) String() string { return n.fstring("") }
-func (n hashNode) String() string { return n.fstring("") }
-func (n valueNode) String() string { return n.fstring("") }
-
-func (n *fullNode) fstring(ind string) string {
- resp := fmt.Sprintf("[\n%s ", ind)
- for i, node := range &n.Children {
- if node == nil {
- resp += fmt.Sprintf("%s: ", indices[i])
- } else {
- resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" "))
- }
- }
- return resp + fmt.Sprintf("\n%s] ", ind)
-}
-func (n *shortNode) fstring(ind string) string {
- return fmt.Sprintf("{%x: %v} ", n.Key, n.Val.fstring(ind+" "))
-}
-func (n hashNode) fstring(ind string) string {
- return fmt.Sprintf("<%x> ", []byte(n))
-}
-func (n valueNode) fstring(ind string) string {
- return fmt.Sprintf("%x ", []byte(n))
-}
-
-// rawNode is a simple binary blob used to differentiate between collapsed trie
-// nodes and already encoded RLP binary blobs (while at the same time store them
-// in the same cache fields).
-type rawNode []byte
-
-func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") }
-func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") }
-
-func (n rawNode) EncodeRLP(w io.Writer) error {
- _, err := w.Write(n)
- return err
-}
-
-// mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered.
-func mustDecodeNode(hash, buf []byte) node {
- n, err := decodeNode(hash, buf)
- if err != nil {
- panic(fmt.Sprintf("node %x: %v", hash, err))
- }
- return n
-}
-
-// mustDecodeNodeUnsafe is a wrapper of decodeNodeUnsafe and panic if any error is
-// encountered.
-func mustDecodeNodeUnsafe(hash, buf []byte) node {
- n, err := decodeNodeUnsafe(hash, buf)
- if err != nil {
- panic(fmt.Sprintf("node %x: %v", hash, err))
- }
- return n
-}
-
-// decodeNode parses the RLP encoding of a trie node. It will deep-copy the passed
-// byte slice for decoding, so it's safe to modify the byte slice afterwards. The-
-// decode performance of this function is not optimal, but it is suitable for most
-// scenarios with low performance requirements and hard to determine whether the
-// byte slice be modified or not.
-func decodeNode(hash, buf []byte) (node, error) {
- return decodeNodeUnsafe(hash, common.CopyBytes(buf))
-}
-
-// decodeNodeUnsafe parses the RLP encoding of a trie node. The passed byte slice
-// will be directly referenced by node without bytes deep copy, so the input MUST
-// not be changed after.
-func decodeNodeUnsafe(hash, buf []byte) (node, error) {
- if len(buf) == 0 {
- return nil, io.ErrUnexpectedEOF
- }
- elems, _, err := rlp.SplitList(buf)
- if err != nil {
- return nil, fmt.Errorf("decode error: %v", err)
- }
- switch c, _ := rlp.CountValues(elems); c {
- case 2:
- n, err := decodeShort(hash, elems)
- return n, wrapError(err, "short")
- case 17:
- n, err := decodeFull(hash, elems)
- return n, wrapError(err, "full")
- default:
- return nil, fmt.Errorf("invalid number of list elements: %v", c)
- }
-}
-
-func decodeShort(hash, elems []byte) (node, error) {
- kbuf, rest, err := rlp.SplitString(elems)
- if err != nil {
- return nil, err
- }
- flag := nodeFlag{hash: hash}
- key := compactToHex(kbuf)
- if hasTerm(key) {
- // value node
- val, _, err := rlp.SplitString(rest)
- if err != nil {
- return nil, fmt.Errorf("invalid value node: %v", err)
- }
- return &shortNode{key, valueNode(val), flag}, nil
- }
- r, _, err := decodeRef(rest)
- if err != nil {
- return nil, wrapError(err, "val")
- }
- return &shortNode{key, r, flag}, nil
-}
-
-func decodeFull(hash, elems []byte) (*fullNode, error) {
- n := &fullNode{flags: nodeFlag{hash: hash}}
- for i := 0; i < 16; i++ {
- cld, rest, err := decodeRef(elems)
- if err != nil {
- return n, wrapError(err, fmt.Sprintf("[%d]", i))
- }
- n.Children[i], elems = cld, rest
- }
- val, _, err := rlp.SplitString(elems)
- if err != nil {
- return n, err
- }
- if len(val) > 0 {
- n.Children[16] = valueNode(val)
- }
- return n, nil
-}
-
-const hashLen = len(common.Hash{})
-
-func decodeRef(buf []byte) (node, []byte, error) {
- kind, val, rest, err := rlp.Split(buf)
- if err != nil {
- return nil, buf, err
- }
- switch {
- case kind == rlp.List:
- // 'embedded' node reference. The encoding must be smaller
- // than a hash in order to be valid.
- if size := len(buf) - len(rest); size > hashLen {
- err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen)
- return nil, buf, err
- }
- n, err := decodeNode(nil, buf)
- return n, rest, err
- case kind == rlp.String && len(val) == 0:
- // empty node
- return nil, rest, nil
- case kind == rlp.String && len(val) == 32:
- return hashNode(val), rest, nil
- default:
- return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val))
- }
-}
-
-// wraps a decoding error with information about the path to the
-// invalid child node (for debugging encoding issues).
-type decodeError struct {
- what error
- stack []string
-}
-
-func wrapError(err error, ctx string) error {
- if err == nil {
- return nil
- }
- if decErr, ok := err.(*decodeError); ok {
- decErr.stack = append(decErr.stack, ctx)
- return decErr
- }
- return &decodeError{err, []string{ctx}}
-}
-
-func (err *decodeError) Error() string {
- return fmt.Sprintf("%v (decode path: %s)", err.what, strings.Join(err.stack, "<-"))
-}
diff --git a/trie/node_enc.go b/trie/node_enc.go
deleted file mode 100644
index 1b2eca682f..0000000000
--- a/trie/node_enc.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-func nodeToBytes(n node) []byte {
- w := rlp.NewEncoderBuffer(nil)
- n.encode(w)
- result := w.ToBytes()
- w.Flush()
- return result
-}
-
-func (n *fullNode) encode(w rlp.EncoderBuffer) {
- offset := w.List()
- for _, c := range n.Children {
- if c != nil {
- c.encode(w)
- } else {
- w.Write(rlp.EmptyString)
- }
- }
- w.ListEnd(offset)
-}
-
-func (n *shortNode) encode(w rlp.EncoderBuffer) {
- offset := w.List()
- w.WriteBytes(n.Key)
- if n.Val != nil {
- n.Val.encode(w)
- } else {
- w.Write(rlp.EmptyString)
- }
- w.ListEnd(offset)
-}
-
-func (n hashNode) encode(w rlp.EncoderBuffer) {
- w.WriteBytes(n)
-}
-
-func (n valueNode) encode(w rlp.EncoderBuffer) {
- w.WriteBytes(n)
-}
-
-func (n rawNode) encode(w rlp.EncoderBuffer) {
- w.Write(n)
-}
diff --git a/trie/node_test.go b/trie/node_test.go
deleted file mode 100644
index 9b8b33748f..0000000000
--- a/trie/node_test.go
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-func newTestFullNode(v []byte) []interface{} {
- fullNodeData := []interface{}{}
- for i := 0; i < 16; i++ {
- k := bytes.Repeat([]byte{byte(i + 1)}, 32)
- fullNodeData = append(fullNodeData, k)
- }
- fullNodeData = append(fullNodeData, v)
- return fullNodeData
-}
-
-func TestDecodeNestedNode(t *testing.T) {
- fullNodeData := newTestFullNode([]byte("fullnode"))
-
- data := [][]byte{}
- for i := 0; i < 16; i++ {
- data = append(data, nil)
- }
- data = append(data, []byte("subnode"))
- fullNodeData[15] = data
-
- buf := bytes.NewBuffer([]byte{})
- rlp.Encode(buf, fullNodeData)
-
- if _, err := decodeNode([]byte("testdecode"), buf.Bytes()); err != nil {
- t.Fatalf("decode nested full node err: %v", err)
- }
-}
-
-func TestDecodeFullNodeWrongSizeChild(t *testing.T) {
- fullNodeData := newTestFullNode([]byte("wrongsizechild"))
- fullNodeData[0] = []byte("00")
- buf := bytes.NewBuffer([]byte{})
- rlp.Encode(buf, fullNodeData)
-
- _, err := decodeNode([]byte("testdecode"), buf.Bytes())
- if _, ok := err.(*decodeError); !ok {
- t.Fatalf("decodeNode returned wrong err: %v", err)
- }
-}
-
-func TestDecodeFullNodeWrongNestedFullNode(t *testing.T) {
- fullNodeData := newTestFullNode([]byte("fullnode"))
-
- data := [][]byte{}
- for i := 0; i < 16; i++ {
- data = append(data, []byte("123456"))
- }
- data = append(data, []byte("subnode"))
- fullNodeData[15] = data
-
- buf := bytes.NewBuffer([]byte{})
- rlp.Encode(buf, fullNodeData)
-
- _, err := decodeNode([]byte("testdecode"), buf.Bytes())
- if _, ok := err.(*decodeError); !ok {
- t.Fatalf("decodeNode returned wrong err: %v", err)
- }
-}
-
-func TestDecodeFullNode(t *testing.T) {
- fullNodeData := newTestFullNode([]byte("decodefullnode"))
- buf := bytes.NewBuffer([]byte{})
- rlp.Encode(buf, fullNodeData)
-
- _, err := decodeNode([]byte("testdecode"), buf.Bytes())
- if err != nil {
- t.Fatalf("decode full node err: %v", err)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkEncodeShortNode
-// BenchmarkEncodeShortNode-8 16878850 70.81 ns/op 48 B/op 1 allocs/op
-func BenchmarkEncodeShortNode(b *testing.B) {
- node := &shortNode{
- Key: []byte{0x1, 0x2},
- Val: hashNode(randBytes(32)),
- }
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- nodeToBytes(node)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkEncodeFullNode
-// BenchmarkEncodeFullNode-8 4323273 284.4 ns/op 576 B/op 1 allocs/op
-func BenchmarkEncodeFullNode(b *testing.B) {
- node := &fullNode{}
- for i := 0; i < 16; i++ {
- node.Children[i] = hashNode(randBytes(32))
- }
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- nodeToBytes(node)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkDecodeShortNode
-// BenchmarkDecodeShortNode-8 7925638 151.0 ns/op 157 B/op 4 allocs/op
-func BenchmarkDecodeShortNode(b *testing.B) {
- node := &shortNode{
- Key: []byte{0x1, 0x2},
- Val: hashNode(randBytes(32)),
- }
- blob := nodeToBytes(node)
- hash := crypto.Keccak256(blob)
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- mustDecodeNode(hash, blob)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkDecodeShortNodeUnsafe
-// BenchmarkDecodeShortNodeUnsafe-8 9027476 128.6 ns/op 109 B/op 3 allocs/op
-func BenchmarkDecodeShortNodeUnsafe(b *testing.B) {
- node := &shortNode{
- Key: []byte{0x1, 0x2},
- Val: hashNode(randBytes(32)),
- }
- blob := nodeToBytes(node)
- hash := crypto.Keccak256(blob)
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- mustDecodeNodeUnsafe(hash, blob)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkDecodeFullNode
-// BenchmarkDecodeFullNode-8 1597462 761.9 ns/op 1280 B/op 18 allocs/op
-func BenchmarkDecodeFullNode(b *testing.B) {
- node := &fullNode{}
- for i := 0; i < 16; i++ {
- node.Children[i] = hashNode(randBytes(32))
- }
- blob := nodeToBytes(node)
- hash := crypto.Keccak256(blob)
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- mustDecodeNode(hash, blob)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkDecodeFullNodeUnsafe
-// BenchmarkDecodeFullNodeUnsafe-8 1789070 687.1 ns/op 704 B/op 17 allocs/op
-func BenchmarkDecodeFullNodeUnsafe(b *testing.B) {
- node := &fullNode{}
- for i := 0; i < 16; i++ {
- node.Children[i] = hashNode(randBytes(32))
- }
- blob := nodeToBytes(node)
- hash := crypto.Keccak256(blob)
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- mustDecodeNodeUnsafe(hash, blob)
- }
-}
diff --git a/trie/preimages.go b/trie/preimages.go
deleted file mode 100644
index 66f34117c1..0000000000
--- a/trie/preimages.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/ethdb"
-)
-
-// preimageStore is the store for caching preimages of node key.
-type preimageStore struct {
- lock sync.RWMutex
- disk ethdb.KeyValueStore
- preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
- preimagesSize common.StorageSize // Storage size of the preimages cache
-}
-
-// newPreimageStore initializes the store for caching preimages.
-func newPreimageStore(disk ethdb.KeyValueStore) *preimageStore {
- return &preimageStore{
- disk: disk,
- preimages: make(map[common.Hash][]byte),
- }
-}
-
-// insertPreimage writes a new trie node pre-image to the memory database if it's
-// yet unknown. The method will NOT make a copy of the slice, only use if the
-// preimage will NOT be changed later on.
-func (store *preimageStore) insertPreimage(preimages map[common.Hash][]byte) {
- store.lock.Lock()
- defer store.lock.Unlock()
-
- for hash, preimage := range preimages {
- if _, ok := store.preimages[hash]; ok {
- continue
- }
- store.preimages[hash] = preimage
- store.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
- }
-}
-
-// preimage retrieves a cached trie node pre-image from memory. If it cannot be
-// found cached, the method queries the persistent database for the content.
-func (store *preimageStore) preimage(hash common.Hash) []byte {
- store.lock.RLock()
- preimage := store.preimages[hash]
- store.lock.RUnlock()
-
- if preimage != nil {
- return preimage
- }
- return rawdb.ReadPreimage(store.disk, hash)
-}
-
-// commit flushes the cached preimages into the disk.
-func (store *preimageStore) commit(force bool) error {
- store.lock.Lock()
- defer store.lock.Unlock()
-
- if store.preimagesSize <= 4*1024*1024 && !force {
- return nil
- }
- batch := store.disk.NewBatch()
- rawdb.WritePreimages(batch, store.preimages)
- if err := batch.Write(); err != nil {
- return err
- }
- store.preimages, store.preimagesSize = make(map[common.Hash][]byte), 0
- return nil
-}
-
-// size returns the current storage size of accumulated preimages.
-func (store *preimageStore) size() common.StorageSize {
- store.lock.RLock()
- defer store.lock.RUnlock()
-
- return store.preimagesSize
-}
diff --git a/trie/proof.go b/trie/proof.go
deleted file mode 100644
index a526a53402..0000000000
--- a/trie/proof.go
+++ /dev/null
@@ -1,616 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// Prove constructs a merkle proof for key. The result contains all encoded nodes
-// on the path to the value at key. The value itself is also included in the last
-// node and can be retrieved by verifying the proof.
-//
-// If the trie does not contain a value for key, the returned proof contains all
-// nodes of the longest existing prefix of the key (at least the root node), ending
-// with the node that proves the absence of the key.
-func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return ErrCommitted
- }
- // Collect all nodes on the path to key.
- var (
- prefix []byte
- nodes []node
- tn = t.root
- )
- key = keybytesToHex(key)
- for len(key) > 0 && tn != nil {
- switch n := tn.(type) {
- case *shortNode:
- if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) {
- // The trie doesn't contain the key.
- tn = nil
- } else {
- tn = n.Val
- prefix = append(prefix, n.Key...)
- key = key[len(n.Key):]
- }
- nodes = append(nodes, n)
- case *fullNode:
- tn = n.Children[key[0]]
- prefix = append(prefix, key[0])
- key = key[1:]
- nodes = append(nodes, n)
- case hashNode:
- // Retrieve the specified node from the underlying node reader.
- // trie.resolveAndTrack is not used since in that function the
- // loaded blob will be tracked, while it's not required here since
- // all loaded nodes won't be linked to trie at all and track nodes
- // may lead to out-of-memory issue.
- blob, err := t.reader.node(prefix, common.BytesToHash(n))
- if err != nil {
- log.Error("Unhandled trie error in Trie.Prove", "err", err)
- return err
- }
- // The raw-blob format nodes are loaded either from the
- // clean cache or the database, they are all in their own
- // copy and safe to use unsafe decoder.
- tn = mustDecodeNodeUnsafe(n, blob)
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
- }
- }
- hasher := newHasher(false)
- defer returnHasherToPool(hasher)
-
- for i, n := range nodes {
- var hn node
- n, hn = hasher.proofHash(n)
- if hash, ok := hn.(hashNode); ok || i == 0 {
- // If the node's database encoding is a hash (or is the
- // root node), it becomes a proof element.
- enc := nodeToBytes(n)
- if !ok {
- hash = hasher.hashData(enc)
- }
- proofDb.Put(hash, enc)
- }
- }
- return nil
-}
-
-// Prove constructs a merkle proof for key. The result contains all encoded nodes
-// on the path to the value at key. The value itself is also included in the last
-// node and can be retrieved by verifying the proof.
-//
-// If the trie does not contain a value for key, the returned proof contains all
-// nodes of the longest existing prefix of the key (at least the root node), ending
-// with the node that proves the absence of the key.
-func (t *StateTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
- return t.trie.Prove(key, proofDb)
-}
-
-// VerifyProof checks merkle proofs. The given proof must contain the value for
-// key in a trie with the given root hash. VerifyProof returns an error if the
-// proof contains invalid trie nodes or the wrong value.
-func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) (value []byte, err error) {
- key = keybytesToHex(key)
- wantHash := rootHash
- for i := 0; ; i++ {
- buf, _ := proofDb.Get(wantHash[:])
- if buf == nil {
- return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash)
- }
- n, err := decodeNode(wantHash[:], buf)
- if err != nil {
- return nil, fmt.Errorf("bad proof node %d: %v", i, err)
- }
- keyrest, cld := get(n, key, true)
- switch cld := cld.(type) {
- case nil:
- // The trie doesn't contain the key.
- return nil, nil
- case hashNode:
- key = keyrest
- copy(wantHash[:], cld)
- case valueNode:
- return cld, nil
- }
- }
-}
-
-// proofToPath converts a merkle proof to trie node path. The main purpose of
-// this function is recovering a node path from the merkle proof stream. All
-// necessary nodes will be resolved and leave the remaining as hashnode.
-//
-// The given edge proof is allowed to be an existent or non-existent proof.
-func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyValueReader, allowNonExistent bool) (node, []byte, error) {
- // resolveNode retrieves and resolves trie node from merkle proof stream
- resolveNode := func(hash common.Hash) (node, error) {
- buf, _ := proofDb.Get(hash[:])
- if buf == nil {
- return nil, fmt.Errorf("proof node (hash %064x) missing", hash)
- }
- n, err := decodeNode(hash[:], buf)
- if err != nil {
- return nil, fmt.Errorf("bad proof node %v", err)
- }
- return n, err
- }
- // If the root node is empty, resolve it first.
- // Root node must be included in the proof.
- if root == nil {
- n, err := resolveNode(rootHash)
- if err != nil {
- return nil, nil, err
- }
- root = n
- }
- var (
- err error
- child, parent node
- keyrest []byte
- valnode []byte
- )
- key, parent = keybytesToHex(key), root
- for {
- keyrest, child = get(parent, key, false)
- switch cld := child.(type) {
- case nil:
- // The trie doesn't contain the key. It's possible
- // the proof is a non-existing proof, but at least
- // we can prove all resolved nodes are correct, it's
- // enough for us to prove range.
- if allowNonExistent {
- return root, nil, nil
- }
- return nil, nil, errors.New("the node is not contained in trie")
- case *shortNode:
- key, parent = keyrest, child // Already resolved
- continue
- case *fullNode:
- key, parent = keyrest, child // Already resolved
- continue
- case hashNode:
- child, err = resolveNode(common.BytesToHash(cld))
- if err != nil {
- return nil, nil, err
- }
- case valueNode:
- valnode = cld
- }
- // Link the parent and child.
- switch pnode := parent.(type) {
- case *shortNode:
- pnode.Val = child
- case *fullNode:
- pnode.Children[key[0]] = child
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", pnode, pnode))
- }
- if len(valnode) > 0 {
- return root, valnode, nil // The whole path is resolved
- }
- key, parent = keyrest, child
- }
-}
-
-// unsetInternal removes all internal node references(hashnode, embedded node).
-// It should be called after a trie is constructed with two edge paths. Also
-// the given boundary keys must be the one used to construct the edge paths.
-//
-// It's the key step for range proof. All visited nodes should be marked dirty
-// since the node content might be modified. Besides it can happen that some
-// fullnodes only have one child which is disallowed. But if the proof is valid,
-// the missing children will be filled, otherwise it will be thrown anyway.
-//
-// Note we have the assumption here the given boundary keys are different
-// and right is larger than left.
-func unsetInternal(n node, left []byte, right []byte) (bool, error) {
- left, right = keybytesToHex(left), keybytesToHex(right)
-
- // Step down to the fork point. There are two scenarios can happen:
- // - the fork point is a shortnode: either the key of left proof or
- // right proof doesn't match with shortnode's key.
- // - the fork point is a fullnode: both two edge proofs are allowed
- // to point to a non-existent key.
- var (
- pos = 0
- parent node
-
- // fork indicator, 0 means no fork, -1 means proof is less, 1 means proof is greater
- shortForkLeft, shortForkRight int
- )
-findFork:
- for {
- switch rn := (n).(type) {
- case *shortNode:
- rn.flags = nodeFlag{dirty: true}
-
- // If either the key of left proof or right proof doesn't match with
- // shortnode, stop here and the forkpoint is the shortnode.
- if len(left)-pos < len(rn.Key) {
- shortForkLeft = bytes.Compare(left[pos:], rn.Key)
- } else {
- shortForkLeft = bytes.Compare(left[pos:pos+len(rn.Key)], rn.Key)
- }
- if len(right)-pos < len(rn.Key) {
- shortForkRight = bytes.Compare(right[pos:], rn.Key)
- } else {
- shortForkRight = bytes.Compare(right[pos:pos+len(rn.Key)], rn.Key)
- }
- if shortForkLeft != 0 || shortForkRight != 0 {
- break findFork
- }
- parent = n
- n, pos = rn.Val, pos+len(rn.Key)
- case *fullNode:
- rn.flags = nodeFlag{dirty: true}
-
- // If either the node pointed by left proof or right proof is nil,
- // stop here and the forkpoint is the fullnode.
- leftnode, rightnode := rn.Children[left[pos]], rn.Children[right[pos]]
- if leftnode == nil || rightnode == nil || leftnode != rightnode {
- break findFork
- }
- parent = n
- n, pos = rn.Children[left[pos]], pos+1
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", n, n))
- }
- }
- switch rn := n.(type) {
- case *shortNode:
- // There can have these five scenarios:
- // - both proofs are less than the trie path => no valid range
- // - both proofs are greater than the trie path => no valid range
- // - left proof is less and right proof is greater => valid range, unset the shortnode entirely
- // - left proof points to the shortnode, but right proof is greater
- // - right proof points to the shortnode, but left proof is less
- if shortForkLeft == -1 && shortForkRight == -1 {
- return false, errors.New("empty range")
- }
- if shortForkLeft == 1 && shortForkRight == 1 {
- return false, errors.New("empty range")
- }
- if shortForkLeft != 0 && shortForkRight != 0 {
- // The fork point is root node, unset the entire trie
- if parent == nil {
- return true, nil
- }
- parent.(*fullNode).Children[left[pos-1]] = nil
- return false, nil
- }
- // Only one proof points to non-existent key.
- if shortForkRight != 0 {
- if _, ok := rn.Val.(valueNode); ok {
- // The fork point is root node, unset the entire trie
- if parent == nil {
- return true, nil
- }
- parent.(*fullNode).Children[left[pos-1]] = nil
- return false, nil
- }
- return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false)
- }
- if shortForkLeft != 0 {
- if _, ok := rn.Val.(valueNode); ok {
- // The fork point is root node, unset the entire trie
- if parent == nil {
- return true, nil
- }
- parent.(*fullNode).Children[right[pos-1]] = nil
- return false, nil
- }
- return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true)
- }
- return false, nil
- case *fullNode:
- // unset all internal nodes in the forkpoint
- for i := left[pos] + 1; i < right[pos]; i++ {
- rn.Children[i] = nil
- }
- if err := unset(rn, rn.Children[left[pos]], left[pos:], 1, false); err != nil {
- return false, err
- }
- if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil {
- return false, err
- }
- return false, nil
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", n, n))
- }
-}
-
-// unset removes all internal node references either the left most or right most.
-// It can meet these scenarios:
-//
-// - The given path is existent in the trie, unset the associated nodes with the
-// specific direction
-// - The given path is non-existent in the trie
-// - the fork point is a fullnode, the corresponding child pointed by path
-// is nil, return
-// - the fork point is a shortnode, the shortnode is included in the range,
-// keep the entire branch and return.
-// - the fork point is a shortnode, the shortnode is excluded in the range,
-// unset the entire branch.
-func unset(parent node, child node, key []byte, pos int, removeLeft bool) error {
- switch cld := child.(type) {
- case *fullNode:
- if removeLeft {
- for i := 0; i < int(key[pos]); i++ {
- cld.Children[i] = nil
- }
- cld.flags = nodeFlag{dirty: true}
- } else {
- for i := key[pos] + 1; i < 16; i++ {
- cld.Children[i] = nil
- }
- cld.flags = nodeFlag{dirty: true}
- }
- return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft)
- case *shortNode:
- if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) {
- // Find the fork point, it's an non-existent branch.
- if removeLeft {
- if bytes.Compare(cld.Key, key[pos:]) < 0 {
- // The key of fork shortnode is less than the path
- // (it belongs to the range), unset the entire
- // branch. The parent must be a fullnode.
- fn := parent.(*fullNode)
- fn.Children[key[pos-1]] = nil
- }
- //else {
- // The key of fork shortnode is greater than the
- // path(it doesn't belong to the range), keep
- // it with the cached hash available.
- //}
- } else {
- if bytes.Compare(cld.Key, key[pos:]) > 0 {
- // The key of fork shortnode is greater than the
- // path(it belongs to the range), unset the entrie
- // branch. The parent must be a fullnode.
- fn := parent.(*fullNode)
- fn.Children[key[pos-1]] = nil
- }
- //else {
- // The key of fork shortnode is less than the
- // path(it doesn't belong to the range), keep
- // it with the cached hash available.
- //}
- }
- return nil
- }
- if _, ok := cld.Val.(valueNode); ok {
- fn := parent.(*fullNode)
- fn.Children[key[pos-1]] = nil
- return nil
- }
- cld.flags = nodeFlag{dirty: true}
- return unset(cld, cld.Val, key, pos+len(cld.Key), removeLeft)
- case nil:
- // If the node is nil, then it's a child of the fork point
- // fullnode(it's a non-existent branch).
- return nil
- default:
- panic("it shouldn't happen") // hashNode, valueNode
- }
-}
-
-// hasRightElement returns the indicator whether there exists more elements
-// on the right side of the given path. The given path can point to an existent
-// key or a non-existent one. This function has the assumption that the whole
-// path should already be resolved.
-func hasRightElement(node node, key []byte) bool {
- pos, key := 0, keybytesToHex(key)
- for node != nil {
- switch rn := node.(type) {
- case *fullNode:
- for i := key[pos] + 1; i < 16; i++ {
- if rn.Children[i] != nil {
- return true
- }
- }
- node, pos = rn.Children[key[pos]], pos+1
- case *shortNode:
- if len(key)-pos < len(rn.Key) || !bytes.Equal(rn.Key, key[pos:pos+len(rn.Key)]) {
- return bytes.Compare(rn.Key, key[pos:]) > 0
- }
- node, pos = rn.Val, pos+len(rn.Key)
- case valueNode:
- return false // We have resolved the whole path
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", node, node)) // hashnode
- }
- }
- return false
-}
-
-// VerifyRangeProof checks whether the given leaf nodes and edge proof
-// can prove the given trie leaves range is matched with the specific root.
-// Besides, the range should be consecutive (no gap inside) and monotonic
-// increasing.
-//
-// Note the given proof actually contains two edge proofs. Both of them can
-// be non-existent proofs. For example the first proof is for a non-existent
-// key 0x03, the last proof is for a non-existent key 0x10. The given batch
-// leaves are [0x04, 0x05, .. 0x09]. It's still feasible to prove the given
-// batch is valid.
-//
-// The firstKey is paired with firstProof, not necessarily the same as keys[0]
-// (unless firstProof is an existent proof). Similarly, lastKey and lastProof
-// are paired.
-//
-// Expect the normal case, this function can also be used to verify the following
-// range proofs:
-//
-// - All elements proof. In this case the proof can be nil, but the range should
-// be all the leaves in the trie.
-//
-// - One element proof. In this case no matter the edge proof is a non-existent
-// proof or not, we can always verify the correctness of the proof.
-//
-// - Zero element proof. In this case a single non-existent proof is enough to prove.
-// Besides, if there are still some other leaves available on the right side, then
-// an error will be returned.
-//
-// Except returning the error to indicate the proof is valid or not, the function will
-// also return a flag to indicate whether there exists more accounts/slots in the trie.
-//
-// Note: This method does not verify that the proof is of minimal form. If the input
-// proofs are 'bloated' with neighbour leaves or random data, aside from the 'useful'
-// data, then the proof will still be accepted.
-func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) {
- if len(keys) != len(values) {
- return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
- }
- // Ensure the received batch is monotonic increasing and contains no deletions
- for i := 0; i < len(keys)-1; i++ {
- if bytes.Compare(keys[i], keys[i+1]) >= 0 {
- return false, errors.New("range is not monotonically increasing")
- }
- }
- for _, value := range values {
- if len(value) == 0 {
- return false, errors.New("range contains deletion")
- }
- }
- // Special case, there is no edge proof at all. The given range is expected
- // to be the whole leaf-set in the trie.
- if proof == nil {
- tr := NewStackTrie(nil)
- for index, key := range keys {
- tr.Update(key, values[index])
- }
- if have, want := tr.Hash(), rootHash; have != want {
- return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have)
- }
- return false, nil // No more elements
- }
- // Special case, there is a provided edge proof but zero key/value
- // pairs, ensure there are no more accounts / slots in the trie.
- if len(keys) == 0 {
- root, val, err := proofToPath(rootHash, nil, firstKey, proof, true)
- if err != nil {
- return false, err
- }
- if val != nil || hasRightElement(root, firstKey) {
- return false, errors.New("more entries available")
- }
- return false, nil
- }
- var lastKey = keys[len(keys)-1]
- // Special case, there is only one element and two edge keys are same.
- // In this case, we can't construct two edge paths. So handle it here.
- if len(keys) == 1 && bytes.Equal(firstKey, lastKey) {
- root, val, err := proofToPath(rootHash, nil, firstKey, proof, false)
- if err != nil {
- return false, err
- }
- if !bytes.Equal(firstKey, keys[0]) {
- return false, errors.New("correct proof but invalid key")
- }
- if !bytes.Equal(val, values[0]) {
- return false, errors.New("correct proof but invalid data")
- }
- return hasRightElement(root, firstKey), nil
- }
- // Ok, in all other cases, we require two edge paths available.
- // First check the validity of edge keys.
- if bytes.Compare(firstKey, lastKey) >= 0 {
- return false, errors.New("invalid edge keys")
- }
- // todo(rjl493456442) different length edge keys should be supported
- if len(firstKey) != len(lastKey) {
- return false, errors.New("inconsistent edge keys")
- }
- // Convert the edge proofs to edge trie paths. Then we can
- // have the same tree architecture with the original one.
- // For the first edge proof, non-existent proof is allowed.
- root, _, err := proofToPath(rootHash, nil, firstKey, proof, true)
- if err != nil {
- return false, err
- }
- // Pass the root node here, the second path will be merged
- // with the first one. For the last edge proof, non-existent
- // proof is also allowed.
- root, _, err = proofToPath(rootHash, root, lastKey, proof, true)
- if err != nil {
- return false, err
- }
- // Remove all internal references. All the removed parts should
- // be re-filled(or re-constructed) by the given leaves range.
- empty, err := unsetInternal(root, firstKey, lastKey)
- if err != nil {
- return false, err
- }
- // Rebuild the trie with the leaf stream, the shape of trie
- // should be same with the original one.
- tr := &Trie{root: root, reader: newEmptyReader(), tracer: newTracer()}
- if empty {
- tr.root = nil
- }
- for index, key := range keys {
- tr.Update(key, values[index])
- }
- if tr.Hash() != rootHash {
- return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
- }
- return hasRightElement(tr.root, keys[len(keys)-1]), nil
-}
-
-// get returns the child of the given node. Return nil if the
-// node with specified key doesn't exist at all.
-//
-// There is an additional flag `skipResolved`. If it's set then
-// all resolved nodes won't be returned.
-func get(tn node, key []byte, skipResolved bool) ([]byte, node) {
- for {
- switch n := tn.(type) {
- case *shortNode:
- if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) {
- return nil, nil
- }
- tn = n.Val
- key = key[len(n.Key):]
- if !skipResolved {
- return key, tn
- }
- case *fullNode:
- tn = n.Children[key[0]]
- key = key[1:]
- if !skipResolved {
- return key, tn
- }
- case hashNode:
- return key, n
- case nil:
- return key, nil
- case valueNode:
- return nil, n
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
- }
- }
-}
diff --git a/trie/proof_test.go b/trie/proof_test.go
deleted file mode 100644
index 59ae201cea..0000000000
--- a/trie/proof_test.go
+++ /dev/null
@@ -1,1002 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- crand "crypto/rand"
- "encoding/binary"
- "fmt"
- mrand "math/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "golang.org/x/exp/slices"
-)
-
-// Prng is a pseudo random number generator seeded by strong randomness.
-// The randomness is printed on startup in order to make failures reproducible.
-var prng = initRnd()
-
-func initRnd() *mrand.Rand {
- var seed [8]byte
- crand.Read(seed[:])
- rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:]))))
- fmt.Printf("Seed: %x\n", seed)
- return rnd
-}
-
-func randBytes(n int) []byte {
- r := make([]byte, n)
- prng.Read(r)
- return r
-}
-
-// makeProvers creates Merkle trie provers based on different implementations to
-// test all variations.
-func makeProvers(trie *Trie) []func(key []byte) *memorydb.Database {
- var provers []func(key []byte) *memorydb.Database
-
- // Create a direct trie based Merkle prover
- provers = append(provers, func(key []byte) *memorydb.Database {
- proof := memorydb.New()
- trie.Prove(key, proof)
- return proof
- })
- // Create a leaf iterator based Merkle prover
- provers = append(provers, func(key []byte) *memorydb.Database {
- proof := memorydb.New()
- if it := NewIterator(trie.MustNodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) {
- for _, p := range it.Prove() {
- proof.Put(crypto.Keccak256(p), p)
- }
- }
- return proof
- })
- return provers
-}
-
-func TestProof(t *testing.T) {
- trie, vals := randomTrie(500)
- root := trie.Hash()
- for i, prover := range makeProvers(trie) {
- for _, kv := range vals {
- proof := prover(kv.k)
- if proof == nil {
- t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k)
- }
- val, err := VerifyProof(root, kv.k, proof)
- if err != nil {
- t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof)
- }
- if !bytes.Equal(val, kv.v) {
- t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v)
- }
- }
- }
-}
-
-func TestOneElementProof(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- updateString(trie, "k", "v")
- for i, prover := range makeProvers(trie) {
- proof := prover([]byte("k"))
- if proof == nil {
- t.Fatalf("prover %d: nil proof", i)
- }
- if proof.Len() != 1 {
- t.Errorf("prover %d: proof should have one element", i)
- }
- val, err := VerifyProof(trie.Hash(), []byte("k"), proof)
- if err != nil {
- t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
- }
- if !bytes.Equal(val, []byte("v")) {
- t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val)
- }
- }
-}
-
-func TestBadProof(t *testing.T) {
- trie, vals := randomTrie(800)
- root := trie.Hash()
- for i, prover := range makeProvers(trie) {
- for _, kv := range vals {
- proof := prover(kv.k)
- if proof == nil {
- t.Fatalf("prover %d: nil proof", i)
- }
- it := proof.NewIterator(nil, nil)
- for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ {
- it.Next()
- }
- key := it.Key()
- val, _ := proof.Get(key)
- proof.Delete(key)
- it.Release()
-
- mutateByte(val)
- proof.Put(crypto.Keccak256(val), val)
-
- if _, err := VerifyProof(root, kv.k, proof); err == nil {
- t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k)
- }
- }
- }
-}
-
-// Tests that missing keys can also be proven. The test explicitly uses a single
-// entry trie and checks for missing keys both before and after the single entry.
-func TestMissingKeyProof(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- updateString(trie, "k", "v")
-
- for i, key := range []string{"a", "j", "l", "z"} {
- proof := memorydb.New()
- trie.Prove([]byte(key), proof)
-
- if proof.Len() != 1 {
- t.Errorf("test %d: proof should have one element", i)
- }
- val, err := VerifyProof(trie.Hash(), []byte(key), proof)
- if err != nil {
- t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
- }
- if val != nil {
- t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val)
- }
- }
-}
-
-// TestRangeProof tests normal range proof with both edge proofs
-// as the existent proof. The test cases are generated randomly.
-func TestRangeProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
- for i := 0; i < 500; i++ {
- start := mrand.Intn(len(entries))
- end := mrand.Intn(len(entries)-start) + start + 1
-
- proof := memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var vals [][]byte
- for i := start; i < end; i++ {
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof)
- if err != nil {
- t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
- }
- }
-}
-
-// TestRangeProof tests normal range proof with two non-existent proofs.
-// The test cases are generated randomly.
-func TestRangeProofWithNonExistentProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
- for i := 0; i < 500; i++ {
- start := mrand.Intn(len(entries))
- end := mrand.Intn(len(entries)-start) + start + 1
- proof := memorydb.New()
-
- // Short circuit if the decreased key is same with the previous key
- first := decreaseKey(common.CopyBytes(entries[start].k))
- if start != 0 && bytes.Equal(first, entries[start-1].k) {
- continue
- }
- // Short circuit if the decreased key is underflow
- if bytes.Compare(first, entries[start].k) > 0 {
- continue
- }
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var vals [][]byte
- for i := start; i < end; i++ {
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), first, keys, vals, proof)
- if err != nil {
- t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
- }
- }
-}
-
-// TestRangeProofWithInvalidNonExistentProof tests such scenarios:
-// - There exists a gap between the first element and the left edge proof
-func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Case 1
- start, end := 100, 200
- first := decreaseKey(common.CopyBytes(entries[start].k))
-
- proof := memorydb.New()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- start = 105 // Gap created
- k := make([][]byte, 0)
- v := make([][]byte, 0)
- for i := start; i < end; i++ {
- k = append(k, entries[i].k)
- v = append(v, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), first, k, v, proof)
- if err == nil {
- t.Fatalf("Expected to detect the error, got nil")
- }
-}
-
-// TestOneElementRangeProof tests the proof with only one
-// element. The first edge proof can be existent one or
-// non-existent one.
-func TestOneElementRangeProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // One element with existent edge proof, both edge proofs
- // point to the SAME key.
- start := 1000
- proof := memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- _, err := VerifyRangeProof(trie.Hash(), entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // One element with left non-existent edge proof
- start = 1000
- first := decreaseKey(common.CopyBytes(entries[start].k))
- proof = memorydb.New()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // One element with right non-existent edge proof
- start = 1000
- last := increaseKey(common.CopyBytes(entries[start].k))
- proof = memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(last, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // One element with two non-existent edge proofs
- start = 1000
- first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].k))
- proof = memorydb.New()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(last, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // Test the mini trie with only a single element.
- tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- entry := &kv{randBytes(32), randBytes(20), false}
- tinyTrie.MustUpdate(entry.k, entry.v)
-
- first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes()
- last = entry.k
- proof = memorydb.New()
- if err := tinyTrie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := tinyTrie.Prove(last, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(tinyTrie.Hash(), first, [][]byte{entry.k}, [][]byte{entry.v}, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-}
-
-// TestAllElementsProof tests the range proof with all elements.
-// The edge proofs can be nil.
-func TestAllElementsProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var k [][]byte
- var v [][]byte
- for i := 0; i < len(entries); i++ {
- k = append(k, entries[i].k)
- v = append(v, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), nil, k, v, nil)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // With edge proofs, it should still work.
- proof := memorydb.New()
- if err := trie.Prove(entries[0].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[len(entries)-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), k[0], k, v, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-
- // Even with non-existent edge proofs, it should still work.
- proof = memorydb.New()
- first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[len(entries)-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), first, k, v, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
-}
-
-// TestSingleSideRangeProof tests the range starts from zero.
-func TestSingleSideRangeProof(t *testing.T) {
- for i := 0; i < 64; i++ {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- var entries []*kv
- for i := 0; i < 4096; i++ {
- value := &kv{randBytes(32), randBytes(20), false}
- trie.MustUpdate(value.k, value.v)
- entries = append(entries, value)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1}
- for _, pos := range cases {
- proof := memorydb.New()
- if err := trie.Prove(common.Hash{}.Bytes(), proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[pos].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- k := make([][]byte, 0)
- v := make([][]byte, 0)
- for i := 0; i <= pos; i++ {
- k = append(k, entries[i].k)
- v = append(v, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k, v, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
- }
- }
-}
-
-// TestBadRangeProof tests a few cases which the proof is wrong.
-// The prover is expected to detect the error.
-func TestBadRangeProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- for i := 0; i < 500; i++ {
- start := mrand.Intn(len(entries))
- end := mrand.Intn(len(entries)-start) + start + 1
- proof := memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var vals [][]byte
- for i := start; i < end; i++ {
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- var first = keys[0]
- testcase := mrand.Intn(6)
- var index int
- switch testcase {
- case 0:
- // Modified key
- index = mrand.Intn(end - start)
- keys[index] = randBytes(32) // In theory it can't be same
- case 1:
- // Modified val
- index = mrand.Intn(end - start)
- vals[index] = randBytes(20) // In theory it can't be same
- case 2:
- // Gapped entry slice
- index = mrand.Intn(end - start)
- if (index == 0 && start < 100) || (index == end-start-1) {
- continue
- }
- keys = append(keys[:index], keys[index+1:]...)
- vals = append(vals[:index], vals[index+1:]...)
- case 3:
- // Out of order
- index1 := mrand.Intn(end - start)
- index2 := mrand.Intn(end - start)
- if index1 == index2 {
- continue
- }
- keys[index1], keys[index2] = keys[index2], keys[index1]
- vals[index1], vals[index2] = vals[index2], vals[index1]
- case 4:
- // Set random key to nil, do nothing
- index = mrand.Intn(end - start)
- keys[index] = nil
- case 5:
- // Set random value to nil, deletion
- index = mrand.Intn(end - start)
- vals[index] = nil
- }
- _, err := VerifyRangeProof(trie.Hash(), first, keys, vals, proof)
- if err == nil {
- t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1)
- }
- }
-}
-
-// TestGappedRangeProof focuses on the small trie with embedded nodes.
-// If the gapped node is embedded in the trie, it should be detected too.
-func TestGappedRangeProof(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- var entries []*kv // Sorted entries
- for i := byte(0); i < 10; i++ {
- value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
- trie.MustUpdate(value.k, value.v)
- entries = append(entries, value)
- }
- first, last := 2, 8
- proof := memorydb.New()
- if err := trie.Prove(entries[first].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[last-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var vals [][]byte
- for i := first; i < last; i++ {
- if i == (first+last)/2 {
- continue
- }
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof)
- if err == nil {
- t.Fatal("expect error, got nil")
- }
-}
-
-// TestSameSideProofs tests the element is not in the range covered by proofs
-func TestSameSideProofs(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- pos := 1000
- first := common.CopyBytes(entries[0].k)
-
- proof := memorydb.New()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[2000].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- _, err := VerifyRangeProof(trie.Hash(), first, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
- if err == nil {
- t.Fatalf("Expected error, got nil")
- }
-
- first = increaseKey(common.CopyBytes(entries[pos].k))
- last := increaseKey(common.CopyBytes(entries[pos].k))
- last = increaseKey(last)
-
- proof = memorydb.New()
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(last, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
- if err == nil {
- t.Fatalf("Expected error, got nil")
- }
-}
-
-func TestHasRightElement(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- var entries []*kv
- for i := 0; i < 4096; i++ {
- value := &kv{randBytes(32), randBytes(20), false}
- trie.MustUpdate(value.k, value.v)
- entries = append(entries, value)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var cases = []struct {
- start int
- end int
- hasMore bool
- }{
- {-1, 1, true}, // single element with non-existent left proof
- {0, 1, true}, // single element with existent left proof
- {0, 10, true},
- {50, 100, true},
- {50, len(entries), false}, // No more element expected
- {len(entries) - 1, len(entries), false}, // Single last element with two existent proofs(point to same key)
- {0, len(entries), false}, // The whole set with existent left proof
- {-1, len(entries), false}, // The whole set with non-existent left proof
- }
- for _, c := range cases {
- var (
- firstKey []byte
- start = c.start
- end = c.end
- proof = memorydb.New()
- )
- if c.start == -1 {
- firstKey, start = common.Hash{}.Bytes(), 0
- if err := trie.Prove(firstKey, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- } else {
- firstKey = entries[c.start].k
- if err := trie.Prove(entries[c.start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- }
- if err := trie.Prove(entries[c.end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- k := make([][]byte, 0)
- v := make([][]byte, 0)
- for i := start; i < end; i++ {
- k = append(k, entries[i].k)
- v = append(v, entries[i].v)
- }
- hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, k, v, proof)
- if err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
- if hasMore != c.hasMore {
- t.Fatalf("Wrong hasMore indicator, want %t, got %t", c.hasMore, hasMore)
- }
- }
-}
-
-// TestEmptyRangeProof tests the range proof with "no" element.
-// The first edge proof must be a non-existent proof.
-func TestEmptyRangeProof(t *testing.T) {
- trie, vals := randomTrie(4096)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var cases = []struct {
- pos int
- err bool
- }{
- {len(entries) - 1, false},
- {500, true},
- }
- for _, c := range cases {
- proof := memorydb.New()
- first := increaseKey(common.CopyBytes(entries[c.pos].k))
- if err := trie.Prove(first, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, proof)
- if c.err && err == nil {
- t.Fatalf("Expected error, got nil")
- }
- if !c.err && err != nil {
- t.Fatalf("Expected no error, got %v", err)
- }
- }
-}
-
-// TestBloatedProof tests a malicious proof, where the proof is more or less the
-// whole trie. Previously we didn't accept such packets, but the new APIs do, so
-// lets leave this test as a bit weird, but present.
-func TestBloatedProof(t *testing.T) {
- // Use a small trie
- trie, kvs := nonRandomTrie(100)
- var entries []*kv
- for _, kv := range kvs {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
- var keys [][]byte
- var vals [][]byte
-
- proof := memorydb.New()
- // In the 'malicious' case, we add proofs for every single item
- // (but only one key/value pair used as leaf)
- for i, entry := range entries {
- trie.Prove(entry.k, proof)
- if i == 50 {
- keys = append(keys, entry.k)
- vals = append(vals, entry.v)
- }
- }
- // For reference, we use the same function, but _only_ prove the first
- // and last element
- want := memorydb.New()
- trie.Prove(keys[0], want)
- trie.Prove(keys[len(keys)-1], want)
-
- if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof); err != nil {
- t.Fatalf("expected bloated proof to succeed, got %v", err)
- }
-}
-
-// TestEmptyValueRangeProof tests normal range proof with both edge proofs
-// as the existent proof, but with an extra empty value included, which is a
-// noop technically, but practically should be rejected.
-func TestEmptyValueRangeProof(t *testing.T) {
- trie, values := randomTrie(512)
- var entries []*kv
- for _, kv := range values {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Create a new entry with a slightly modified key
- mid := len(entries) / 2
- key := common.CopyBytes(entries[mid-1].k)
- for n := len(key) - 1; n >= 0; n-- {
- if key[n] < 0xff {
- key[n]++
- break
- }
- }
- noop := &kv{key, []byte{}, false}
- entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...)
-
- start, end := 1, len(entries)-1
-
- proof := memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- t.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- t.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var vals [][]byte
- for i := start; i < end; i++ {
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof)
- if err == nil {
- t.Fatalf("Expected failure on noop entry")
- }
-}
-
-// TestAllElementsEmptyValueRangeProof tests the range proof with all elements,
-// but with an extra empty value included, which is a noop technically, but
-// practically should be rejected.
-func TestAllElementsEmptyValueRangeProof(t *testing.T) {
- trie, values := randomTrie(512)
- var entries []*kv
- for _, kv := range values {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- // Create a new entry with a slightly modified key
- mid := len(entries) / 2
- key := common.CopyBytes(entries[mid-1].k)
- for n := len(key) - 1; n >= 0; n-- {
- if key[n] < 0xff {
- key[n]++
- break
- }
- }
- noop := &kv{key, []byte{}, false}
- entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...)
-
- var keys [][]byte
- var vals [][]byte
- for i := 0; i < len(entries); i++ {
- keys = append(keys, entries[i].k)
- vals = append(vals, entries[i].v)
- }
- _, err := VerifyRangeProof(trie.Hash(), nil, keys, vals, nil)
- if err == nil {
- t.Fatalf("Expected failure on noop entry")
- }
-}
-
-// mutateByte changes one byte in b.
-func mutateByte(b []byte) {
- for r := mrand.Intn(len(b)); ; {
- new := byte(mrand.Intn(255))
- if new != b[r] {
- b[r] = new
- break
- }
- }
-}
-
-func increaseKey(key []byte) []byte {
- for i := len(key) - 1; i >= 0; i-- {
- key[i]++
- if key[i] != 0x0 {
- break
- }
- }
- return key
-}
-
-func decreaseKey(key []byte) []byte {
- for i := len(key) - 1; i >= 0; i-- {
- key[i]--
- if key[i] != 0xff {
- break
- }
- }
- return key
-}
-
-func BenchmarkProve(b *testing.B) {
- trie, vals := randomTrie(100)
- var keys []string
- for k := range vals {
- keys = append(keys, k)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- kv := vals[keys[i%len(keys)]]
- proofs := memorydb.New()
- if trie.Prove(kv.k, proofs); proofs.Len() == 0 {
- b.Fatalf("zero length proof for %x", kv.k)
- }
- }
-}
-
-func BenchmarkVerifyProof(b *testing.B) {
- trie, vals := randomTrie(100)
- root := trie.Hash()
- var keys []string
- var proofs []*memorydb.Database
- for k := range vals {
- keys = append(keys, k)
- proof := memorydb.New()
- trie.Prove([]byte(k), proof)
- proofs = append(proofs, proof)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- im := i % len(keys)
- if _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil {
- b.Fatalf("key %x: %v", keys[im], err)
- }
- }
-}
-
-func BenchmarkVerifyRangeProof10(b *testing.B) { benchmarkVerifyRangeProof(b, 10) }
-func BenchmarkVerifyRangeProof100(b *testing.B) { benchmarkVerifyRangeProof(b, 100) }
-func BenchmarkVerifyRangeProof1000(b *testing.B) { benchmarkVerifyRangeProof(b, 1000) }
-func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, 5000) }
-
-func benchmarkVerifyRangeProof(b *testing.B, size int) {
- trie, vals := randomTrie(8192)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- start := 2
- end := start + size
- proof := memorydb.New()
- if err := trie.Prove(entries[start].k, proof); err != nil {
- b.Fatalf("Failed to prove the first node %v", err)
- }
- if err := trie.Prove(entries[end-1].k, proof); err != nil {
- b.Fatalf("Failed to prove the last node %v", err)
- }
- var keys [][]byte
- var values [][]byte
- for i := start; i < end; i++ {
- keys = append(keys, entries[i].k)
- values = append(values, entries[i].v)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, proof)
- if err != nil {
- b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
- }
- }
-}
-
-func BenchmarkVerifyRangeNoProof10(b *testing.B) { benchmarkVerifyRangeNoProof(b, 100) }
-func BenchmarkVerifyRangeNoProof500(b *testing.B) { benchmarkVerifyRangeNoProof(b, 500) }
-func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof(b, 1000) }
-
-func benchmarkVerifyRangeNoProof(b *testing.B, size int) {
- trie, vals := randomTrie(size)
- var entries []*kv
- for _, kv := range vals {
- entries = append(entries, kv)
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var keys [][]byte
- var values [][]byte
- for _, entry := range entries {
- keys = append(keys, entry.k)
- values = append(values, entry.v)
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, nil)
- if err != nil {
- b.Fatalf("Expected no error, got %v", err)
- }
- }
-}
-
-func randomTrie(n int) (*Trie, map[string]*kv) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- vals := make(map[string]*kv)
- for i := byte(0); i < 100; i++ {
- value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
- value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false}
- trie.MustUpdate(value.k, value.v)
- trie.MustUpdate(value2.k, value2.v)
- vals[string(value.k)] = value
- vals[string(value2.k)] = value2
- }
- for i := 0; i < n; i++ {
- value := &kv{randBytes(32), randBytes(20), false}
- trie.MustUpdate(value.k, value.v)
- vals[string(value.k)] = value
- }
- return trie, vals
-}
-
-func nonRandomTrie(n int) (*Trie, map[string]*kv) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- vals := make(map[string]*kv)
- max := uint64(0xffffffffffffffff)
- for i := uint64(0); i < uint64(n); i++ {
- value := make([]byte, 32)
- key := make([]byte, 32)
- binary.LittleEndian.PutUint64(key, i)
- binary.LittleEndian.PutUint64(value, i-max)
- //value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
- elem := &kv{key, value, false}
- trie.MustUpdate(elem.k, elem.v)
- vals[string(elem.k)] = elem
- }
- return trie, vals
-}
-
-func TestRangeProofKeysWithSharedPrefix(t *testing.T) {
- keys := [][]byte{
- common.Hex2Bytes("aa10000000000000000000000000000000000000000000000000000000000000"),
- common.Hex2Bytes("aa20000000000000000000000000000000000000000000000000000000000000"),
- }
- vals := [][]byte{
- common.Hex2Bytes("02"),
- common.Hex2Bytes("03"),
- }
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for i, key := range keys {
- trie.MustUpdate(key, vals[i])
- }
- root := trie.Hash()
- proof := memorydb.New()
- start := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")
- if err := trie.Prove(start, proof); err != nil {
- t.Fatalf("failed to prove start: %v", err)
- }
- if err := trie.Prove(keys[len(keys)-1], proof); err != nil {
- t.Fatalf("failed to prove end: %v", err)
- }
-
- more, err := VerifyRangeProof(root, start, keys, vals, proof)
- if err != nil {
- t.Fatalf("failed to verify range proof: %v", err)
- }
- if more != false {
- t.Error("expected more to be false")
- }
-}
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
deleted file mode 100644
index 7f0685e306..0000000000
--- a/trie/secure_trie.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// SecureTrie is the old name of StateTrie.
-// Deprecated: use StateTrie.
-type SecureTrie = StateTrie
-
-// NewSecure creates a new StateTrie.
-// Deprecated: use NewStateTrie.
-func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) {
- id := &ID{
- StateRoot: stateRoot,
- Owner: owner,
- Root: root,
- }
- return NewStateTrie(id, db)
-}
-
-// StateTrie wraps a trie with key hashing. In a stateTrie trie, all
-// access operations hash the key using keccak256. This prevents
-// calling code from creating long chains of nodes that
-// increase the access time.
-//
-// Contrary to a regular trie, a StateTrie can only be created with
-// New and must have an attached database. The database also stores
-// the preimage of each key if preimage recording is enabled.
-//
-// StateTrie is not safe for concurrent use.
-type StateTrie struct {
- trie Trie
- preimages *preimageStore
- hashKeyBuf [common.HashLength]byte
- secKeyCache map[string][]byte
- secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch
-}
-
-// NewStateTrie creates a trie with an existing root node from a backing database.
-//
-// If root is the zero hash or the sha3 hash of an empty string, the
-// trie is initially empty. Otherwise, New will panic if db is nil
-// and returns MissingNodeError if the root node cannot be found.
-func NewStateTrie(id *ID, db *Database) (*StateTrie, error) {
- if db == nil {
- panic("trie.NewStateTrie called without a database")
- }
- trie, err := New(id, db)
- if err != nil {
- return nil, err
- }
- return &StateTrie{trie: *trie, preimages: db.preimages}, nil
-}
-
-// MustGet returns the value for key stored in the trie.
-// The value bytes must not be modified by the caller.
-//
-// This function will omit any encountered error but just
-// print out an error message.
-func (t *StateTrie) MustGet(key []byte) []byte {
- return t.trie.MustGet(t.hashKey(key))
-}
-
-// GetStorage attempts to retrieve a storage slot with provided account address
-// and slot key. The value bytes must not be modified by the caller.
-// If the specified storage slot is not in the trie, nil will be returned.
-// If a trie node is not found in the database, a MissingNodeError is returned.
-func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
- enc, err := t.trie.Get(t.hashKey(key))
- if err != nil || len(enc) == 0 {
- return nil, err
- }
- _, content, _, err := rlp.Split(enc)
- return content, err
-}
-
-// GetAccount attempts to retrieve an account with provided account address.
-// If the specified account is not in the trie, nil will be returned.
-// If a trie node is not found in the database, a MissingNodeError is returned.
-func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
- res, err := t.trie.Get(t.hashKey(address.Bytes()))
- if res == nil || err != nil {
- return nil, err
- }
- ret := new(types.StateAccount)
- err = rlp.DecodeBytes(res, ret)
- return ret, err
-}
-
-// GetAccountByHash does the same thing as GetAccount, however it expects an
-// account hash that is the hash of address. This constitutes an abstraction
-// leak, since the client code needs to know the key format.
-func (t *StateTrie) GetAccountByHash(addrHash common.Hash) (*types.StateAccount, error) {
- res, err := t.trie.Get(addrHash.Bytes())
- if res == nil || err != nil {
- return nil, err
- }
- ret := new(types.StateAccount)
- err = rlp.DecodeBytes(res, ret)
- return ret, err
-}
-
-// GetNode attempts to retrieve a trie node by compact-encoded path. It is not
-// possible to use keybyte-encoding as the path might contain odd nibbles.
-// If the specified trie node is not in the trie, nil will be returned.
-// If a trie node is not found in the database, a MissingNodeError is returned.
-func (t *StateTrie) GetNode(path []byte) ([]byte, int, error) {
- return t.trie.GetNode(path)
-}
-
-// MustUpdate associates key with value in the trie. Subsequent calls to
-// Get will return value. If value has length zero, any existing value
-// is deleted from the trie and calls to Get will return nil.
-//
-// The value bytes must not be modified by the caller while they are
-// stored in the trie.
-//
-// This function will omit any encountered error but just print out an
-// error message.
-func (t *StateTrie) MustUpdate(key, value []byte) {
- hk := t.hashKey(key)
- t.trie.MustUpdate(hk, value)
- t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
-}
-
-// UpdateStorage associates key with value in the trie. Subsequent calls to
-// Get will return value. If value has length zero, any existing value
-// is deleted from the trie and calls to Get will return nil.
-//
-// The value bytes must not be modified by the caller while they are
-// stored in the trie.
-//
-// If a node is not found in the database, a MissingNodeError is returned.
-func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
- hk := t.hashKey(key)
- v, _ := rlp.EncodeToBytes(value)
- err := t.trie.Update(hk, v)
- if err != nil {
- return err
- }
- t.getSecKeyCache()[string(hk)] = common.CopyBytes(key)
- return nil
-}
-
-// UpdateAccount will abstract the write of an account to the secure trie.
-func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error {
- hk := t.hashKey(address.Bytes())
- data, err := rlp.EncodeToBytes(acc)
- if err != nil {
- return err
- }
- if err := t.trie.Update(hk, data); err != nil {
- return err
- }
- t.getSecKeyCache()[string(hk)] = address.Bytes()
- return nil
-}
-
-func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
- return nil
-}
-
-// MustDelete removes any existing value for key from the trie. This function
-// will omit any encountered error but just print out an error message.
-func (t *StateTrie) MustDelete(key []byte) {
- hk := t.hashKey(key)
- delete(t.getSecKeyCache(), string(hk))
- t.trie.MustDelete(hk)
-}
-
-// DeleteStorage removes any existing storage slot from the trie.
-// If the specified trie node is not in the trie, nothing will be changed.
-// If a node is not found in the database, a MissingNodeError is returned.
-func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error {
- hk := t.hashKey(key)
- delete(t.getSecKeyCache(), string(hk))
- return t.trie.Delete(hk)
-}
-
-// DeleteAccount abstracts an account deletion from the trie.
-func (t *StateTrie) DeleteAccount(address common.Address) error {
- hk := t.hashKey(address.Bytes())
- delete(t.getSecKeyCache(), string(hk))
- return t.trie.Delete(hk)
-}
-
-// GetKey returns the sha3 preimage of a hashed key that was
-// previously used to store a value.
-func (t *StateTrie) GetKey(shaKey []byte) []byte {
- if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
- return key
- }
- if t.preimages == nil {
- return nil
- }
- return t.preimages.preimage(common.BytesToHash(shaKey))
-}
-
-// Commit collects all dirty nodes in the trie and replaces them with the
-// corresponding node hash. All collected nodes (including dirty leaves if
-// collectLeaf is true) will be encapsulated into a nodeset for return.
-// The returned nodeset can be nil if the trie is clean (nothing to commit).
-// All cached preimages will be also flushed if preimages recording is enabled.
-// Once the trie is committed, it's not usable anymore. A new trie must
-// be created with new root and updated trie database for following usage
-func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
- // Write all the pre-images to the actual disk database
- if len(t.getSecKeyCache()) > 0 {
- if t.preimages != nil {
- preimages := make(map[common.Hash][]byte)
- for hk, key := range t.secKeyCache {
- preimages[common.BytesToHash([]byte(hk))] = key
- }
- t.preimages.insertPreimage(preimages)
- }
- t.secKeyCache = make(map[string][]byte)
- }
- // Commit the trie and return its modified nodeset.
- return t.trie.Commit(collectLeaf)
-}
-
-// Hash returns the root hash of StateTrie. It does not write to the
-// database and can be used even if the trie doesn't have one.
-func (t *StateTrie) Hash() common.Hash {
- return t.trie.Hash()
-}
-
-// Copy returns a copy of StateTrie.
-func (t *StateTrie) Copy() *StateTrie {
- return &StateTrie{
- trie: *t.trie.Copy(),
- preimages: t.preimages,
- secKeyCache: t.secKeyCache,
- }
-}
-
-// NodeIterator returns an iterator that returns nodes of the underlying trie.
-// Iteration starts at the key after the given start key.
-func (t *StateTrie) NodeIterator(start []byte) (NodeIterator, error) {
- return t.trie.NodeIterator(start)
-}
-
-// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered
-// error but just print out an error message.
-func (t *StateTrie) MustNodeIterator(start []byte) NodeIterator {
- return t.trie.MustNodeIterator(start)
-}
-
-// hashKey returns the hash of key as an ephemeral buffer.
-// The caller must not hold onto the return value because it will become
-// invalid on the next call to hashKey or secKey.
-func (t *StateTrie) hashKey(key []byte) []byte {
- h := newHasher(false)
- h.sha.Reset()
- h.sha.Write(key)
- h.sha.Read(t.hashKeyBuf[:])
- returnHasherToPool(h)
- return t.hashKeyBuf[:]
-}
-
-// getSecKeyCache returns the current secure key cache, creating a new one if
-// ownership changed (i.e. the current secure trie is a copy of another owning
-// the actual cache).
-func (t *StateTrie) getSecKeyCache() map[string][]byte {
- if t != t.secKeyCacheOwner {
- t.secKeyCacheOwner = t
- t.secKeyCache = make(map[string][]byte)
- }
- return t.secKeyCache
-}
diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go
deleted file mode 100644
index 2087866d38..0000000000
--- a/trie/secure_trie_test.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "fmt"
- "runtime"
- "sync"
- "testing"
-
- "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/trie/trienode"
-)
-
-func newEmptySecure() *StateTrie {
- trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), NewDatabase(rawdb.NewMemoryDatabase(), nil))
- return trie
-}
-
-// makeTestStateTrie creates a large enough secure trie for testing.
-func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
- // Create an empty trie
- triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb)
-
- // Fill it with some arbitrary data
- content := make(map[string][]byte)
- for i := byte(0); i < 255; i++ {
- // Map the same data under multiple keys
- key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
-
- key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
-
- // Add some other data to inflate the trie
- for j := byte(3); j < 13; j++ {
- key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
- }
- }
- root, nodes, _ := trie.Commit(false)
- if err := triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- panic(fmt.Errorf("failed to commit db %v", err))
- }
- // Re-create the trie based on the new state
- trie, _ = NewStateTrie(TrieID(root), triedb)
- return triedb, trie, content
-}
-
-func TestSecureDelete(t *testing.T) {
- trie := newEmptySecure()
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"ether", ""},
- {"dog", "puppy"},
- {"shaman", ""},
- }
- for _, val := range vals {
- if val.v != "" {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- } else {
- trie.MustDelete([]byte(val.k))
- }
- }
- hash := trie.Hash()
- exp := common.HexToHash("29b235a58c3c25ab83010c327d5932bcf05324b7d6b1185e650798034783ca9d")
- if hash != exp {
- t.Errorf("expected %x got %x", exp, hash)
- }
-}
-
-func TestSecureGetKey(t *testing.T) {
- trie := newEmptySecure()
- trie.MustUpdate([]byte("foo"), []byte("bar"))
-
- key := []byte("foo")
- value := []byte("bar")
- seckey := crypto.Keccak256(key)
-
- if !bytes.Equal(trie.MustGet(key), value) {
- t.Errorf("Get did not return bar")
- }
- if k := trie.GetKey(seckey); !bytes.Equal(k, key) {
- t.Errorf("GetKey returned %q, want %q", k, key)
- }
-}
-
-func TestStateTrieConcurrency(t *testing.T) {
- // Create an initial trie and copy if for concurrent access
- _, trie, _ := makeTestStateTrie()
-
- threads := runtime.NumCPU()
- tries := make([]*StateTrie, threads)
- for i := 0; i < threads; i++ {
- tries[i] = trie.Copy()
- }
- // Start a batch of goroutines interacting with the trie
- pend := new(sync.WaitGroup)
- pend.Add(threads)
- for i := 0; i < threads; i++ {
- go func(index int) {
- defer pend.Done()
-
- for j := byte(0); j < 255; j++ {
- // Map the same data under multiple keys
- key, val := common.LeftPadBytes([]byte{byte(index), 1, j}, 32), []byte{j}
- tries[index].MustUpdate(key, val)
-
- key, val = common.LeftPadBytes([]byte{byte(index), 2, j}, 32), []byte{j}
- tries[index].MustUpdate(key, val)
-
- // Add some other data to inflate the trie
- for k := byte(3); k < 13; k++ {
- key, val = common.LeftPadBytes([]byte{byte(index), k, j}, 32), []byte{k, j}
- tries[index].MustUpdate(key, val)
- }
- }
- tries[index].Commit(false)
- }(i)
- }
- // Wait for all threads to finish
- pend.Wait()
-}
diff --git a/trie/stacktrie.go b/trie/stacktrie.go
deleted file mode 100644
index f2f5355c49..0000000000
--- a/trie/stacktrie.go
+++ /dev/null
@@ -1,479 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "errors"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-var (
- stPool = sync.Pool{New: func() any { return new(stNode) }}
- _ = types.TrieHasher((*StackTrie)(nil))
-)
-
-// StackTrieOptions contains the configured options for manipulating the stackTrie.
-type StackTrieOptions struct {
- Writer func(path []byte, hash common.Hash, blob []byte) // The function to commit the dirty nodes
- Cleaner func(path []byte) // The function to clean up dangling nodes
-
- SkipLeftBoundary bool // Flag whether the nodes on the left boundary are skipped for committing
- SkipRightBoundary bool // Flag whether the nodes on the right boundary are skipped for committing
- boundaryGauge metrics.Gauge // Gauge to track how many boundary nodes are met
-}
-
-// NewStackTrieOptions initializes an empty options for stackTrie.
-func NewStackTrieOptions() *StackTrieOptions { return &StackTrieOptions{} }
-
-// WithWriter configures trie node writer within the options.
-func (o *StackTrieOptions) WithWriter(writer func(path []byte, hash common.Hash, blob []byte)) *StackTrieOptions {
- o.Writer = writer
- return o
-}
-
-// WithCleaner configures the cleaner in the option for removing dangling nodes.
-func (o *StackTrieOptions) WithCleaner(cleaner func(path []byte)) *StackTrieOptions {
- o.Cleaner = cleaner
- return o
-}
-
-// WithSkipBoundary configures whether the left and right boundary nodes are
-// filtered for committing, along with a gauge metrics to track how many
-// boundary nodes are met.
-func (o *StackTrieOptions) WithSkipBoundary(skipLeft, skipRight bool, gauge metrics.Gauge) *StackTrieOptions {
- o.SkipLeftBoundary = skipLeft
- o.SkipRightBoundary = skipRight
- o.boundaryGauge = gauge
- return o
-}
-
-// StackTrie is a trie implementation that expects keys to be inserted
-// in order. Once it determines that a subtree will no longer be inserted
-// into, it will hash it and free up the memory it uses.
-type StackTrie struct {
- options *StackTrieOptions
- root *stNode
- h *hasher
-
- first []byte // The (hex-encoded without terminator) key of first inserted entry, tracked as left boundary.
- last []byte // The (hex-encoded without terminator) key of last inserted entry, tracked as right boundary.
-}
-
-// NewStackTrie allocates and initializes an empty trie.
-func NewStackTrie(options *StackTrieOptions) *StackTrie {
- if options == nil {
- options = NewStackTrieOptions()
- }
- return &StackTrie{
- options: options,
- root: stPool.Get().(*stNode),
- h: newHasher(false),
- }
-}
-
-// Update inserts a (key, value) pair into the stack trie.
-func (t *StackTrie) Update(key, value []byte) error {
- if len(value) == 0 {
- return errors.New("trying to insert empty (deletion)")
- }
- k := keybytesToHex(key)
- k = k[:len(k)-1] // chop the termination flag
- if bytes.Compare(t.last, k) >= 0 {
- return errors.New("non-ascending key order")
- }
- // track the first and last inserted entries.
- if t.first == nil {
- t.first = append([]byte{}, k...)
- }
- if t.last == nil {
- t.last = append([]byte{}, k...) // allocate key slice
- } else {
- t.last = append(t.last[:0], k...) // reuse key slice
- }
- t.insert(t.root, k, value, nil)
- return nil
-}
-
-// MustUpdate is a wrapper of Update and will omit any encountered error but
-// just print out an error message.
-func (t *StackTrie) MustUpdate(key, value []byte) {
- if err := t.Update(key, value); err != nil {
- log.Error("Unhandled trie error in StackTrie.Update", "err", err)
- }
-}
-
-// Reset resets the stack trie object to empty state.
-func (t *StackTrie) Reset() {
- t.options = NewStackTrieOptions()
- t.root = stPool.Get().(*stNode)
- t.first = nil
- t.last = nil
-}
-
-// stNode represents a node within a StackTrie
-type stNode struct {
- typ uint8 // node type (as in branch, ext, leaf)
- key []byte // key chunk covered by this (leaf|ext) node
- val []byte // value contained by this node if it's a leaf
- children [16]*stNode // list of children (for branch and exts)
-}
-
-// newLeaf constructs a leaf node with provided node key and value. The key
-// will be deep-copied in the function and safe to modify afterwards, but
-// value is not.
-func newLeaf(key, val []byte) *stNode {
- st := stPool.Get().(*stNode)
- st.typ = leafNode
- st.key = append(st.key, key...)
- st.val = val
- return st
-}
-
-// newExt constructs an extension node with provided node key and child. The
-// key will be deep-copied in the function and safe to modify afterwards.
-func newExt(key []byte, child *stNode) *stNode {
- st := stPool.Get().(*stNode)
- st.typ = extNode
- st.key = append(st.key, key...)
- st.children[0] = child
- return st
-}
-
-// List all values that stNode#nodeType can hold
-const (
- emptyNode = iota
- branchNode
- extNode
- leafNode
- hashedNode
-)
-
-func (n *stNode) reset() *stNode {
- n.key = n.key[:0]
- n.val = nil
- for i := range n.children {
- n.children[i] = nil
- }
- n.typ = emptyNode
- return n
-}
-
-// Helper function that, given a full key, determines the index
-// at which the chunk pointed by st.keyOffset is different from
-// the same chunk in the full key.
-func (n *stNode) getDiffIndex(key []byte) int {
- for idx, nibble := range n.key {
- if nibble != key[idx] {
- return idx
- }
- }
- return len(n.key)
-}
-
-// Helper function to that inserts a (key, value) pair into
-// the trie.
-func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
- switch st.typ {
- case branchNode: /* Branch */
- idx := int(key[0])
-
- // Unresolve elder siblings
- for i := idx - 1; i >= 0; i-- {
- if st.children[i] != nil {
- if st.children[i].typ != hashedNode {
- t.hash(st.children[i], append(path, byte(i)))
- }
- break
- }
- }
-
- // Add new child
- if st.children[idx] == nil {
- st.children[idx] = newLeaf(key[1:], value)
- } else {
- t.insert(st.children[idx], key[1:], value, append(path, key[0]))
- }
-
- case extNode: /* Ext */
- // Compare both key chunks and see where they differ
- diffidx := st.getDiffIndex(key)
-
- // Check if chunks are identical. If so, recurse into
- // the child node. Otherwise, the key has to be split
- // into 1) an optional common prefix, 2) the fullnode
- // representing the two differing path, and 3) a leaf
- // for each of the differentiated subtrees.
- if diffidx == len(st.key) {
- // Ext key and key segment are identical, recurse into
- // the child node.
- t.insert(st.children[0], key[diffidx:], value, append(path, key[:diffidx]...))
- return
- }
- // Save the original part. Depending if the break is
- // at the extension's last byte or not, create an
- // intermediate extension or use the extension's child
- // node directly.
- var n *stNode
- if diffidx < len(st.key)-1 {
- // Break on the non-last byte, insert an intermediate
- // extension. The path prefix of the newly-inserted
- // extension should also contain the different byte.
- n = newExt(st.key[diffidx+1:], st.children[0])
- t.hash(n, append(path, st.key[:diffidx+1]...))
- } else {
- // Break on the last byte, no need to insert
- // an extension node: reuse the current node.
- // The path prefix of the original part should
- // still be same.
- n = st.children[0]
- t.hash(n, append(path, st.key...))
- }
- var p *stNode
- if diffidx == 0 {
- // the break is on the first byte, so
- // the current node is converted into
- // a branch node.
- st.children[0] = nil
- p = st
- st.typ = branchNode
- } else {
- // the common prefix is at least one byte
- // long, insert a new intermediate branch
- // node.
- st.children[0] = stPool.Get().(*stNode)
- st.children[0].typ = branchNode
- p = st.children[0]
- }
- // Create a leaf for the inserted part
- o := newLeaf(key[diffidx+1:], value)
-
- // Insert both child leaves where they belong:
- origIdx := st.key[diffidx]
- newIdx := key[diffidx]
- p.children[origIdx] = n
- p.children[newIdx] = o
- st.key = st.key[:diffidx]
-
- case leafNode: /* Leaf */
- // Compare both key chunks and see where they differ
- diffidx := st.getDiffIndex(key)
-
- // Overwriting a key isn't supported, which means that
- // the current leaf is expected to be split into 1) an
- // optional extension for the common prefix of these 2
- // keys, 2) a fullnode selecting the path on which the
- // keys differ, and 3) one leaf for the differentiated
- // component of each key.
- if diffidx >= len(st.key) {
- panic("Trying to insert into existing key")
- }
-
- // Check if the split occurs at the first nibble of the
- // chunk. In that case, no prefix extnode is necessary.
- // Otherwise, create that
- var p *stNode
- if diffidx == 0 {
- // Convert current leaf into a branch
- st.typ = branchNode
- p = st
- st.children[0] = nil
- } else {
- // Convert current node into an ext,
- // and insert a child branch node.
- st.typ = extNode
- st.children[0] = stPool.Get().(*stNode)
- st.children[0].typ = branchNode
- p = st.children[0]
- }
-
- // Create the two child leaves: one containing the original
- // value and another containing the new value. The child leaf
- // is hashed directly in order to free up some memory.
- origIdx := st.key[diffidx]
- p.children[origIdx] = newLeaf(st.key[diffidx+1:], st.val)
- t.hash(p.children[origIdx], append(path, st.key[:diffidx+1]...))
-
- newIdx := key[diffidx]
- p.children[newIdx] = newLeaf(key[diffidx+1:], value)
-
- // Finally, cut off the key part that has been passed
- // over to the children.
- st.key = st.key[:diffidx]
- st.val = nil
-
- case emptyNode: /* Empty */
- st.typ = leafNode
- st.key = key
- st.val = value
-
- case hashedNode:
- panic("trying to insert into hash")
-
- default:
- panic("invalid type")
- }
-}
-
-// hash converts st into a 'hashedNode', if possible. Possible outcomes:
-//
-// 1. The rlp-encoded value was >= 32 bytes:
-// - Then the 32-byte `hash` will be accessible in `st.val`.
-// - And the 'st.type' will be 'hashedNode'
-//
-// 2. The rlp-encoded value was < 32 bytes
-// - Then the <32 byte rlp-encoded value will be accessible in 'st.val'.
-// - And the 'st.type' will be 'hashedNode' AGAIN
-//
-// This method also sets 'st.type' to hashedNode, and clears 'st.key'.
-func (t *StackTrie) hash(st *stNode, path []byte) {
- var (
- blob []byte // RLP-encoded node blob
- internal [][]byte // List of node paths covered by the extension node
- )
- switch st.typ {
- case hashedNode:
- return
-
- case emptyNode:
- st.val = types.EmptyRootHash.Bytes()
- st.key = st.key[:0]
- st.typ = hashedNode
- return
-
- case branchNode:
- var nodes fullNode
- for i, child := range st.children {
- if child == nil {
- nodes.Children[i] = nilValueNode
- continue
- }
- t.hash(child, append(path, byte(i)))
-
- if len(child.val) < 32 {
- nodes.Children[i] = rawNode(child.val)
- } else {
- nodes.Children[i] = hashNode(child.val)
- }
- st.children[i] = nil
- stPool.Put(child.reset()) // Release child back to pool.
- }
- nodes.encode(t.h.encbuf)
- blob = t.h.encodedBytes()
-
- case extNode:
- // recursively hash and commit child as the first step
- t.hash(st.children[0], append(path, st.key...))
-
- // Collect the path of internal nodes between shortNode and its **in disk**
- // child. This is essential in the case of path mode scheme to avoid leaving
- // danging nodes within the range of this internal path on disk, which would
- // break the guarantee for state healing.
- if len(st.children[0].val) >= 32 && t.options.Cleaner != nil {
- for i := 1; i < len(st.key); i++ {
- internal = append(internal, append(path, st.key[:i]...))
- }
- }
- // encode the extension node
- n := shortNode{Key: hexToCompactInPlace(st.key)}
- if len(st.children[0].val) < 32 {
- n.Val = rawNode(st.children[0].val)
- } else {
- n.Val = hashNode(st.children[0].val)
- }
- n.encode(t.h.encbuf)
- blob = t.h.encodedBytes()
-
- stPool.Put(st.children[0].reset()) // Release child back to pool.
- st.children[0] = nil
-
- case leafNode:
- st.key = append(st.key, byte(16))
- n := shortNode{Key: hexToCompactInPlace(st.key), Val: valueNode(st.val)}
-
- n.encode(t.h.encbuf)
- blob = t.h.encodedBytes()
-
- default:
- panic("invalid node type")
- }
-
- st.typ = hashedNode
- st.key = st.key[:0]
-
- // Skip committing the non-root node if the size is smaller than 32 bytes.
- if len(blob) < 32 && len(path) > 0 {
- st.val = common.CopyBytes(blob)
- return
- }
- // Write the hash to the 'val'. We allocate a new val here to not mutate
- // input values.
- st.val = t.h.hashData(blob)
-
- // Short circuit if the stack trie is not configured for writing.
- if t.options.Writer == nil {
- return
- }
- // Skip committing if the node is on the left boundary and stackTrie is
- // configured to filter the boundary.
- if t.options.SkipLeftBoundary && bytes.HasPrefix(t.first, path) {
- if t.options.boundaryGauge != nil {
- t.options.boundaryGauge.Inc(1)
- }
- return
- }
- // Skip committing if the node is on the right boundary and stackTrie is
- // configured to filter the boundary.
- if t.options.SkipRightBoundary && bytes.HasPrefix(t.last, path) {
- if t.options.boundaryGauge != nil {
- t.options.boundaryGauge.Inc(1)
- }
- return
- }
- // Clean up the internal dangling nodes covered by the extension node.
- // This should be done before writing the node to adhere to the committing
- // order from bottom to top.
- for _, path := range internal {
- t.options.Cleaner(path)
- }
- t.options.Writer(path, common.BytesToHash(st.val), blob)
-}
-
-// Hash will firstly hash the entire trie if it's still not hashed and then commit
-// all nodes to the associated database. Actually most of the trie nodes have been
-// committed already. The main purpose here is to commit the nodes on right boundary.
-//
-// For stack trie, Hash and Commit are functionally identical.
-func (t *StackTrie) Hash() common.Hash {
- n := t.root
- t.hash(n, nil)
- return common.BytesToHash(n.val)
-}
-
-// Commit will firstly hash the entire trie if it's still not hashed and then commit
-// all nodes to the associated database. Actually most of the trie nodes have been
-// committed already. The main purpose here is to commit the nodes on right boundary.
-//
-// For stack trie, Hash and Commit are functionally identical.
-func (t *StackTrie) Commit() common.Hash {
- return t.Hash()
-}
diff --git a/trie/stacktrie_fuzzer_test.go b/trie/stacktrie_fuzzer_test.go
deleted file mode 100644
index 1b3f9dbe9c..0000000000
--- a/trie/stacktrie_fuzzer_test.go
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "testing"
-
- "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/trie/trienode"
- "golang.org/x/crypto/sha3"
- "golang.org/x/exp/slices"
-)
-
-func FuzzStackTrie(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- fuzz(data, false)
- })
-}
-
-func fuzz(data []byte, debugging bool) {
- // This spongeDb is used to check the sequence of disk-db-writes
- var (
- input = bytes.NewReader(data)
- spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- dbA = NewDatabase(rawdb.NewDatabase(spongeA), nil)
- trieA = NewEmpty(dbA)
- spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- dbB = NewDatabase(rawdb.NewDatabase(spongeB), nil)
-
- options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme())
- })
- trieB = NewStackTrie(options)
- vals []*kv
- maxElements = 10000
- // operate on unique keys only
- keys = make(map[string]struct{})
- )
- // Fill the trie with elements
- for i := 0; input.Len() > 0 && i < maxElements; i++ {
- k := make([]byte, 32)
- input.Read(k)
- var a uint16
- binary.Read(input, binary.LittleEndian, &a)
- a = 1 + a%100
- v := make([]byte, a)
- input.Read(v)
- if input.Len() == 0 {
- // If it was exhausted while reading, the value may be all zeroes,
- // thus 'deletion' which is not supported on stacktrie
- break
- }
- if _, present := keys[string(k)]; present {
- // This key is a duplicate, ignore it
- continue
- }
- keys[string(k)] = struct{}{}
- vals = append(vals, &kv{k: k, v: v})
- trieA.MustUpdate(k, v)
- }
- if len(vals) == 0 {
- return
- }
- // Flush trie -> database
- rootA, nodes, err := trieA.Commit(false)
- if err != nil {
- panic(err)
- }
- if nodes != nil {
- dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- }
- // Flush memdb -> disk (sponge)
- dbA.Commit(rootA, false)
-
- // Stacktrie requires sorted insertion
- slices.SortFunc(vals, (*kv).cmp)
-
- for _, kv := range vals {
- if debugging {
- fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v)
- }
- trieB.MustUpdate(kv.k, kv.v)
- }
- rootB := trieB.Hash()
- trieB.Commit()
- if rootA != rootB {
- panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB))
- }
- sumA := spongeA.sponge.Sum(nil)
- sumB := spongeB.sponge.Sum(nil)
- if !bytes.Equal(sumA, sumB) {
- panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB))
- }
-
- // Ensure all the nodes are persisted correctly
- var (
- nodeset = make(map[string][]byte) // path -> blob
- optionsC = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- if crypto.Keccak256Hash(blob) != hash {
- panic("invalid node blob")
- }
- nodeset[string(path)] = common.CopyBytes(blob)
- })
- trieC = NewStackTrie(optionsC)
- checked int
- )
- for _, kv := range vals {
- trieC.MustUpdate(kv.k, kv.v)
- }
- rootC := trieC.Commit()
- if rootA != rootC {
- panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC))
- }
- trieA, _ = New(TrieID(rootA), dbA)
- iterA := trieA.MustNodeIterator(nil)
- for iterA.Next(true) {
- if iterA.Hash() == (common.Hash{}) {
- if _, present := nodeset[string(iterA.Path())]; present {
- panic("unexpected tiny node")
- }
- continue
- }
- nodeBlob, present := nodeset[string(iterA.Path())]
- if !present {
- panic("missing node")
- }
- if !bytes.Equal(nodeBlob, iterA.NodeBlob()) {
- panic("node blob is not matched")
- }
- checked += 1
- }
- if checked != len(nodeset) {
- panic("node number is not matched")
- }
-}
diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go
deleted file mode 100644
index 909a77062a..0000000000
--- a/trie/stacktrie_test.go
+++ /dev/null
@@ -1,487 +0,0 @@
-// Copyright 2020 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "math/big"
- "math/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/trie/testutil"
- "github.com/stretchr/testify/assert"
- "golang.org/x/exp/slices"
-)
-
-func TestStackTrieInsertAndHash(t *testing.T) {
- type KeyValueHash struct {
- K string // Hex string for key.
- V string // Value, directly converted to bytes.
- H string // Expected root hash after insert of (K, V) to an existing trie.
- }
- tests := [][]KeyValueHash{
- { // {0:0, 7:0, f:0}
- {"00", "v_______________________0___0", "5cb26357b95bb9af08475be00243ceb68ade0b66b5cd816b0c18a18c612d2d21"},
- {"70", "v_______________________0___1", "8ff64309574f7a437a7ad1628e690eb7663cfde10676f8a904a8c8291dbc1603"},
- {"f0", "v_______________________0___2", "9e3a01bd8d43efb8e9d4b5506648150b8e3ed1caea596f84ee28e01a72635470"},
- },
- { // {1:0cc, e:{1:fc, e:fc}}
- {"10cc", "v_______________________1___0", "233e9b257843f3dfdb1cce6676cdaf9e595ac96ee1b55031434d852bc7ac9185"},
- {"e1fc", "v_______________________1___1", "39c5e908ae83d0c78520c7c7bda0b3782daf594700e44546e93def8f049cca95"},
- {"eefc", "v_______________________1___2", "d789567559fd76fe5b7d9cc42f3750f942502ac1c7f2a466e2f690ec4b6c2a7c"},
- },
- { // {b:{a:ac, b:ac}, d:acc}
- {"baac", "v_______________________2___0", "8be1c86ba7ec4c61e14c1a9b75055e0464c2633ae66a055a24e75450156a5d42"},
- {"bbac", "v_______________________2___1", "8495159b9895a7d88d973171d737c0aace6fe6ac02a4769fff1bc43bcccce4cc"},
- {"dacc", "v_______________________2___2", "9bcfc5b220a27328deb9dc6ee2e3d46c9ebc9c69e78acda1fa2c7040602c63ca"},
- },
- { // {0:0cccc, 2:456{0:0, 2:2}
- {"00cccc", "v_______________________3___0", "e57dc2785b99ce9205080cb41b32ebea7ac3e158952b44c87d186e6d190a6530"},
- {"245600", "v_______________________3___1", "0335354adbd360a45c1871a842452287721b64b4234dfe08760b243523c998db"},
- {"245622", "v_______________________3___2", "9e6832db0dca2b5cf81c0e0727bfde6afc39d5de33e5720bccacc183c162104e"},
- },
- { // {1:4567{1:1c, 3:3c}, 3:0cccccc}
- {"1456711c", "v_______________________4___0", "f2389e78d98fed99f3e63d6d1623c1d4d9e8c91cb1d585de81fbc7c0e60d3529"},
- {"1456733c", "v_______________________4___1", "101189b3fab852be97a0120c03d95eefcf984d3ed639f2328527de6def55a9c0"},
- {"30cccccc", "v_______________________4___2", "3780ce111f98d15751dfde1eb21080efc7d3914b429e5c84c64db637c55405b3"},
- },
- { // 8800{1:f, 2:e, 3:d}
- {"88001f", "v_______________________5___0", "e817db50d84f341d443c6f6593cafda093fc85e773a762421d47daa6ac993bd5"},
- {"88002e", "v_______________________5___1", "d6e3e6047bdc110edd296a4d63c030aec451bee9d8075bc5a198eee8cda34f68"},
- {"88003d", "v_______________________5___2", "b6bdf8298c703342188e5f7f84921a402042d0e5fb059969dd53a6b6b1fb989e"},
- },
- { // 0{1:fc, 2:ec, 4:dc}
- {"01fc", "v_______________________6___0", "693268f2ca80d32b015f61cd2c4dba5a47a6b52a14c34f8e6945fad684e7a0d5"},
- {"02ec", "v_______________________6___1", "e24ddd44469310c2b785a2044618874bf486d2f7822603a9b8dce58d6524d5de"},
- {"04dc", "v_______________________6___2", "33fc259629187bbe54b92f82f0cd8083b91a12e41a9456b84fc155321e334db7"},
- },
- { // f{0:fccc, f:ff{0:f, f:f}}
- {"f0fccc", "v_______________________7___0", "b0966b5aa469a3e292bc5fcfa6c396ae7a657255eef552ea7e12f996de795b90"},
- {"ffff0f", "v_______________________7___1", "3b1ca154ec2a3d96d8d77bddef0abfe40a53a64eb03cecf78da9ec43799fa3d0"},
- {"ffffff", "v_______________________7___2", "e75463041f1be8252781be0ace579a44ea4387bf5b2739f4607af676f7719678"},
- },
- { // ff{0:f{0:f, f:f}, f:fcc}
- {"ff0f0f", "v_______________________8___0", "0928af9b14718ec8262ab89df430f1e5fbf66fac0fed037aff2b6767ae8c8684"},
- {"ff0fff", "v_______________________8___1", "d870f4d3ce26b0bf86912810a1960693630c20a48ba56be0ad04bc3e9ddb01e6"},
- {"ffffcc", "v_______________________8___2", "4239f10dd9d9915ecf2e047d6a576bdc1733ed77a30830f1bf29deaf7d8e966f"},
- },
- {
- {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"},
- {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"},
- {"123f", "x___________________________2", "1164d7299964e74ac40d761f9189b2a3987fae959800d0f7e29d3aaf3eae9e15"},
- },
- {
- {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"},
- {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"},
- {"124a", "x___________________________2", "661a96a669869d76b7231380da0649d013301425fbea9d5c5fae6405aa31cfce"},
- },
- {
- {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"},
- {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"},
- {"13aa", "x___________________________2", "6590120e1fd3ffd1a90e8de5bb10750b61079bb0776cca4414dd79a24e4d4356"},
- },
- {
- {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"},
- {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"},
- {"2aaa", "x___________________________2", "f869b40e0c55eace1918332ef91563616fbf0755e2b946119679f7ef8e44b514"},
- },
- {
- {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"},
- {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"},
- {"1234fa", "x___________________________2", "4f4e368ab367090d5bc3dbf25f7729f8bd60df84de309b4633a6b69ab66142c0"},
- },
- {
- {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"},
- {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"},
- {"1235aa", "x___________________________2", "21840121d11a91ac8bbad9a5d06af902a5c8d56a47b85600ba813814b7bfcb9b"},
- },
- {
- {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"},
- {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"},
- {"124aaa", "x___________________________2", "ea4040ddf6ae3fbd1524bdec19c0ab1581015996262006632027fa5cf21e441e"},
- },
- {
- {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"},
- {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"},
- {"13aaaa", "x___________________________2", "e4beb66c67e44f2dd8ba36036e45a44ff68f8d52942472b1911a45f886a34507"},
- },
- {
- {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"},
- {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"},
- {"2aaaaa", "x___________________________2", "5f5989b820ff5d76b7d49e77bb64f26602294f6c42a1a3becc669cd9e0dc8ec9"},
- },
- {
- {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"},
- {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"},
- {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"},
- {"1234fa", "x___________________________3", "65bb3aafea8121111d693ffe34881c14d27b128fd113fa120961f251fe28428d"},
- },
- {
- {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"},
- {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"},
- {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"},
- {"1235aa", "x___________________________3", "f670e4d2547c533c5f21e0045442e2ecb733f347ad6d29ef36e0f5ba31bb11a8"},
- },
- {
- {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"},
- {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"},
- {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"},
- {"124aaa", "x___________________________3", "c17464123050a9a6f29b5574bb2f92f6d305c1794976b475b7fb0316b6335598"},
- },
- {
- {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"},
- {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"},
- {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"},
- {"13aaaa", "x___________________________3", "aa8301be8cb52ea5cd249f5feb79fb4315ee8de2140c604033f4b3fff78f0105"},
- },
- {
- {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"},
- {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"},
- {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"},
- {"123f", "x___________________________3", "80f7bad1893ca57e3443bb3305a517723a74d3ba831bcaca22a170645eb7aafb"},
- },
- {
- {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"},
- {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"},
- {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"},
- {"124a", "x___________________________3", "383bc1bb4f019e6bc4da3751509ea709b58dd1ac46081670834bae072f3e9557"},
- },
- {
- {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"},
- {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"},
- {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"},
- {"13aa", "x___________________________3", "ff0dc70ce2e5db90ee42a4c2ad12139596b890e90eb4e16526ab38fa465b35cf"},
- },
- { // branch node with short values
- {"01", "a", "b48605025f5f4b129d40a420e721aa7d504487f015fce85b96e52126365ef7dc"},
- {"80", "b", "2dc6b680daf74db067cb7aeaad73265ded93d96fce190fcbf64f498d475672ab"},
- {"ee", "c", "017dc705a54ac5328dd263fa1bae68d655310fb3e3f7b7bc57e9a43ddf99c4bf"},
- {"ff", "d", "bd5a3584d271d459bd4eb95247b2fc88656b3671b60c1125ffe7bc0b689470d0"},
- },
- { // ext node with short branch node, then becoming long
- {"a0", "a", "a83e028cb1e4365935661a9fd36a5c65c30b9ab416eaa877424146ca2a69d088"},
- {"a1", "b", "f586a4639b07b01798ca65e05c253b75d51135ebfbf6f8d6e87c0435089e65f0"},
- {"a2", "c", "63e297c295c008e09a8d531e18d57f270b6bc403e23179b915429db948cd62e3"},
- {"a3", "d", "94a7b721535578e9381f1f4e4b6ec29f8bdc5f0458a30320684c562f5d47b4b5"},
- {"a4", "e", "4b7e66d1c81965cdbe8fab8295ef56bc57fefdc5733d4782d2f8baf630f083c6"},
- {"a5", "f", "2997e7b502198ce1783b5277faacf52b25844fb55a99b63e88bdbbafac573106"},
- {"a6", "g", "bee629dd27a40772b2e1a67ec6db270d26acdf8d3b674dfae27866ad6ae1f48b"},
- },
- { // branch node with short values, then long ones
- {"a001", "v1", "b9cc982d995392b51e6787f1915f0b88efd4ad8b30f138da0a3e2242f2323e35"},
- {"b002", "v2", "a7b474bc77ef5097096fa0ee6298fdae8928c0bc3724e7311cd0fa9ed1942fc7"},
- {"c003", "v___________________________3", "dceb5bb7c92b0e348df988a8d9fc36b101397e38ebd405df55ba6ee5f14a264a"},
- {"d004", "v___________________________4", "36e60ecb86b9626165e1c6543c42ecbe4d83bca58e8e1124746961511fce362a"},
- },
- { // ext node to branch node with short values, then long ones
- {"8002", "v1", "3258fcb3e9e7d7234ecd3b8d4743999e4ab3a21592565e0a5ca64c141e8620d9"},
- {"8004", "v2", "b6cb95b7024a83c17624a3c9bed09b4b5e8ed426f49f54b8ad13c39028b1e75a"},
- {"8008", "v___________________________3", "c769d82963abe6f0900bf69754738eeb2f84559777cfa87a44f54e1aab417871"},
- {"800d", "v___________________________4", "1cad1fdaab1a6fa95d7b780fd680030e423eb76669971368ba04797a8d9cdfc9"},
- },
- { // ext node with a child of size 31 (Y) and branch node with a child of size 31 (X)
- {"000001", "ZZZZZZZZZ", "cef154b87c03c563408520ff9b26923c360cbc3ddb590c079bedeeb25a8c9c77"},
- {"000002", "Y", "2130735e600f612f6e657a32bd7be64ddcaec6512c5694844b19de713922895d"},
- {"000003", "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", "962c0fffdeef7612a4f7bff1950d67e3e81c878e48b9ae45b3b374253b050bd8"},
- },
- }
- for i, test := range tests {
- // The StackTrie does not allow Insert(), Hash(), Insert(), ...
- // so we will create new trie for every sequence length of inserts.
- for l := 1; l <= len(test); l++ {
- st := NewStackTrie(nil)
- for j := 0; j < l; j++ {
- kv := &test[j]
- if err := st.Update(common.FromHex(kv.K), []byte(kv.V)); err != nil {
- t.Fatal(err)
- }
- }
- expected := common.HexToHash(test[l-1].H)
- if h := st.Hash(); h != expected {
- t.Errorf("%d(%d): root hash mismatch: %x, expected %x", i, l, h, expected)
- }
- }
- }
-}
-
-func TestSizeBug(t *testing.T) {
- st := NewStackTrie(nil)
- nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
-
- leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
- value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
-
- nt.Update(leaf, value)
- st.Update(leaf, value)
-
- if nt.Hash() != st.Hash() {
- t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
- }
-}
-
-func TestEmptyBug(t *testing.T) {
- st := NewStackTrie(nil)
- nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
-
- //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
- //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
- kvs := []struct {
- K string
- V string
- }{
- {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "9496f4ec2bf9dab484cac6be589e8417d84781be08"},
- {K: "40edb63a35fcf86c08022722aa3287cdd36440d671b4918131b2514795fefa9c", V: "01"},
- {K: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", V: "947a30f7736e48d6599356464ba4c150d8da0302ff"},
- {K: "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", V: "02"},
- }
-
- for _, kv := range kvs {
- nt.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- st.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- }
-
- if nt.Hash() != st.Hash() {
- t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
- }
-}
-
-func TestValLength56(t *testing.T) {
- st := NewStackTrie(nil)
- nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
-
- //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
- //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
- kvs := []struct {
- K string
- V string
- }{
- {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"},
- }
-
- for _, kv := range kvs {
- nt.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- st.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- }
-
- if nt.Hash() != st.Hash() {
- t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
- }
-}
-
-// TestUpdateSmallNodes tests a case where the leaves are small (both key and value),
-// which causes a lot of node-within-node. This case was found via fuzzing.
-func TestUpdateSmallNodes(t *testing.T) {
- st := NewStackTrie(nil)
- nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- kvs := []struct {
- K string
- V string
- }{
- {"63303030", "3041"}, // stacktrie.Update
- {"65", "3000"}, // stacktrie.Update
- }
- for _, kv := range kvs {
- nt.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- st.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- }
- if nt.Hash() != st.Hash() {
- t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
- }
-}
-
-// TestUpdateVariableKeys contains a case which stacktrie fails: when keys of different
-// sizes are used, and the second one has the same prefix as the first, then the
-// stacktrie fails, since it's unable to 'expand' on an already added leaf.
-// For all practical purposes, this is fine, since keys are fixed-size length
-// in account and storage tries.
-//
-// The test is marked as 'skipped', and exists just to have the behaviour documented.
-// This case was found via fuzzing.
-func TestUpdateVariableKeys(t *testing.T) {
- t.SkipNow()
- st := NewStackTrie(nil)
- nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- kvs := []struct {
- K string
- V string
- }{
- {"0x33303534636532393561313031676174", "303030"},
- {"0x3330353463653239356131303167617430", "313131"},
- }
- for _, kv := range kvs {
- nt.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- st.Update(common.FromHex(kv.K), common.FromHex(kv.V))
- }
- if nt.Hash() != st.Hash() {
- t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
- }
-}
-
-// TestStacktrieNotModifyValues checks that inserting blobs of data into the
-// stacktrie does not mutate the blobs
-func TestStacktrieNotModifyValues(t *testing.T) {
- st := NewStackTrie(nil)
- { // Test a very small trie
- // Give it the value as a slice with large backing alloc,
- // so if the stacktrie tries to append, it won't have to realloc
- value := make([]byte, 1, 100)
- value[0] = 0x2
- want := common.CopyBytes(value)
- st.Update([]byte{0x01}, value)
- st.Hash()
- if have := value; !bytes.Equal(have, want) {
- t.Fatalf("tiny trie: have %#x want %#x", have, want)
- }
- st = NewStackTrie(nil)
- }
- // Test with a larger trie
- keyB := big.NewInt(1)
- keyDelta := big.NewInt(1)
- var vals [][]byte
- getValue := func(i int) []byte {
- if i%2 == 0 { // large
- return crypto.Keccak256(big.NewInt(int64(i)).Bytes())
- } else { //small
- return big.NewInt(int64(i)).Bytes()
- }
- }
- for i := 0; i < 1000; i++ {
- key := common.BigToHash(keyB)
- value := getValue(i)
- st.Update(key.Bytes(), value)
- vals = append(vals, value)
- keyB = keyB.Add(keyB, keyDelta)
- keyDelta.Add(keyDelta, common.Big1)
- }
- st.Hash()
- for i := 0; i < 1000; i++ {
- want := getValue(i)
-
- have := vals[i]
- if !bytes.Equal(have, want) {
- t.Fatalf("item %d, have %#x want %#x", i, have, want)
- }
- }
-}
-
-func buildPartialTree(entries []*kv, t *testing.T) map[string]common.Hash {
- var (
- options = NewStackTrieOptions()
- nodes = make(map[string]common.Hash)
- )
- var (
- first int
- last = len(entries) - 1
-
- noLeft bool
- noRight bool
- )
- // Enter split mode if there are at least two elements
- if rand.Intn(5) != 0 {
- for {
- first = rand.Intn(len(entries))
- last = rand.Intn(len(entries))
- if first <= last {
- break
- }
- }
- if first != 0 {
- noLeft = true
- }
- if last != len(entries)-1 {
- noRight = true
- }
- }
- options = options.WithSkipBoundary(noLeft, noRight, nil)
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- nodes[string(path)] = hash
- })
- tr := NewStackTrie(options)
-
- for i := first; i <= last; i++ {
- tr.MustUpdate(entries[i].k, entries[i].v)
- }
- tr.Commit()
- return nodes
-}
-
-func TestPartialStackTrie(t *testing.T) {
- for round := 0; round < 100; round++ {
- var (
- n = rand.Intn(100) + 1
- entries []*kv
- )
- for i := 0; i < n; i++ {
- var val []byte
- if rand.Intn(3) == 0 {
- val = testutil.RandBytes(3)
- } else {
- val = testutil.RandBytes(32)
- }
- entries = append(entries, &kv{
- k: testutil.RandBytes(32),
- v: val,
- })
- }
- slices.SortFunc(entries, (*kv).cmp)
-
- var (
- nodes = make(map[string]common.Hash)
- options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- nodes[string(path)] = hash
- })
- )
- tr := NewStackTrie(options)
-
- for i := 0; i < len(entries); i++ {
- tr.MustUpdate(entries[i].k, entries[i].v)
- }
- tr.Commit()
-
- for j := 0; j < 100; j++ {
- for path, hash := range buildPartialTree(entries, t) {
- if nodes[path] != hash {
- t.Errorf("%v, want %x, got %x", []byte(path), nodes[path], hash)
- }
- }
- }
- }
-}
-
-func TestStackTrieErrors(t *testing.T) {
- s := NewStackTrie(nil)
- // Deletion
- if err := s.Update(nil, nil); err == nil {
- t.Fatal("expected error")
- }
- if err := s.Update(nil, []byte{}); err == nil {
- t.Fatal("expected error")
- }
- if err := s.Update([]byte{0xa}, []byte{}); err == nil {
- t.Fatal("expected error")
- }
- // Non-ascending keys (going backwards or repeating)
- assert.Nil(t, s.Update([]byte{0xaa}, []byte{0xa}))
- assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xa}), "repeat insert same key")
- assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xb}), "repeat insert same key")
- assert.Nil(t, s.Update([]byte{0xab}, []byte{0xa}))
- assert.NotNil(t, s.Update([]byte{0x10}, []byte{0xb}), "out of order insert")
- assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xb}), "repeat insert same key")
-}
diff --git a/trie/sync.go b/trie/sync.go
deleted file mode 100644
index 589d28364b..0000000000
--- a/trie/sync.go
+++ /dev/null
@@ -1,714 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "errors"
- "fmt"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/prque"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
-)
-
-// ErrNotRequested is returned by the trie sync when it's requested to process a
-// node it did not request.
-var ErrNotRequested = errors.New("not requested")
-
-// ErrAlreadyProcessed is returned by the trie sync when it's requested to process a
-// node it already processed previously.
-var ErrAlreadyProcessed = errors.New("already processed")
-
-// maxFetchesPerDepth is the maximum number of pending trie nodes per depth. The
-// role of this value is to limit the number of trie nodes that get expanded in
-// memory if the node was configured with a significant number of peers.
-const maxFetchesPerDepth = 16384
-
-var (
- // deletionGauge is the metric to track how many trie node deletions
- // are performed in total during the sync process.
- deletionGauge = metrics.NewRegisteredGauge("trie/sync/delete", nil)
-
- // lookupGauge is the metric to track how many trie node lookups are
- // performed to determine if node needs to be deleted.
- lookupGauge = metrics.NewRegisteredGauge("trie/sync/lookup", nil)
-
- // accountNodeSyncedGauge is the metric to track how many account trie
- // node are written during the sync.
- accountNodeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/nodes/account", nil)
-
- // storageNodeSyncedGauge is the metric to track how many account trie
- // node are written during the sync.
- storageNodeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/nodes/storage", nil)
-
- // codeSyncedGauge is the metric to track how many contract codes are
- // written during the sync.
- codeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/codes", nil)
-)
-
-// SyncPath is a path tuple identifying a particular trie node either in a single
-// trie (account) or a layered trie (account -> storage).
-//
-// Content wise the tuple either has 1 element if it addresses a node in a single
-// trie or 2 elements if it addresses a node in a stacked trie.
-//
-// To support aiming arbitrary trie nodes, the path needs to support odd nibble
-// lengths. To avoid transferring expanded hex form over the network, the last
-// part of the tuple (which needs to index into the middle of a trie) is compact
-// encoded. In case of a 2-tuple, the first item is always 32 bytes so that is
-// simple binary encoded.
-//
-// Examples:
-// - Path 0x9 -> {0x19}
-// - Path 0x99 -> {0x0099}
-// - Path 0x01234567890123456789012345678901012345678901234567890123456789019 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19}
-// - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099}
-type SyncPath [][]byte
-
-// NewSyncPath converts an expanded trie path from nibble form into a compact
-// version that can be sent over the network.
-func NewSyncPath(path []byte) SyncPath {
- // If the hash is from the account trie, append a single item, if it
- // is from a storage trie, append a tuple. Note, the length 64 is
- // clashing between account leaf and storage root. It's fine though
- // because having a trie node at 64 depth means a hash collision was
- // found and we're long dead.
- if len(path) < 64 {
- return SyncPath{hexToCompact(path)}
- }
- return SyncPath{hexToKeybytes(path[:64]), hexToCompact(path[64:])}
-}
-
-// LeafCallback is a callback type invoked when a trie operation reaches a leaf
-// node.
-//
-// The keys is a path tuple identifying a particular trie node either in a single
-// trie (account) or a layered trie (account -> storage). Each key in the tuple
-// is in the raw format(32 bytes).
-//
-// The path is a composite hexary path identifying the trie node. All the key
-// bytes are converted to the hexary nibbles and composited with the parent path
-// if the trie node is in a layered trie.
-//
-// It's used by state sync and commit to allow handling external references
-// between account and storage tries. And also it's used in the state healing
-// for extracting the raw states(leaf nodes) with corresponding paths.
-type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error
-
-// nodeRequest represents a scheduled or already in-flight trie node retrieval request.
-type nodeRequest struct {
- hash common.Hash // Hash of the trie node to retrieve
- path []byte // Merkle path leading to this node for prioritization
- data []byte // Data content of the node, cached until all subtrees complete
-
- parent *nodeRequest // Parent state node referencing this entry
- deps int // Number of dependencies before allowed to commit this node
- callback LeafCallback // Callback to invoke if a leaf node it reached on this branch
-}
-
-// codeRequest represents a scheduled or already in-flight bytecode retrieval request.
-type codeRequest struct {
- hash common.Hash // Hash of the contract bytecode to retrieve
- path []byte // Merkle path leading to this node for prioritization
- data []byte // Data content of the node, cached until all subtrees complete
- parents []*nodeRequest // Parent state nodes referencing this entry (notify all upon completion)
-}
-
-// NodeSyncResult is a response with requested trie node along with its node path.
-type NodeSyncResult struct {
- Path string // Path of the originally unknown trie node
- Data []byte // Data content of the retrieved trie node
-}
-
-// CodeSyncResult is a response with requested bytecode along with its hash.
-type CodeSyncResult struct {
- Hash common.Hash // Hash the originally unknown bytecode
- Data []byte // Data content of the retrieved bytecode
-}
-
-// nodeOp represents an operation upon the trie node. It can either represent a
-// deletion to the specific node or a node write for persisting retrieved node.
-type nodeOp struct {
- owner common.Hash // identifier of the trie (empty for account trie)
- path []byte // path from the root to the specified node.
- blob []byte // the content of the node (nil for deletion)
- hash common.Hash // hash of the node content (empty for node deletion)
-}
-
-// isDelete indicates if the operation is a database deletion.
-func (op *nodeOp) isDelete() bool {
- return len(op.blob) == 0
-}
-
-// syncMemBatch is an in-memory buffer of successfully downloaded but not yet
-// persisted data items.
-type syncMemBatch struct {
- scheme string // State scheme identifier
- codes map[common.Hash][]byte // In-memory batch of recently completed codes
- nodes []nodeOp // In-memory batch of recently completed/deleted nodes
- size uint64 // Estimated batch-size of in-memory data.
-}
-
-// newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes.
-func newSyncMemBatch(scheme string) *syncMemBatch {
- return &syncMemBatch{
- scheme: scheme,
- codes: make(map[common.Hash][]byte),
- }
-}
-
-// hasCode reports the contract code with specific hash is already cached.
-func (batch *syncMemBatch) hasCode(hash common.Hash) bool {
- _, ok := batch.codes[hash]
- return ok
-}
-
-// addCode caches a contract code database write operation.
-func (batch *syncMemBatch) addCode(hash common.Hash, code []byte) {
- batch.codes[hash] = code
- batch.size += common.HashLength + uint64(len(code))
-}
-
-// addNode caches a node database write operation.
-func (batch *syncMemBatch) addNode(owner common.Hash, path []byte, blob []byte, hash common.Hash) {
- if batch.scheme == rawdb.PathScheme {
- if owner == (common.Hash{}) {
- batch.size += uint64(len(path) + len(blob))
- } else {
- batch.size += common.HashLength + uint64(len(path)+len(blob))
- }
- } else {
- batch.size += common.HashLength + uint64(len(blob))
- }
- batch.nodes = append(batch.nodes, nodeOp{
- owner: owner,
- path: path,
- blob: blob,
- hash: hash,
- })
-}
-
-// delNode caches a node database delete operation.
-func (batch *syncMemBatch) delNode(owner common.Hash, path []byte) {
- if batch.scheme != rawdb.PathScheme {
- log.Error("Unexpected node deletion", "owner", owner, "path", path, "scheme", batch.scheme)
- return // deletion is not supported in hash mode.
- }
- if owner == (common.Hash{}) {
- batch.size += uint64(len(path))
- } else {
- batch.size += common.HashLength + uint64(len(path))
- }
- batch.nodes = append(batch.nodes, nodeOp{
- owner: owner,
- path: path,
- })
-}
-
-// Sync is the main state trie synchronisation scheduler, which provides yet
-// unknown trie hashes to retrieve, accepts node data associated with said hashes
-// and reconstructs the trie step by step until all is done.
-type Sync struct {
- scheme string // Node scheme descriptor used in database.
- database ethdb.KeyValueReader // Persistent database to check for existing entries
- membatch *syncMemBatch // Memory buffer to avoid frequent database writes
- nodeReqs map[string]*nodeRequest // Pending requests pertaining to a trie node path
- codeReqs map[common.Hash]*codeRequest // Pending requests pertaining to a code hash
- queue *prque.Prque[int64, any] // Priority queue with the pending requests
- fetches map[int]int // Number of active fetches per trie node depth
-}
-
-// NewSync creates a new trie data download scheduler.
-func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallback, scheme string) *Sync {
- ts := &Sync{
- scheme: scheme,
- database: database,
- membatch: newSyncMemBatch(scheme),
- nodeReqs: make(map[string]*nodeRequest),
- codeReqs: make(map[common.Hash]*codeRequest),
- queue: prque.New[int64, any](nil), // Ugh, can contain both string and hash, whyyy
- fetches: make(map[int]int),
- }
- ts.AddSubTrie(root, nil, common.Hash{}, nil, callback)
- return ts
-}
-
-// AddSubTrie registers a new trie to the sync code, rooted at the designated
-// parent for completion tracking. The given path is a unique node path in
-// hex format and contain all the parent path if it's layered trie node.
-func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) {
- if root == types.EmptyRootHash {
- return
- }
- owner, inner := ResolvePath(path)
- exist, inconsistent := s.hasNode(owner, inner, root)
- if exist {
- // The entire subtrie is already present in the database.
- return
- } else if inconsistent {
- // There is a pre-existing node with the wrong hash in DB, remove it.
- s.membatch.delNode(owner, inner)
- }
- // Assemble the new sub-trie sync request
- req := &nodeRequest{
- hash: root,
- path: path,
- callback: callback,
- }
- // If this sub-trie has a designated parent, link them together
- if parent != (common.Hash{}) {
- ancestor := s.nodeReqs[string(parentPath)]
- if ancestor == nil {
- panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent))
- }
- ancestor.deps++
- req.parent = ancestor
- }
- s.scheduleNodeRequest(req)
-}
-
-// AddCodeEntry schedules the direct retrieval of a contract code that should not
-// be interpreted as a trie node, but rather accepted and stored into the database
-// as is.
-func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash, parentPath []byte) {
- // Short circuit if the entry is empty or already known
- if hash == types.EmptyCodeHash {
- return
- }
- if s.membatch.hasCode(hash) {
- return
- }
- // If database says duplicate, the blob is present for sure.
- // Note we only check the existence with new code scheme, snap
- // sync is expected to run with a fresh new node. Even there
- // exists the code with legacy format, fetch and store with
- // new scheme anyway.
- if rawdb.HasCodeWithPrefix(s.database, hash) {
- return
- }
- // Assemble the new sub-trie sync request
- req := &codeRequest{
- path: path,
- hash: hash,
- }
- // If this sub-trie has a designated parent, link them together
- if parent != (common.Hash{}) {
- ancestor := s.nodeReqs[string(parentPath)] // the parent of codereq can ONLY be nodereq
- if ancestor == nil {
- panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent))
- }
- ancestor.deps++
- req.parents = append(req.parents, ancestor)
- }
- s.scheduleCodeRequest(req)
-}
-
-// Missing retrieves the known missing nodes from the trie for retrieval. To aid
-// both eth/6x style fast sync and snap/1x style state sync, the paths of trie
-// nodes are returned too, as well as separate hash list for codes.
-func (s *Sync) Missing(max int) ([]string, []common.Hash, []common.Hash) {
- var (
- nodePaths []string
- nodeHashes []common.Hash
- codeHashes []common.Hash
- )
- for !s.queue.Empty() && (max == 0 || len(nodeHashes)+len(codeHashes) < max) {
- // Retrieve the next item in line
- item, prio := s.queue.Peek()
-
- // If we have too many already-pending tasks for this depth, throttle
- depth := int(prio >> 56)
- if s.fetches[depth] > maxFetchesPerDepth {
- break
- }
- // Item is allowed to be scheduled, add it to the task list
- s.queue.Pop()
- s.fetches[depth]++
-
- switch item := item.(type) {
- case common.Hash:
- codeHashes = append(codeHashes, item)
- case string:
- req, ok := s.nodeReqs[item]
- if !ok {
- log.Error("Missing node request", "path", item)
- continue // System very wrong, shouldn't happen
- }
- nodePaths = append(nodePaths, item)
- nodeHashes = append(nodeHashes, req.hash)
- }
- }
- return nodePaths, nodeHashes, codeHashes
-}
-
-// ProcessCode injects the received data for requested item. Note it can
-// happen that the single response commits two pending requests(e.g.
-// there are two requests one for code and one for node but the hash
-// is same). In this case the second response for the same hash will
-// be treated as "non-requested" item or "already-processed" item but
-// there is no downside.
-func (s *Sync) ProcessCode(result CodeSyncResult) error {
- // If the code was not requested or it's already processed, bail out
- req := s.codeReqs[result.Hash]
- if req == nil {
- return ErrNotRequested
- }
- if req.data != nil {
- return ErrAlreadyProcessed
- }
- req.data = result.Data
- return s.commitCodeRequest(req)
-}
-
-// ProcessNode injects the received data for requested item. Note it can
-// happen that the single response commits two pending requests(e.g.
-// there are two requests one for code and one for node but the hash
-// is same). In this case the second response for the same hash will
-// be treated as "non-requested" item or "already-processed" item but
-// there is no downside.
-func (s *Sync) ProcessNode(result NodeSyncResult) error {
- // If the trie node was not requested or it's already processed, bail out
- req := s.nodeReqs[result.Path]
- if req == nil {
- return ErrNotRequested
- }
- if req.data != nil {
- return ErrAlreadyProcessed
- }
- // Decode the node data content and update the request
- node, err := decodeNode(req.hash.Bytes(), result.Data)
- if err != nil {
- return err
- }
- req.data = result.Data
-
- // Create and schedule a request for all the children nodes
- requests, err := s.children(req, node)
- if err != nil {
- return err
- }
- if len(requests) == 0 && req.deps == 0 {
- s.commitNodeRequest(req)
- } else {
- req.deps += len(requests)
- for _, child := range requests {
- s.scheduleNodeRequest(child)
- }
- }
- return nil
-}
-
-// Commit flushes the data stored in the internal membatch out to persistent
-// storage, returning any occurred error. The whole data set will be flushed
-// in an atomic database batch.
-func (s *Sync) Commit(dbw ethdb.Batch) error {
- // Flush the pending node writes into database batch.
- var (
- account int
- storage int
- )
- for _, op := range s.membatch.nodes {
- if op.isDelete() {
- // node deletion is only supported in path mode.
- if op.owner == (common.Hash{}) {
- rawdb.DeleteAccountTrieNode(dbw, op.path)
- } else {
- rawdb.DeleteStorageTrieNode(dbw, op.owner, op.path)
- }
- deletionGauge.Inc(1)
- } else {
- if op.owner == (common.Hash{}) {
- account += 1
- } else {
- storage += 1
- }
- rawdb.WriteTrieNode(dbw, op.owner, op.path, op.hash, op.blob, s.scheme)
- }
- }
- accountNodeSyncedGauge.Inc(int64(account))
- storageNodeSyncedGauge.Inc(int64(storage))
-
- // Flush the pending code writes into database batch.
- for hash, value := range s.membatch.codes {
- rawdb.WriteCode(dbw, hash, value)
- }
- codeSyncedGauge.Inc(int64(len(s.membatch.codes)))
-
- s.membatch = newSyncMemBatch(s.scheme) // reset the batch
- return nil
-}
-
-// MemSize returns an estimated size (in bytes) of the data held in the membatch.
-func (s *Sync) MemSize() uint64 {
- return s.membatch.size
-}
-
-// Pending returns the number of state entries currently pending for download.
-func (s *Sync) Pending() int {
- return len(s.nodeReqs) + len(s.codeReqs)
-}
-
-// scheduleNodeRequest inserts a new state retrieval request into the fetch queue. If there
-// is already a pending request for this node, the new request will be discarded
-// and only a parent reference added to the old one.
-func (s *Sync) scheduleNodeRequest(req *nodeRequest) {
- s.nodeReqs[string(req.path)] = req
-
- // Schedule the request for future retrieval. This queue is shared
- // by both node requests and code requests.
- prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents
- for i := 0; i < 14 && i < len(req.path); i++ {
- prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order
- }
- s.queue.Push(string(req.path), prio)
-}
-
-// scheduleCodeRequest inserts a new state retrieval request into the fetch queue. If there
-// is already a pending request for this node, the new request will be discarded
-// and only a parent reference added to the old one.
-func (s *Sync) scheduleCodeRequest(req *codeRequest) {
- // If we're already requesting this node, add a new reference and stop
- if old, ok := s.codeReqs[req.hash]; ok {
- old.parents = append(old.parents, req.parents...)
- return
- }
- s.codeReqs[req.hash] = req
-
- // Schedule the request for future retrieval. This queue is shared
- // by both node requests and code requests.
- prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents
- for i := 0; i < 14 && i < len(req.path); i++ {
- prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order
- }
- s.queue.Push(req.hash, prio)
-}
-
-// children retrieves all the missing children of a state trie entry for future
-// retrieval scheduling.
-func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
- // Gather all the children of the node, irrelevant whether known or not
- type childNode struct {
- path []byte
- node node
- }
- var children []childNode
-
- switch node := (object).(type) {
- case *shortNode:
- key := node.Key
- if hasTerm(key) {
- key = key[:len(key)-1]
- }
- children = []childNode{{
- node: node.Val,
- path: append(append([]byte(nil), req.path...), key...),
- }}
- // Mark all internal nodes between shortNode and its **in disk**
- // child as invalid. This is essential in the case of path mode
- // scheme; otherwise, state healing might overwrite existing child
- // nodes silently while leaving a dangling parent node within the
- // range of this internal path on disk and the persistent state
- // ends up with a very weird situation that nodes on the same path
- // are not inconsistent while they all present in disk. This property
- // would break the guarantee for state healing.
- //
- // While it's possible for this shortNode to overwrite a previously
- // existing full node, the other branches of the fullNode can be
- // retained as they are not accessible with the new shortNode, and
- // also the whole sub-trie is still untouched and complete.
- //
- // This step is only necessary for path mode, as there is no deletion
- // in hash mode at all.
- if _, ok := node.Val.(hashNode); ok && s.scheme == rawdb.PathScheme {
- owner, inner := ResolvePath(req.path)
- for i := 1; i < len(key); i++ {
- // While checking for a non-existent item in Pebble can be less efficient
- // without a bloom filter, the relatively low frequency of lookups makes
- // the performance impact negligible.
- var exists bool
- if owner == (common.Hash{}) {
- exists = rawdb.ExistsAccountTrieNode(s.database, append(inner, key[:i]...))
- } else {
- exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...))
- }
- if exists {
- s.membatch.delNode(owner, append(inner, key[:i]...))
- log.Debug("Detected dangling node", "owner", owner, "path", append(inner, key[:i]...))
- }
- }
- lookupGauge.Inc(int64(len(key) - 1))
- }
- case *fullNode:
- for i := 0; i < 17; i++ {
- if node.Children[i] != nil {
- children = append(children, childNode{
- node: node.Children[i],
- path: append(append([]byte(nil), req.path...), byte(i)),
- })
- }
- }
- default:
- panic(fmt.Sprintf("unknown node: %+v", node))
- }
- // Iterate over the children, and request all unknown ones
- var (
- missing = make(chan *nodeRequest, len(children))
- pending sync.WaitGroup
- batchMu sync.Mutex
- )
- for _, child := range children {
- // Notify any external watcher of a new key/value node
- if req.callback != nil {
- if node, ok := (child.node).(valueNode); ok {
- var paths [][]byte
- if len(child.path) == 2*common.HashLength {
- paths = append(paths, hexToKeybytes(child.path))
- } else if len(child.path) == 4*common.HashLength {
- paths = append(paths, hexToKeybytes(child.path[:2*common.HashLength]))
- paths = append(paths, hexToKeybytes(child.path[2*common.HashLength:]))
- }
- if err := req.callback(paths, child.path, node, req.hash, req.path); err != nil {
- return nil, err
- }
- }
- }
- // If the child references another node, resolve or schedule.
- // We check all children concurrently.
- if node, ok := (child.node).(hashNode); ok {
- path := child.path
- hash := common.BytesToHash(node)
- pending.Add(1)
- go func() {
- defer pending.Done()
- owner, inner := ResolvePath(path)
- exist, inconsistent := s.hasNode(owner, inner, hash)
- if exist {
- return
- } else if inconsistent {
- // There is a pre-existing node with the wrong hash in DB, remove it.
- batchMu.Lock()
- s.membatch.delNode(owner, inner)
- batchMu.Unlock()
- }
- // Locally unknown node, schedule for retrieval
- missing <- &nodeRequest{
- path: path,
- hash: hash,
- parent: req,
- callback: req.callback,
- }
- }()
- }
- }
- pending.Wait()
-
- requests := make([]*nodeRequest, 0, len(children))
- for done := false; !done; {
- select {
- case miss := <-missing:
- requests = append(requests, miss)
- default:
- done = true
- }
- }
- return requests, nil
-}
-
-// commitNodeRequest finalizes a retrieval request and stores it into the membatch. If any
-// of the referencing parent requests complete due to this commit, they are also
-// committed themselves.
-func (s *Sync) commitNodeRequest(req *nodeRequest) error {
- // Write the node content to the membatch
- owner, path := ResolvePath(req.path)
- s.membatch.addNode(owner, path, req.data, req.hash)
-
- // Removed the completed node request
- delete(s.nodeReqs, string(req.path))
- s.fetches[len(req.path)]--
-
- // Check parent for completion
- if req.parent != nil {
- req.parent.deps--
- if req.parent.deps == 0 {
- if err := s.commitNodeRequest(req.parent); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// commitCodeRequest finalizes a retrieval request and stores it into the membatch. If any
-// of the referencing parent requests complete due to this commit, they are also
-// committed themselves.
-func (s *Sync) commitCodeRequest(req *codeRequest) error {
- // Write the node content to the membatch
- s.membatch.addCode(req.hash, req.data)
-
- // Removed the completed code request
- delete(s.codeReqs, req.hash)
- s.fetches[len(req.path)]--
-
- // Check all parents for completion
- for _, parent := range req.parents {
- parent.deps--
- if parent.deps == 0 {
- if err := s.commitNodeRequest(parent); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// hasNode reports whether the specified trie node is present in the database.
-// 'exists' is true when the node exists in the database and matches the given root
-// hash. The 'inconsistent' return value is true when the node exists but does not
-// match the expected hash.
-func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists bool, inconsistent bool) {
- // If node is running with hash scheme, check the presence with node hash.
- if s.scheme == rawdb.HashScheme {
- return rawdb.HasLegacyTrieNode(s.database, hash), false
- }
- // If node is running with path scheme, check the presence with node path.
- var blob []byte
- var dbHash common.Hash
- if owner == (common.Hash{}) {
- blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path)
- } else {
- blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path)
- }
- exists = hash == dbHash
- inconsistent = !exists && len(blob) != 0
- return exists, inconsistent
-}
-
-// ResolvePath resolves the provided composite node path by separating the
-// path in account trie if it's existent.
-func ResolvePath(path []byte) (common.Hash, []byte) {
- var owner common.Hash
- if len(path) >= 2*common.HashLength {
- owner = common.BytesToHash(hexToKeybytes(path[:2*common.HashLength]))
- path = path[2*common.HashLength:]
- }
- return owner, path
-}
diff --git a/trie/sync_test.go b/trie/sync_test.go
deleted file mode 100644
index 585181b48c..0000000000
--- a/trie/sync_test.go
+++ /dev/null
@@ -1,1015 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "testing"
-
- "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/ethdb"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// makeTestTrie create a sample test trie to test node-wise reconstruction.
-func makeTestTrie(scheme string) (ethdb.Database, *Database, *StateTrie, map[string][]byte) {
- // Create an empty trie
- db := rawdb.NewMemoryDatabase()
- triedb := newTestDatabase(db, scheme)
- trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb)
-
- // Fill it with some arbitrary data
- content := make(map[string][]byte)
- for i := byte(0); i < 255; i++ {
- // Map the same data under multiple keys
- key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
-
- key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
-
- // Add some other data to inflate the trie
- for j := byte(3); j < 13; j++ {
- key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i}
- content[string(key)] = val
- trie.MustUpdate(key, val)
- }
- }
- root, nodes, _ := trie.Commit(false)
- if err := triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- panic(fmt.Errorf("failed to commit db %v", err))
- }
- if err := triedb.Commit(root, false); err != nil {
- panic(err)
- }
- // Re-create the trie based on the new state
- trie, _ = NewStateTrie(TrieID(root), triedb)
- return db, triedb, trie, content
-}
-
-// checkTrieContents cross references a reconstructed trie with an expected data
-// content map.
-func checkTrieContents(t *testing.T, db ethdb.Database, scheme string, root []byte, content map[string][]byte, rawTrie bool) {
- // Check root availability and trie contents
- ndb := newTestDatabase(db, scheme)
- if err := checkTrieConsistency(db, scheme, common.BytesToHash(root), rawTrie); err != nil {
- t.Fatalf("inconsistent trie at %x: %v", root, err)
- }
- type reader interface {
- MustGet(key []byte) []byte
- }
- var r reader
- if rawTrie {
- trie, err := New(TrieID(common.BytesToHash(root)), ndb)
- if err != nil {
- t.Fatalf("failed to create trie at %x: %v", root, err)
- }
- r = trie
- } else {
- trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), ndb)
- if err != nil {
- t.Fatalf("failed to create trie at %x: %v", root, err)
- }
- r = trie
- }
- for key, val := range content {
- if have := r.MustGet([]byte(key)); !bytes.Equal(have, val) {
- t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val)
- }
- }
-}
-
-// checkTrieConsistency checks that all nodes in a trie are indeed present.
-func checkTrieConsistency(db ethdb.Database, scheme string, root common.Hash, rawTrie bool) error {
- ndb := newTestDatabase(db, scheme)
- var it NodeIterator
- if rawTrie {
- trie, err := New(TrieID(root), ndb)
- if err != nil {
- return nil // Consider a non existent state consistent
- }
- it = trie.MustNodeIterator(nil)
- } else {
- trie, err := NewStateTrie(TrieID(root), ndb)
- if err != nil {
- return nil // Consider a non existent state consistent
- }
- it = trie.MustNodeIterator(nil)
- }
- for it.Next(true) {
- }
- return it.Error()
-}
-
-// trieElement represents the element in the state trie(bytecode or trie node).
-type trieElement struct {
- path string
- hash common.Hash
- syncPath SyncPath
-}
-
-// Tests that an empty trie is not scheduled for syncing.
-func TestEmptySync(t *testing.T) {
- dbA := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
- dbB := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)
- dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
- dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
-
- emptyA := NewEmpty(dbA)
- emptyB, _ := New(TrieID(types.EmptyRootHash), dbB)
- emptyC := NewEmpty(dbC)
- emptyD, _ := New(TrieID(types.EmptyRootHash), dbD)
-
- for i, trie := range []*Trie{emptyA, emptyB, emptyC, emptyD} {
- sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB, dbC, dbD}[i].Scheme())
- if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 {
- t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes)
- }
- }
-}
-
-// Tests that given a root hash, a trie can sync iteratively on a single thread,
-// requesting retrieval tasks and returning all of them in one go.
-func TestIterativeSync(t *testing.T) {
- testIterativeSync(t, 1, false, rawdb.HashScheme)
- testIterativeSync(t, 100, false, rawdb.HashScheme)
- testIterativeSync(t, 1, true, rawdb.HashScheme)
- testIterativeSync(t, 100, true, rawdb.HashScheme)
- testIterativeSync(t, 1, false, rawdb.PathScheme)
- testIterativeSync(t, 100, false, rawdb.PathScheme)
- testIterativeSync(t, 1, true, rawdb.PathScheme)
- testIterativeSync(t, 100, true, rawdb.PathScheme)
-}
-
-func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(count)
- var elements []trieElement
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(elements) > 0 {
- results := make([]NodeSyncResult, len(elements))
- if !bypath {
- for i, element := range elements {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- } else {
- for i, element := range elements {
- data, _, err := srcTrie.GetNode(element.syncPath[len(element.syncPath)-1])
- if err != nil {
- t.Fatalf("failed to retrieve node data for path %x: %v", element.path, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- }
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- paths, nodes, _ = sched.Missing(count)
- elements = elements[:0]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-}
-
-// Tests that the trie scheduler can correctly reconstruct the state even if only
-// partial results are returned, and the others sent only later.
-func TestIterativeDelayedSync(t *testing.T) {
- testIterativeDelayedSync(t, rawdb.HashScheme)
- testIterativeDelayedSync(t, rawdb.PathScheme)
-}
-
-func testIterativeDelayedSync(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(10000)
- var elements []trieElement
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(elements) > 0 {
- // Sync only half of the scheduled nodes
- results := make([]NodeSyncResult, len(elements)/2+1)
- for i, element := range elements[:len(results)] {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- paths, nodes, _ = sched.Missing(10000)
- elements = elements[len(results):]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-}
-
-// Tests that given a root hash, a trie can sync iteratively on a single thread,
-// requesting retrieval tasks and returning all of them in one go, however in a
-// random order.
-func TestIterativeRandomSyncIndividual(t *testing.T) {
- testIterativeRandomSync(t, 1, rawdb.HashScheme)
- testIterativeRandomSync(t, 100, rawdb.HashScheme)
- testIterativeRandomSync(t, 1, rawdb.PathScheme)
- testIterativeRandomSync(t, 100, rawdb.PathScheme)
-}
-
-func testIterativeRandomSync(t *testing.T, count int, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(count)
- queue := make(map[string]trieElement)
- for i, path := range paths {
- queue[path] = trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- }
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(queue) > 0 {
- // Fetch all the queued nodes in a random order
- results := make([]NodeSyncResult, 0, len(queue))
- for path, element := range queue {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- results = append(results, NodeSyncResult{path, data})
- }
- // Feed the retrieved results back and queue new tasks
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- paths, nodes, _ = sched.Missing(count)
- queue = make(map[string]trieElement)
- for i, path := range paths {
- queue[path] = trieElement{
- path: path,
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(path)),
- }
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-}
-
-// Tests that the trie scheduler can correctly reconstruct the state even if only
-// partial results are returned (Even those randomly), others sent only later.
-func TestIterativeRandomDelayedSync(t *testing.T) {
- testIterativeRandomDelayedSync(t, rawdb.HashScheme)
- testIterativeRandomDelayedSync(t, rawdb.PathScheme)
-}
-
-func testIterativeRandomDelayedSync(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(10000)
- queue := make(map[string]trieElement)
- for i, path := range paths {
- queue[path] = trieElement{
- path: path,
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(path)),
- }
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(queue) > 0 {
- // Sync only half of the scheduled nodes, even those in random order
- results := make([]NodeSyncResult, 0, len(queue)/2+1)
- for path, element := range queue {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- results = append(results, NodeSyncResult{path, data})
-
- if len(results) >= cap(results) {
- break
- }
- }
- // Feed the retrieved results back and queue new tasks
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
- for _, result := range results {
- delete(queue, result.Path)
- }
- paths, nodes, _ = sched.Missing(10000)
- for i, path := range paths {
- queue[path] = trieElement{
- path: path,
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(path)),
- }
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-}
-
-// Tests that a trie sync will not request nodes multiple times, even if they
-// have such references.
-func TestDuplicateAvoidanceSync(t *testing.T) {
- testDuplicateAvoidanceSync(t, rawdb.HashScheme)
- testDuplicateAvoidanceSync(t, rawdb.PathScheme)
-}
-
-func testDuplicateAvoidanceSync(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(0)
- var elements []trieElement
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- requested := make(map[common.Hash]struct{})
- for len(elements) > 0 {
- results := make([]NodeSyncResult, len(elements))
- for i, element := range elements {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- if _, ok := requested[element.hash]; ok {
- t.Errorf("hash %x already requested once", element.hash)
- }
- requested[element.hash] = struct{}{}
-
- results[i] = NodeSyncResult{element.path, data}
- }
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- paths, nodes, _ = sched.Missing(0)
- elements = elements[:0]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-}
-
-// Tests that at any point in time during a sync, only complete sub-tries are in
-// the database.
-func TestIncompleteSyncHash(t *testing.T) {
- testIncompleteSync(t, rawdb.HashScheme)
- testIncompleteSync(t, rawdb.PathScheme)
-}
-
-func testIncompleteSync(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, _ := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- var (
- addedKeys []string
- addedHashes []common.Hash
- elements []trieElement
- root = srcTrie.Hash()
- )
- paths, nodes, _ := sched.Missing(1)
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(elements) > 0 {
- // Fetch a batch of trie nodes
- results := make([]NodeSyncResult, len(elements))
- for i, element := range elements {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- // Process each of the trie nodes
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- for _, result := range results {
- hash := crypto.Keccak256Hash(result.Data)
- if hash != root {
- addedKeys = append(addedKeys, result.Path)
- addedHashes = append(addedHashes, hash)
- }
- }
- // Fetch the next batch to retrieve
- paths, nodes, _ = sched.Missing(1)
- elements = elements[:0]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- }
- // Sanity check that removing any node from the database is detected
- for i, path := range addedKeys {
- if rand.Int31n(100) > 5 {
- // Only check 5 percent of added keys as a sanity check
- continue
- }
- owner, inner := ResolvePath([]byte(path))
- nodeHash := addedHashes[i]
- value := rawdb.ReadTrieNode(diskdb, owner, inner, nodeHash, scheme)
- rawdb.DeleteTrieNode(diskdb, owner, inner, nodeHash, scheme)
- if err := checkTrieConsistency(diskdb, srcDb.Scheme(), root, false); err == nil {
- t.Fatalf("trie inconsistency not caught, missing: %x", path)
- }
- rawdb.WriteTrieNode(diskdb, owner, inner, nodeHash, value, scheme)
- }
-}
-
-// Tests that trie nodes get scheduled lexicographically when having the same
-// depth.
-func TestSyncOrdering(t *testing.T) {
- testSyncOrdering(t, rawdb.HashScheme)
- testSyncOrdering(t, rawdb.PathScheme)
-}
-
-func testSyncOrdering(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler, tracking the requests
- diskdb := rawdb.NewMemoryDatabase()
- sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- var (
- reqs []SyncPath
- elements []trieElement
- )
- paths, nodes, _ := sched.Missing(1)
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- reqs = append(reqs, NewSyncPath([]byte(paths[i])))
- }
- reader, err := srcDb.Reader(srcTrie.Hash())
- if err != nil {
- t.Fatalf("State is not available %x", srcTrie.Hash())
- }
- for len(elements) > 0 {
- results := make([]NodeSyncResult, len(elements))
- for i, element := range elements {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- for _, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result %v", err)
- }
- }
- batch := diskdb.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- batch.Write()
-
- paths, nodes, _ = sched.Missing(1)
- elements = elements[:0]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- reqs = append(reqs, NewSyncPath([]byte(paths[i])))
- }
- }
- // Cross check that the two tries are in sync
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-
- // Check that the trie nodes have been requested path-ordered
- for i := 0; i < len(reqs)-1; i++ {
- if len(reqs[i]) > 1 || len(reqs[i+1]) > 1 {
- // In the case of the trie tests, there's no storage so the tuples
- // must always be single items. 2-tuples should be tested in state.
- t.Errorf("Invalid request tuples: len(%v) or len(%v) > 1", reqs[i], reqs[i+1])
- }
- if bytes.Compare(compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) > 0 {
- t.Errorf("Invalid request order: %v before %v", compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0]))
- }
- }
-}
-func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database) {
- syncWithHookWriter(t, root, db, srcDb, nil)
-}
-
-func syncWithHookWriter(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database, hookWriter ethdb.KeyValueWriter) {
- // Create a destination trie and sync with the scheduler
- sched := NewSync(root, db, nil, srcDb.Scheme())
-
- // The code requests are ignored here since there is no code
- // at the testing trie.
- paths, nodes, _ := sched.Missing(0)
- var elements []trieElement
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- reader, err := srcDb.Reader(root)
- if err != nil {
- t.Fatalf("State is not available %x", root)
- }
- for len(elements) > 0 {
- results := make([]NodeSyncResult, len(elements))
- for i, element := range elements {
- owner, inner := ResolvePath([]byte(element.path))
- data, err := reader.Node(owner, inner, element.hash)
- if err != nil {
- t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err)
- }
- results[i] = NodeSyncResult{element.path, data}
- }
- for index, result := range results {
- if err := sched.ProcessNode(result); err != nil {
- t.Fatalf("failed to process result[%d][%v] data %v %v", index, []byte(result.Path), result.Data, err)
- }
- }
- batch := db.NewBatch()
- if err := sched.Commit(batch); err != nil {
- t.Fatalf("failed to commit data: %v", err)
- }
- if hookWriter != nil {
- batch.Replay(hookWriter)
- } else {
- batch.Write()
- }
- paths, nodes, _ = sched.Missing(0)
- elements = elements[:0]
- for i := 0; i < len(paths); i++ {
- elements = append(elements, trieElement{
- path: paths[i],
- hash: nodes[i],
- syncPath: NewSyncPath([]byte(paths[i])),
- })
- }
- }
-}
-
-// Tests that the syncing target is keeping moving which may overwrite the stale
-// states synced in the last cycle.
-func TestSyncMovingTarget(t *testing.T) {
- testSyncMovingTarget(t, rawdb.HashScheme)
- testSyncMovingTarget(t, rawdb.PathScheme)
-}
-
-func testSyncMovingTarget(t *testing.T, scheme string) {
- // Create a random trie to copy
- _, srcDb, srcTrie, srcData := makeTestTrie(scheme)
-
- // Create a destination trie and sync with the scheduler
- diskdb := rawdb.NewMemoryDatabase()
- syncWith(t, srcTrie.Hash(), diskdb, srcDb)
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false)
-
- // Push more modifications into the src trie, to see if dest trie can still
- // sync with it(overwrite stale states)
- var (
- preRoot = srcTrie.Hash()
- diff = make(map[string][]byte)
- )
- for i := byte(0); i < 10; i++ {
- key, val := randBytes(32), randBytes(32)
- srcTrie.MustUpdate(key, val)
- diff[string(key)] = val
- }
- root, nodes, _ := srcTrie.Commit(false)
- if err := srcDb.Update(root, preRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- panic(err)
- }
- if err := srcDb.Commit(root, false); err != nil {
- panic(err)
- }
- preRoot = root
- srcTrie, _ = NewStateTrie(TrieID(root), srcDb)
-
- syncWith(t, srcTrie.Hash(), diskdb, srcDb)
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), diff, false)
-
- // Revert added modifications from the src trie, to see if dest trie can still
- // sync with it(overwrite reverted states)
- var reverted = make(map[string][]byte)
- for k := range diff {
- srcTrie.MustDelete([]byte(k))
- reverted[k] = nil
- }
- for k := range srcData {
- val := randBytes(32)
- srcTrie.MustUpdate([]byte(k), val)
- reverted[k] = val
- }
- root, nodes, _ = srcTrie.Commit(false)
- if err := srcDb.Update(root, preRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil {
- panic(err)
- }
- if err := srcDb.Commit(root, false); err != nil {
- panic(err)
- }
- srcTrie, _ = NewStateTrie(TrieID(root), srcDb)
-
- syncWith(t, srcTrie.Hash(), diskdb, srcDb)
- checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), reverted, false)
-}
-
-// Tests if state syncer can correctly catch up the pivot move.
-func TestPivotMove(t *testing.T) {
- testPivotMove(t, rawdb.HashScheme, true)
- testPivotMove(t, rawdb.HashScheme, false)
- testPivotMove(t, rawdb.PathScheme, true)
- testPivotMove(t, rawdb.PathScheme, false)
-}
-
-func testPivotMove(t *testing.T, scheme string, tiny bool) {
- var (
- srcDisk = rawdb.NewMemoryDatabase()
- srcTrieDB = newTestDatabase(srcDisk, scheme)
- srcTrie, _ = New(TrieID(types.EmptyRootHash), srcTrieDB)
-
- deleteFn = func(key []byte, tr *Trie, states map[string][]byte) {
- tr.Delete(key)
- delete(states, string(key))
- }
- writeFn = func(key []byte, val []byte, tr *Trie, states map[string][]byte) {
- if val == nil {
- if tiny {
- val = randBytes(4)
- } else {
- val = randBytes(32)
- }
- }
- tr.Update(key, val)
- states[string(key)] = common.CopyBytes(val)
- }
- copyStates = func(states map[string][]byte) map[string][]byte {
- cpy := make(map[string][]byte)
- for k, v := range states {
- cpy[k] = v
- }
- return cpy
- }
- )
- stateA := make(map[string][]byte)
- writeFn([]byte{0x01, 0x23}, nil, srcTrie, stateA)
- writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateA)
- writeFn([]byte{0x12, 0x33}, nil, srcTrie, stateA)
- writeFn([]byte{0x12, 0x34}, nil, srcTrie, stateA)
- writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateA)
- writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateA)
-
- rootA, nodesA, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootA, false); err != nil {
- panic(err)
- }
- // Create a destination trie and sync with the scheduler
- destDisk := rawdb.NewMemoryDatabase()
- syncWith(t, rootA, destDisk, srcTrieDB)
- checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateA, true)
-
- // Delete element to collapse trie
- stateB := copyStates(stateA)
- srcTrie, _ = New(TrieID(rootA), srcTrieDB)
- deleteFn([]byte{0x02, 0x34}, srcTrie, stateB)
- deleteFn([]byte{0x13, 0x44}, srcTrie, stateB)
- writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateB)
-
- rootB, nodesB, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootB, rootA, 0, trienode.NewWithNodeSet(nodesB), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootB, false); err != nil {
- panic(err)
- }
- syncWith(t, rootB, destDisk, srcTrieDB)
- checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateB, true)
-
- // Add elements to expand trie
- stateC := copyStates(stateB)
- srcTrie, _ = New(TrieID(rootB), srcTrieDB)
-
- writeFn([]byte{0x01, 0x24}, stateA[string([]byte{0x01, 0x24})], srcTrie, stateC)
- writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateC)
- writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateC)
-
- rootC, nodesC, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootC, rootB, 0, trienode.NewWithNodeSet(nodesC), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootC, false); err != nil {
- panic(err)
- }
- syncWith(t, rootC, destDisk, srcTrieDB)
- checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true)
-}
-
-func TestSyncAbort(t *testing.T) {
- testSyncAbort(t, rawdb.PathScheme)
- testSyncAbort(t, rawdb.HashScheme)
-}
-
-type hookWriter struct {
- db ethdb.KeyValueStore
- filter func(key []byte, value []byte) bool
-}
-
-// Put inserts the given value into the key-value data store.
-func (w *hookWriter) Put(key []byte, value []byte) error {
- if w.filter != nil && w.filter(key, value) {
- return nil
- }
- return w.db.Put(key, value)
-}
-
-// Delete removes the key from the key-value data store.
-func (w *hookWriter) Delete(key []byte) error {
- return w.db.Delete(key)
-}
-
-func testSyncAbort(t *testing.T, scheme string) {
- var (
- srcDisk = rawdb.NewMemoryDatabase()
- srcTrieDB = newTestDatabase(srcDisk, scheme)
- srcTrie, _ = New(TrieID(types.EmptyRootHash), srcTrieDB)
-
- deleteFn = func(key []byte, tr *Trie, states map[string][]byte) {
- tr.Delete(key)
- delete(states, string(key))
- }
- writeFn = func(key []byte, val []byte, tr *Trie, states map[string][]byte) {
- if val == nil {
- val = randBytes(32)
- }
- tr.Update(key, val)
- states[string(key)] = common.CopyBytes(val)
- }
- copyStates = func(states map[string][]byte) map[string][]byte {
- cpy := make(map[string][]byte)
- for k, v := range states {
- cpy[k] = v
- }
- return cpy
- }
- )
- var (
- stateA = make(map[string][]byte)
- key = randBytes(32)
- val = randBytes(32)
- )
- for i := 0; i < 256; i++ {
- writeFn(randBytes(32), nil, srcTrie, stateA)
- }
- writeFn(key, val, srcTrie, stateA)
-
- rootA, nodesA, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootA, false); err != nil {
- panic(err)
- }
- // Create a destination trie and sync with the scheduler
- destDisk := rawdb.NewMemoryDatabase()
- syncWith(t, rootA, destDisk, srcTrieDB)
- checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateA, true)
-
- // Delete the element from the trie
- stateB := copyStates(stateA)
- srcTrie, _ = New(TrieID(rootA), srcTrieDB)
- deleteFn(key, srcTrie, stateB)
-
- rootB, nodesB, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootB, rootA, 0, trienode.NewWithNodeSet(nodesB), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootB, false); err != nil {
- panic(err)
- }
-
- // Sync the new state, but never persist the new root node. Before the
- // fix #28595, the original old root node will still be left in database
- // which breaks the next healing cycle.
- syncWithHookWriter(t, rootB, destDisk, srcTrieDB, &hookWriter{db: destDisk, filter: func(key []byte, value []byte) bool {
- if scheme == rawdb.HashScheme {
- return false
- }
- if len(value) == 0 {
- return false
- }
- ok, path := rawdb.ResolveAccountTrieNodeKey(key)
- return ok && len(path) == 0
- }})
-
- // Add elements to expand trie
- stateC := copyStates(stateB)
- srcTrie, _ = New(TrieID(rootB), srcTrieDB)
-
- writeFn(key, val, srcTrie, stateC)
- rootC, nodesC, _ := srcTrie.Commit(false)
- if err := srcTrieDB.Update(rootC, rootB, 0, trienode.NewWithNodeSet(nodesC), nil); err != nil {
- panic(err)
- }
- if err := srcTrieDB.Commit(rootC, false); err != nil {
- panic(err)
- }
- syncWith(t, rootC, destDisk, srcTrieDB)
- checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true)
-}
diff --git a/trie/testutil/utils.go b/trie/testutil/utils.go
deleted file mode 100644
index a75d0431b0..0000000000
--- a/trie/testutil/utils.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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 .
-
-package testutil
-
-import (
- crand "crypto/rand"
- "encoding/binary"
- mrand "math/rand"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// Prng is a pseudo random number generator seeded by strong randomness.
-// The randomness is printed on startup in order to make failures reproducible.
-var prng = initRand()
-
-func initRand() *mrand.Rand {
- var seed [8]byte
- crand.Read(seed[:])
- rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:]))))
- return rnd
-}
-
-// RandBytes generates a random byte slice with specified length.
-func RandBytes(n int) []byte {
- r := make([]byte, n)
- prng.Read(r)
- return r
-}
-
-// RandomHash generates a random blob of data and returns it as a hash.
-func RandomHash() common.Hash {
- return common.BytesToHash(RandBytes(common.HashLength))
-}
-
-// RandomAddress generates a random blob of data and returns it as an address.
-func RandomAddress() common.Address {
- return common.BytesToAddress(RandBytes(common.AddressLength))
-}
-
-// RandomNode generates a random node.
-func RandomNode() *trienode.Node {
- val := RandBytes(100)
- return trienode.New(crypto.Keccak256Hash(val), val)
-}
diff --git a/trie/tracer.go b/trie/tracer.go
deleted file mode 100644
index 5786af4d3e..0000000000
--- a/trie/tracer.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "github.com/ethereum/go-ethereum/common"
-)
-
-// tracer tracks the changes of trie nodes. During the trie operations,
-// some nodes can be deleted from the trie, while these deleted nodes
-// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted
-// nodes won't be removed from the disk at all. Tracer is an auxiliary tool
-// used to track all insert and delete operations of trie and capture all
-// deleted nodes eventually.
-//
-// The changed nodes can be mainly divided into two categories: the leaf
-// node and intermediate node. The former is inserted/deleted by callers
-// while the latter is inserted/deleted in order to follow the rule of trie.
-// This tool can track all of them no matter the node is embedded in its
-// parent or not, but valueNode is never tracked.
-//
-// Besides, it's also used for recording the original value of the nodes
-// when they are resolved from the disk. The pre-value of the nodes will
-// be used to construct trie history in the future.
-//
-// Note tracer is not thread-safe, callers should be responsible for handling
-// the concurrency issues by themselves.
-type tracer struct {
- inserts map[string]struct{}
- deletes map[string]struct{}
- accessList map[string][]byte
-}
-
-// newTracer initializes the tracer for capturing trie changes.
-func newTracer() *tracer {
- return &tracer{
- inserts: make(map[string]struct{}),
- deletes: make(map[string]struct{}),
- accessList: make(map[string][]byte),
- }
-}
-
-// onRead tracks the newly loaded trie node and caches the rlp-encoded
-// blob internally. Don't change the value outside of function since
-// it's not deep-copied.
-func (t *tracer) onRead(path []byte, val []byte) {
- t.accessList[string(path)] = val
-}
-
-// onInsert tracks the newly inserted trie node. If it's already
-// in the deletion set (resurrected node), then just wipe it from
-// the deletion set as it's "untouched".
-func (t *tracer) onInsert(path []byte) {
- if _, present := t.deletes[string(path)]; present {
- delete(t.deletes, string(path))
- return
- }
- t.inserts[string(path)] = struct{}{}
-}
-
-// onDelete tracks the newly deleted trie node. If it's already
-// in the addition set, then just wipe it from the addition set
-// as it's untouched.
-func (t *tracer) onDelete(path []byte) {
- if _, present := t.inserts[string(path)]; present {
- delete(t.inserts, string(path))
- return
- }
- t.deletes[string(path)] = struct{}{}
-}
-
-// reset clears the content tracked by tracer.
-func (t *tracer) reset() {
- t.inserts = make(map[string]struct{})
- t.deletes = make(map[string]struct{})
- t.accessList = make(map[string][]byte)
-}
-
-// copy returns a deep copied tracer instance.
-func (t *tracer) copy() *tracer {
- var (
- inserts = make(map[string]struct{})
- deletes = make(map[string]struct{})
- accessList = make(map[string][]byte)
- )
- for path := range t.inserts {
- inserts[path] = struct{}{}
- }
- for path := range t.deletes {
- deletes[path] = struct{}{}
- }
- for path, blob := range t.accessList {
- accessList[path] = common.CopyBytes(blob)
- }
- return &tracer{
- inserts: inserts,
- deletes: deletes,
- accessList: accessList,
- }
-}
-
-// deletedNodes returns a list of node paths which are deleted from the trie.
-func (t *tracer) deletedNodes() []string {
- var paths []string
- for path := range t.deletes {
- // It's possible a few deleted nodes were embedded
- // in their parent before, the deletions can be no
- // effect by deleting nothing, filter them out.
- _, ok := t.accessList[path]
- if !ok {
- continue
- }
- paths = append(paths, path)
- }
- return paths
-}
diff --git a/trie/tracer_test.go b/trie/tracer_test.go
deleted file mode 100644
index acb8c2f6bf..0000000000
--- a/trie/tracer_test.go
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "testing"
-
- "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/trie/trienode"
-)
-
-var (
- tiny = []struct{ k, v string }{
- {"k1", "v1"},
- {"k2", "v2"},
- {"k3", "v3"},
- }
- nonAligned = []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- standard = []struct{ k, v string }{
- {string(randBytes(32)), "verb"},
- {string(randBytes(32)), "wookiedoo"},
- {string(randBytes(32)), "stallion"},
- {string(randBytes(32)), "horse"},
- {string(randBytes(32)), "coin"},
- {string(randBytes(32)), "puppy"},
- {string(randBytes(32)), "myothernodedata"},
- }
-)
-
-func TestTrieTracer(t *testing.T) {
- testTrieTracer(t, tiny)
- testTrieTracer(t, nonAligned)
- testTrieTracer(t, standard)
-}
-
-// Tests if the trie diffs are tracked correctly. Tracer should capture
-// all non-leaf dirty nodes, no matter the node is embedded or not.
-func testTrieTracer(t *testing.T, vals []struct{ k, v string }) {
- db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(db)
-
- // Determine all new nodes are tracked
- for _, val := range vals {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- insertSet := copySet(trie.tracer.inserts) // copy before commit
- deleteSet := copySet(trie.tracer.deletes) // copy before commit
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- seen := setKeys(iterNodes(db, root))
- if !compareSet(insertSet, seen) {
- t.Fatal("Unexpected insertion set")
- }
- if !compareSet(deleteSet, nil) {
- t.Fatal("Unexpected deletion set")
- }
-
- // Determine all deletions are tracked
- trie, _ = New(TrieID(root), db)
- for _, val := range vals {
- trie.MustDelete([]byte(val.k))
- }
- insertSet, deleteSet = copySet(trie.tracer.inserts), copySet(trie.tracer.deletes)
- if !compareSet(insertSet, nil) {
- t.Fatal("Unexpected insertion set")
- }
- if !compareSet(deleteSet, seen) {
- t.Fatal("Unexpected deletion set")
- }
-}
-
-// Test that after inserting a new batch of nodes and deleting them immediately,
-// the trie tracer should be cleared normally as no operation happened.
-func TestTrieTracerNoop(t *testing.T) {
- testTrieTracerNoop(t, tiny)
- testTrieTracerNoop(t, nonAligned)
- testTrieTracerNoop(t, standard)
-}
-
-func testTrieTracerNoop(t *testing.T, vals []struct{ k, v string }) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for _, val := range vals {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- for _, val := range vals {
- trie.MustDelete([]byte(val.k))
- }
- if len(trie.tracer.inserts) != 0 {
- t.Fatal("Unexpected insertion set")
- }
- if len(trie.tracer.deletes) != 0 {
- t.Fatal("Unexpected deletion set")
- }
-}
-
-// Tests if the accessList is correctly tracked.
-func TestAccessList(t *testing.T) {
- testAccessList(t, tiny)
- testAccessList(t, nonAligned)
- testAccessList(t, standard)
-}
-
-func testAccessList(t *testing.T, vals []struct{ k, v string }) {
- var (
- db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie = NewEmpty(db)
- orig = trie.Copy()
- )
- // Create trie from scratch
- for _, val := range vals {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, nodes); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-
- // Update trie
- parent := root
- trie, _ = New(TrieID(root), db)
- orig = trie.Copy()
- for _, val := range vals {
- trie.MustUpdate([]byte(val.k), randBytes(32))
- }
- root, nodes, _ = trie.Commit(false)
- db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, nodes); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-
- // Add more new nodes
- parent = root
- trie, _ = New(TrieID(root), db)
- orig = trie.Copy()
- var keys []string
- for i := 0; i < 30; i++ {
- key := randBytes(32)
- keys = append(keys, string(key))
- trie.MustUpdate(key, randBytes(32))
- }
- root, nodes, _ = trie.Commit(false)
- db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, nodes); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-
- // Partial deletions
- parent = root
- trie, _ = New(TrieID(root), db)
- orig = trie.Copy()
- for _, key := range keys {
- trie.MustUpdate([]byte(key), nil)
- }
- root, nodes, _ = trie.Commit(false)
- db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, nodes); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-
- // Delete all
- parent = root
- trie, _ = New(TrieID(root), db)
- orig = trie.Copy()
- for _, val := range vals {
- trie.MustUpdate([]byte(val.k), nil)
- }
- root, nodes, _ = trie.Commit(false)
- db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, nodes); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-}
-
-// Tests origin values won't be tracked in Iterator or Prover
-func TestAccessListLeak(t *testing.T) {
- var (
- db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie = NewEmpty(db)
- )
- // Create trie from scratch
- for _, val := range standard {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- var cases = []struct {
- op func(tr *Trie)
- }{
- {
- func(tr *Trie) {
- it := tr.MustNodeIterator(nil)
- for it.Next(true) {
- }
- },
- },
- {
- func(tr *Trie) {
- it := NewIterator(tr.MustNodeIterator(nil))
- for it.Next() {
- }
- },
- },
- {
- func(tr *Trie) {
- for _, val := range standard {
- tr.Prove([]byte(val.k), rawdb.NewMemoryDatabase())
- }
- },
- },
- }
- for _, c := range cases {
- trie, _ = New(TrieID(root), db)
- n1 := len(trie.tracer.accessList)
- c.op(trie)
- n2 := len(trie.tracer.accessList)
-
- if n1 != n2 {
- t.Fatalf("AccessList is leaked, prev %d after %d", n1, n2)
- }
- }
-}
-
-// Tests whether the original tree node is correctly deleted after being embedded
-// in its parent due to the smaller size of the original tree node.
-func TestTinyTree(t *testing.T) {
- var (
- db = NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie = NewEmpty(db)
- )
- for _, val := range tiny {
- trie.MustUpdate([]byte(val.k), randBytes(32))
- }
- root, set, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(set), nil)
-
- parent := root
- trie, _ = New(TrieID(root), db)
- orig := trie.Copy()
- for _, val := range tiny {
- trie.MustUpdate([]byte(val.k), []byte(val.v))
- }
- root, set, _ = trie.Commit(false)
- db.Update(root, parent, 0, trienode.NewWithNodeSet(set), nil)
-
- trie, _ = New(TrieID(root), db)
- if err := verifyAccessList(orig, trie, set); err != nil {
- t.Fatalf("Invalid accessList %v", err)
- }
-}
-
-func compareSet(setA, setB map[string]struct{}) bool {
- if len(setA) != len(setB) {
- return false
- }
- for key := range setA {
- if _, ok := setB[key]; !ok {
- return false
- }
- }
- return true
-}
-
-func forNodes(tr *Trie) map[string][]byte {
- var (
- it = tr.MustNodeIterator(nil)
- nodes = make(map[string][]byte)
- )
- for it.Next(true) {
- if it.Leaf() {
- continue
- }
- nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
- }
- return nodes
-}
-
-func iterNodes(db *Database, root common.Hash) map[string][]byte {
- tr, _ := New(TrieID(root), db)
- return forNodes(tr)
-}
-
-func forHashedNodes(tr *Trie) map[string][]byte {
- var (
- it = tr.MustNodeIterator(nil)
- nodes = make(map[string][]byte)
- )
- for it.Next(true) {
- if it.Hash() == (common.Hash{}) {
- continue
- }
- nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob())
- }
- return nodes
-}
-
-func diffTries(trieA, trieB *Trie) (map[string][]byte, map[string][]byte, map[string][]byte) {
- var (
- nodesA = forHashedNodes(trieA)
- nodesB = forHashedNodes(trieB)
- inA = make(map[string][]byte) // hashed nodes in trie a but not b
- inB = make(map[string][]byte) // hashed nodes in trie b but not a
- both = make(map[string][]byte) // hashed nodes in both tries but different value
- )
- for path, blobA := range nodesA {
- if blobB, ok := nodesB[path]; ok {
- if bytes.Equal(blobA, blobB) {
- continue
- }
- both[path] = blobA
- continue
- }
- inA[path] = blobA
- }
- for path, blobB := range nodesB {
- if _, ok := nodesA[path]; ok {
- continue
- }
- inB[path] = blobB
- }
- return inA, inB, both
-}
-
-func setKeys(set map[string][]byte) map[string]struct{} {
- keys := make(map[string]struct{})
- for k := range set {
- keys[k] = struct{}{}
- }
- return keys
-}
-
-func copySet(set map[string]struct{}) map[string]struct{} {
- copied := make(map[string]struct{})
- for k := range set {
- copied[k] = struct{}{}
- }
- return copied
-}
diff --git a/trie/trie.go b/trie/trie.go
deleted file mode 100644
index 07467ac69c..0000000000
--- a/trie/trie.go
+++ /dev/null
@@ -1,672 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package trie implements Merkle Patricia Tries.
-package trie
-
-import (
- "bytes"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on
-// top of a database. Whenever trie performs a commit operation, the generated
-// nodes will be gathered and returned in a set. Once the trie is committed,
-// it's not usable anymore. Callers have to re-create the trie with new root
-// based on the updated trie database.
-//
-// Trie is not safe for concurrent use.
-type Trie struct {
- root node
- owner common.Hash
-
- // Flag whether the commit operation is already performed. If so the
- // trie is not usable(latest states is invisible).
- committed bool
-
- // Keep track of the number leaves which have been inserted since the last
- // hashing operation. This number will not directly map to the number of
- // actually unhashed nodes.
- unhashed int
-
- // reader is the handler trie can retrieve nodes from.
- reader *trieReader
-
- // tracer is the tool to track the trie changes.
- // It will be reset after each commit operation.
- tracer *tracer
-}
-
-// newFlag returns the cache flag value for a newly created node.
-func (t *Trie) newFlag() nodeFlag {
- return nodeFlag{dirty: true}
-}
-
-// Copy returns a copy of Trie.
-func (t *Trie) Copy() *Trie {
- return &Trie{
- root: t.root,
- owner: t.owner,
- committed: t.committed,
- unhashed: t.unhashed,
- reader: t.reader,
- tracer: t.tracer.copy(),
- }
-}
-
-// New creates the trie instance with provided trie id and the read-only
-// database. The state specified by trie id must be available, otherwise
-// an error will be returned. The trie root specified by trie id can be
-// zero hash or the sha3 hash of an empty string, then trie is initially
-// empty, otherwise, the root node must be present in database or returns
-// a MissingNodeError if not.
-func New(id *ID, db *Database) (*Trie, error) {
- reader, err := newTrieReader(id.StateRoot, id.Owner, db)
- if err != nil {
- return nil, err
- }
- trie := &Trie{
- owner: id.Owner,
- reader: reader,
- tracer: newTracer(),
- }
- if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash {
- rootnode, err := trie.resolveAndTrack(id.Root[:], nil)
- if err != nil {
- return nil, err
- }
- trie.root = rootnode
- }
- return trie, nil
-}
-
-// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
-func NewEmpty(db *Database) *Trie {
- tr, _ := New(TrieID(types.EmptyRootHash), db)
- return tr
-}
-
-// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered
-// error but just print out an error message.
-func (t *Trie) MustNodeIterator(start []byte) NodeIterator {
- it, err := t.NodeIterator(start)
- if err != nil {
- log.Error("Unhandled trie error in Trie.NodeIterator", "err", err)
- }
- return it
-}
-
-// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
-// the key after the given start key.
-func (t *Trie) NodeIterator(start []byte) (NodeIterator, error) {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return nil, ErrCommitted
- }
- return newNodeIterator(t, start), nil
-}
-
-// MustGet is a wrapper of Get and will omit any encountered error but just
-// print out an error message.
-func (t *Trie) MustGet(key []byte) []byte {
- res, err := t.Get(key)
- if err != nil {
- log.Error("Unhandled trie error in Trie.Get", "err", err)
- }
- return res
-}
-
-// Get returns the value for key stored in the trie.
-// The value bytes must not be modified by the caller.
-//
-// If the requested node is not present in trie, no error will be returned.
-// If the trie is corrupted, a MissingNodeError is returned.
-func (t *Trie) Get(key []byte) ([]byte, error) {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return nil, ErrCommitted
- }
- value, newroot, didResolve, err := t.get(t.root, keybytesToHex(key), 0)
- if err == nil && didResolve {
- t.root = newroot
- }
- return value, err
-}
-
-func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) {
- switch n := (origNode).(type) {
- case nil:
- return nil, nil, false, nil
- case valueNode:
- return n, n, false, nil
- case *shortNode:
- if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) {
- // key not found in trie
- return nil, n, false, nil
- }
- value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key))
- if err == nil && didResolve {
- n = n.copy()
- n.Val = newnode
- }
- return value, n, didResolve, err
- case *fullNode:
- value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1)
- if err == nil && didResolve {
- n = n.copy()
- n.Children[key[pos]] = newnode
- }
- return value, n, didResolve, err
- case hashNode:
- child, err := t.resolveAndTrack(n, key[:pos])
- if err != nil {
- return nil, n, true, err
- }
- value, newnode, _, err := t.get(child, key, pos)
- return value, newnode, true, err
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode))
- }
-}
-
-// MustGetNode is a wrapper of GetNode and will omit any encountered error but
-// just print out an error message.
-func (t *Trie) MustGetNode(path []byte) ([]byte, int) {
- item, resolved, err := t.GetNode(path)
- if err != nil {
- log.Error("Unhandled trie error in Trie.GetNode", "err", err)
- }
- return item, resolved
-}
-
-// GetNode retrieves a trie node by compact-encoded path. It is not possible
-// to use keybyte-encoding as the path might contain odd nibbles.
-//
-// If the requested node is not present in trie, no error will be returned.
-// If the trie is corrupted, a MissingNodeError is returned.
-func (t *Trie) GetNode(path []byte) ([]byte, int, error) {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return nil, 0, ErrCommitted
- }
- item, newroot, resolved, err := t.getNode(t.root, compactToHex(path), 0)
- if err != nil {
- return nil, resolved, err
- }
- if resolved > 0 {
- t.root = newroot
- }
- if item == nil {
- return nil, resolved, nil
- }
- return item, resolved, nil
-}
-
-func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) {
- // If non-existent path requested, abort
- if origNode == nil {
- return nil, nil, 0, nil
- }
- // If we reached the requested path, return the current node
- if pos >= len(path) {
- // Although we most probably have the original node expanded, encoding
- // that into consensus form can be nasty (needs to cascade down) and
- // time consuming. Instead, just pull the hash up from disk directly.
- var hash hashNode
- if node, ok := origNode.(hashNode); ok {
- hash = node
- } else {
- hash, _ = origNode.cache()
- }
- if hash == nil {
- return nil, origNode, 0, errors.New("non-consensus node")
- }
- blob, err := t.reader.node(path, common.BytesToHash(hash))
- return blob, origNode, 1, err
- }
- // Path still needs to be traversed, descend into children
- switch n := (origNode).(type) {
- case valueNode:
- // Path prematurely ended, abort
- return nil, nil, 0, nil
-
- case *shortNode:
- if len(path)-pos < len(n.Key) || !bytes.Equal(n.Key, path[pos:pos+len(n.Key)]) {
- // Path branches off from short node
- return nil, n, 0, nil
- }
- item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key))
- if err == nil && resolved > 0 {
- n = n.copy()
- n.Val = newnode
- }
- return item, n, resolved, err
-
- case *fullNode:
- item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1)
- if err == nil && resolved > 0 {
- n = n.copy()
- n.Children[path[pos]] = newnode
- }
- return item, n, resolved, err
-
- case hashNode:
- child, err := t.resolveAndTrack(n, path[:pos])
- if err != nil {
- return nil, n, 1, err
- }
- item, newnode, resolved, err := t.getNode(child, path, pos)
- return item, newnode, resolved + 1, err
-
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode))
- }
-}
-
-// MustUpdate is a wrapper of Update and will omit any encountered error but
-// just print out an error message.
-func (t *Trie) MustUpdate(key, value []byte) {
- if err := t.Update(key, value); err != nil {
- log.Error("Unhandled trie error in Trie.Update", "err", err)
- }
-}
-
-// Update associates key with value in the trie. Subsequent calls to
-// Get will return value. If value has length zero, any existing value
-// is deleted from the trie and calls to Get will return nil.
-//
-// The value bytes must not be modified by the caller while they are
-// stored in the trie.
-//
-// If the requested node is not present in trie, no error will be returned.
-// If the trie is corrupted, a MissingNodeError is returned.
-func (t *Trie) Update(key, value []byte) error {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return ErrCommitted
- }
- return t.update(key, value)
-}
-
-func (t *Trie) update(key, value []byte) error {
- t.unhashed++
- k := keybytesToHex(key)
- if len(value) != 0 {
- _, n, err := t.insert(t.root, nil, k, valueNode(value))
- if err != nil {
- return err
- }
- t.root = n
- } else {
- _, n, err := t.delete(t.root, nil, k)
- if err != nil {
- return err
- }
- t.root = n
- }
- return nil
-}
-
-func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) {
- if len(key) == 0 {
- if v, ok := n.(valueNode); ok {
- return !bytes.Equal(v, value.(valueNode)), value, nil
- }
- return true, value, nil
- }
- switch n := n.(type) {
- case *shortNode:
- matchlen := prefixLen(key, n.Key)
- // If the whole key matches, keep this short node as is
- // and only update the value.
- if matchlen == len(n.Key) {
- dirty, nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value)
- if !dirty || err != nil {
- return false, n, err
- }
- return true, &shortNode{n.Key, nn, t.newFlag()}, nil
- }
- // Otherwise branch out at the index where they differ.
- branch := &fullNode{flags: t.newFlag()}
- var err error
- _, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val)
- if err != nil {
- return false, nil, err
- }
- _, branch.Children[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value)
- if err != nil {
- return false, nil, err
- }
- // Replace this shortNode with the branch if it occurs at index 0.
- if matchlen == 0 {
- return true, branch, nil
- }
- // New branch node is created as a child of the original short node.
- // Track the newly inserted node in the tracer. The node identifier
- // passed is the path from the root node.
- t.tracer.onInsert(append(prefix, key[:matchlen]...))
-
- // Replace it with a short node leading up to the branch.
- return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil
-
- case *fullNode:
- dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value)
- if !dirty || err != nil {
- return false, n, err
- }
- n = n.copy()
- n.flags = t.newFlag()
- n.Children[key[0]] = nn
- return true, n, nil
-
- case nil:
- // New short node is created and track it in the tracer. The node identifier
- // passed is the path from the root node. Note the valueNode won't be tracked
- // since it's always embedded in its parent.
- t.tracer.onInsert(prefix)
-
- return true, &shortNode{key, value, t.newFlag()}, nil
-
- case hashNode:
- // We've hit a part of the trie that isn't loaded yet. Load
- // the node and insert into it. This leaves all child nodes on
- // the path to the value in the trie.
- rn, err := t.resolveAndTrack(n, prefix)
- if err != nil {
- return false, nil, err
- }
- dirty, nn, err := t.insert(rn, prefix, key, value)
- if !dirty || err != nil {
- return false, rn, err
- }
- return true, nn, nil
-
- default:
- panic(fmt.Sprintf("%T: invalid node: %v", n, n))
- }
-}
-
-// MustDelete is a wrapper of Delete and will omit any encountered error but
-// just print out an error message.
-func (t *Trie) MustDelete(key []byte) {
- if err := t.Delete(key); err != nil {
- log.Error("Unhandled trie error in Trie.Delete", "err", err)
- }
-}
-
-// Delete removes any existing value for key from the trie.
-//
-// If the requested node is not present in trie, no error will be returned.
-// If the trie is corrupted, a MissingNodeError is returned.
-func (t *Trie) Delete(key []byte) error {
- // Short circuit if the trie is already committed and not usable.
- if t.committed {
- return ErrCommitted
- }
- t.unhashed++
- k := keybytesToHex(key)
- _, n, err := t.delete(t.root, nil, k)
- if err != nil {
- return err
- }
- t.root = n
- return nil
-}
-
-// delete returns the new root of the trie with key deleted.
-// It reduces the trie to minimal form by simplifying
-// nodes on the way up after deleting recursively.
-func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
- switch n := n.(type) {
- case *shortNode:
- matchlen := prefixLen(key, n.Key)
- if matchlen < len(n.Key) {
- return false, n, nil // don't replace n on mismatch
- }
- if matchlen == len(key) {
- // The matched short node is deleted entirely and track
- // it in the deletion set. The same the valueNode doesn't
- // need to be tracked at all since it's always embedded.
- t.tracer.onDelete(prefix)
-
- return true, nil, nil // remove n entirely for whole matches
- }
- // The key is longer than n.Key. Remove the remaining suffix
- // from the subtrie. Child can never be nil here since the
- // subtrie must contain at least two other values with keys
- // longer than n.Key.
- dirty, child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):])
- if !dirty || err != nil {
- return false, n, err
- }
- switch child := child.(type) {
- case *shortNode:
- // The child shortNode is merged into its parent, track
- // is deleted as well.
- t.tracer.onDelete(append(prefix, n.Key...))
-
- // Deleting from the subtrie reduced it to another
- // short node. Merge the nodes to avoid creating a
- // shortNode{..., shortNode{...}}. Use concat (which
- // always creates a new slice) instead of append to
- // avoid modifying n.Key since it might be shared with
- // other nodes.
- return true, &shortNode{concat(n.Key, child.Key...), child.Val, t.newFlag()}, nil
- default:
- return true, &shortNode{n.Key, child, t.newFlag()}, nil
- }
-
- case *fullNode:
- dirty, nn, err := t.delete(n.Children[key[0]], append(prefix, key[0]), key[1:])
- if !dirty || err != nil {
- return false, n, err
- }
- n = n.copy()
- n.flags = t.newFlag()
- n.Children[key[0]] = nn
-
- // Because n is a full node, it must've contained at least two children
- // before the delete operation. If the new child value is non-nil, n still
- // has at least two children after the deletion, and cannot be reduced to
- // a short node.
- if nn != nil {
- return true, n, nil
- }
- // Reduction:
- // Check how many non-nil entries are left after deleting and
- // reduce the full node to a short node if only one entry is
- // left. Since n must've contained at least two children
- // before deletion (otherwise it would not be a full node) n
- // can never be reduced to nil.
- //
- // When the loop is done, pos contains the index of the single
- // value that is left in n or -2 if n contains at least two
- // values.
- pos := -1
- for i, cld := range &n.Children {
- if cld != nil {
- if pos == -1 {
- pos = i
- } else {
- pos = -2
- break
- }
- }
- }
- if pos >= 0 {
- if pos != 16 {
- // If the remaining entry is a short node, it replaces
- // n and its key gets the missing nibble tacked to the
- // front. This avoids creating an invalid
- // shortNode{..., shortNode{...}}. Since the entry
- // might not be loaded yet, resolve it just for this
- // check.
- cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos)))
- if err != nil {
- return false, nil, err
- }
- if cnode, ok := cnode.(*shortNode); ok {
- // Replace the entire full node with the short node.
- // Mark the original short node as deleted since the
- // value is embedded into the parent now.
- t.tracer.onDelete(append(prefix, byte(pos)))
-
- k := append([]byte{byte(pos)}, cnode.Key...)
- return true, &shortNode{k, cnode.Val, t.newFlag()}, nil
- }
- }
- // Otherwise, n is replaced by a one-nibble short node
- // containing the child.
- return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], t.newFlag()}, nil
- }
- // n still contains at least two values and cannot be reduced.
- return true, n, nil
-
- case valueNode:
- return true, nil, nil
-
- case nil:
- return false, nil, nil
-
- case hashNode:
- // We've hit a part of the trie that isn't loaded yet. Load
- // the node and delete from it. This leaves all child nodes on
- // the path to the value in the trie.
- rn, err := t.resolveAndTrack(n, prefix)
- if err != nil {
- return false, nil, err
- }
- dirty, nn, err := t.delete(rn, prefix, key)
- if !dirty || err != nil {
- return false, rn, err
- }
- return true, nn, nil
-
- default:
- panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key))
- }
-}
-
-func concat(s1 []byte, s2 ...byte) []byte {
- r := make([]byte, len(s1)+len(s2))
- copy(r, s1)
- copy(r[len(s1):], s2)
- return r
-}
-
-func (t *Trie) resolve(n node, prefix []byte) (node, error) {
- if n, ok := n.(hashNode); ok {
- return t.resolveAndTrack(n, prefix)
- }
- return n, nil
-}
-
-// resolveAndTrack loads node from the underlying store with the given node hash
-// and path prefix and also tracks the loaded node blob in tracer treated as the
-// node's original value. The rlp-encoded blob is preferred to be loaded from
-// database because it's easy to decode node while complex to encode node to blob.
-func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
- blob, err := t.reader.node(prefix, common.BytesToHash(n))
- if err != nil {
- return nil, err
- }
- t.tracer.onRead(prefix, blob)
- return mustDecodeNode(n, blob), nil
-}
-
-// Hash returns the root hash of the trie. It does not write to the
-// database and can be used even if the trie doesn't have one.
-func (t *Trie) Hash() common.Hash {
- hash, cached := t.hashRoot()
- t.root = cached
- return common.BytesToHash(hash.(hashNode))
-}
-
-// Commit collects all dirty nodes in the trie and replaces them with the
-// corresponding node hash. All collected nodes (including dirty leaves if
-// collectLeaf is true) will be encapsulated into a nodeset for return.
-// The returned nodeset can be nil if the trie is clean (nothing to commit).
-// Once the trie is committed, it's not usable anymore. A new trie must
-// be created with new root and updated trie database for following usage
-func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
- defer t.tracer.reset()
- defer func() {
- t.committed = true
- }()
- // Trie is empty and can be classified into two types of situations:
- // (a) The trie was empty and no update happens => return nil
- // (b) The trie was non-empty and all nodes are dropped => return
- // the node set includes all deleted nodes
- if t.root == nil {
- paths := t.tracer.deletedNodes()
- if len(paths) == 0 {
- return types.EmptyRootHash, nil, nil // case (a)
- }
- nodes := trienode.NewNodeSet(t.owner)
- for _, path := range paths {
- nodes.AddNode([]byte(path), trienode.NewDeleted())
- }
- return types.EmptyRootHash, nodes, nil // case (b)
- }
- // Derive the hash for all dirty nodes first. We hold the assumption
- // in the following procedure that all nodes are hashed.
- rootHash := t.Hash()
-
- // Do a quick check if we really need to commit. This can happen e.g.
- // if we load a trie for reading storage values, but don't write to it.
- if hashedNode, dirty := t.root.cache(); !dirty {
- // Replace the root node with the origin hash in order to
- // ensure all resolved nodes are dropped after the commit.
- t.root = hashedNode
- return rootHash, nil, nil
- }
- nodes := trienode.NewNodeSet(t.owner)
- for _, path := range t.tracer.deletedNodes() {
- nodes.AddNode([]byte(path), trienode.NewDeleted())
- }
- t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root)
- return rootHash, nodes, nil
-}
-
-// hashRoot calculates the root hash of the given trie
-func (t *Trie) hashRoot() (node, node) {
- if t.root == nil {
- return hashNode(types.EmptyRootHash.Bytes()), nil
- }
- // If the number of changes is below 100, we let one thread handle it
- h := newHasher(t.unhashed >= 100)
- defer func() {
- returnHasherToPool(h)
- t.unhashed = 0
- }()
- hashed, cached := h.hash(t.root, true)
- return hashed, cached
-}
-
-// Reset drops the referenced root node and cleans all internal state.
-func (t *Trie) Reset() {
- t.root = nil
- t.owner = common.Hash{}
- t.unhashed = 0
- t.tracer.reset()
- t.committed = false
-}
diff --git a/trie/trie_id.go b/trie/trie_id.go
deleted file mode 100644
index 8ab490ca3b..0000000000
--- a/trie/trie_id.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see
-
-package trie
-
-import "github.com/ethereum/go-ethereum/common"
-
-// ID is the identifier for uniquely identifying a trie.
-type ID struct {
- StateRoot common.Hash // The root of the corresponding state(block.root)
- Owner common.Hash // The contract address hash which the trie belongs to
- Root common.Hash // The root hash of trie
-}
-
-// StateTrieID constructs an identifier for state trie with the provided state root.
-func StateTrieID(root common.Hash) *ID {
- return &ID{
- StateRoot: root,
- Owner: common.Hash{},
- Root: root,
- }
-}
-
-// StorageTrieID constructs an identifier for storage trie which belongs to a certain
-// state and contract specified by the stateRoot and owner.
-func StorageTrieID(stateRoot common.Hash, owner common.Hash, root common.Hash) *ID {
- return &ID{
- StateRoot: stateRoot,
- Owner: owner,
- Root: root,
- }
-}
-
-// TrieID constructs an identifier for a standard trie(not a second-layer trie)
-// with provided root. It's mostly used in tests and some other tries like CHT trie.
-func TrieID(root common.Hash) *ID {
- return &ID{
- StateRoot: root,
- Owner: common.Hash{},
- Root: root,
- }
-}
diff --git a/trie/trie_reader.go b/trie/trie_reader.go
deleted file mode 100644
index 4215964559..0000000000
--- a/trie/trie_reader.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package 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/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
-// for concurrent usage.
-type trieReader struct {
- owner common.Hash
- reader Reader
- banned map[string]struct{} // Marker to prevent node from being accessed, for tests
-}
-
-// newTrieReader initializes the trie reader with the given node reader.
-func newTrieReader(stateRoot, owner common.Hash, db *Database) (*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.Reader(stateRoot)
- if err != nil {
- return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err}
- }
- return &trieReader{owner: owner, reader: reader}, nil
-}
-
-// newEmptyReader initializes the pure in-memory reader. All read operations
-// should be forbidden and returns the MissingNodeError.
-func newEmptyReader() *trieReader {
- return &trieReader{}
-}
-
-// node retrieves the rlp-encoded trie node with the provided trie node
-// information. An MissingNodeError will be returned in case the node is
-// not found or any error is encountered.
-func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
- // Perform the logics in tests for preventing trie node access.
- if r.banned != nil {
- if _, ok := r.banned[string(path)]; ok {
- return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
- }
- }
- if r.reader == nil {
- return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path}
- }
- blob, err := r.reader.Node(r.owner, path, hash)
- if err != nil || len(blob) == 0 {
- return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err}
- }
- return blob, nil
-}
-
-// trieLoader implements triestate.TrieLoader for constructing tries.
-type trieLoader struct {
- db *Database
-}
-
-// OpenTrie opens the main account trie.
-func (l *trieLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
- return New(TrieID(root), l.db)
-}
-
-// OpenStorageTrie opens the storage trie of an account.
-func (l *trieLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
- return New(StorageTrieID(stateRoot, addrHash, root), l.db)
-}
diff --git a/trie/trie_test.go b/trie/trie_test.go
deleted file mode 100644
index c5bd3faf53..0000000000
--- a/trie/trie_test.go
+++ /dev/null
@@ -1,1224 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "hash"
- "io"
- "math/big"
- "math/rand"
- "reflect"
- "testing"
- "testing/quick"
-
- "github.com/davecgh/go-spew/spew"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "golang.org/x/crypto/sha3"
-)
-
-func init() {
- spew.Config.Indent = " "
- spew.Config.DisableMethods = false
-}
-
-func TestEmptyTrie(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- res := trie.Hash()
- exp := types.EmptyRootHash
- if res != exp {
- t.Errorf("expected %x got %x", exp, res)
- }
-}
-
-func TestNull(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- key := make([]byte, 32)
- value := []byte("test")
- trie.MustUpdate(key, value)
- if !bytes.Equal(trie.MustGet(key), value) {
- t.Fatal("wrong value")
- }
-}
-
-func TestMissingRoot(t *testing.T) {
- testMissingRoot(t, rawdb.HashScheme)
- testMissingRoot(t, rawdb.PathScheme)
-}
-
-func testMissingRoot(t *testing.T, scheme string) {
- root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")
- trie, err := New(TrieID(root), newTestDatabase(rawdb.NewMemoryDatabase(), scheme))
- if trie != nil {
- t.Error("New returned non-nil trie for invalid root")
- }
- if _, ok := err.(*MissingNodeError); !ok {
- t.Errorf("New returned wrong error: %v", err)
- }
-}
-
-func TestMissingNode(t *testing.T) {
- testMissingNode(t, false, rawdb.HashScheme)
- testMissingNode(t, false, rawdb.PathScheme)
- testMissingNode(t, true, rawdb.HashScheme)
- testMissingNode(t, true, rawdb.PathScheme)
-}
-
-func testMissingNode(t *testing.T, memonly bool, scheme string) {
- diskdb := rawdb.NewMemoryDatabase()
- triedb := newTestDatabase(diskdb, scheme)
-
- trie := NewEmpty(triedb)
- updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
- updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
- root, nodes, _ := trie.Commit(false)
- triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- if !memonly {
- triedb.Commit(root, false)
- }
-
- trie, _ = New(TrieID(root), triedb)
- _, err := trie.Get([]byte("120000"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- trie, _ = New(TrieID(root), triedb)
- _, err = trie.Get([]byte("120099"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- trie, _ = New(TrieID(root), triedb)
- _, err = trie.Get([]byte("123456"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- trie, _ = New(TrieID(root), triedb)
- err = trie.Update([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- trie, _ = New(TrieID(root), triedb)
- err = trie.Delete([]byte("123456"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
-
- var (
- path []byte
- hash = common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9")
- )
- for p, n := range nodes.Nodes {
- if n.Hash == hash {
- path = common.CopyBytes([]byte(p))
- break
- }
- }
- trie, _ = New(TrieID(root), triedb)
- if memonly {
- trie.reader.banned = map[string]struct{}{string(path): {}}
- } else {
- rawdb.DeleteTrieNode(diskdb, common.Hash{}, path, hash, scheme)
- }
-
- _, err = trie.Get([]byte("120000"))
- if _, ok := err.(*MissingNodeError); !ok {
- t.Errorf("Wrong error: %v", err)
- }
- _, err = trie.Get([]byte("120099"))
- if _, ok := err.(*MissingNodeError); !ok {
- t.Errorf("Wrong error: %v", err)
- }
- _, err = trie.Get([]byte("123456"))
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- err = trie.Update([]byte("120099"), []byte("zxcv"))
- if _, ok := err.(*MissingNodeError); !ok {
- t.Errorf("Wrong error: %v", err)
- }
- err = trie.Delete([]byte("123456"))
- if _, ok := err.(*MissingNodeError); !ok {
- t.Errorf("Wrong error: %v", err)
- }
-}
-
-func TestInsert(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
-
- updateString(trie, "doe", "reindeer")
- updateString(trie, "dog", "puppy")
- updateString(trie, "dogglesworth", "cat")
-
- exp := common.HexToHash("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3")
- root := trie.Hash()
- if root != exp {
- t.Errorf("case 1: exp %x got %x", exp, root)
- }
-
- trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
-
- exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
- root, _, _ = trie.Commit(false)
- if root != exp {
- t.Errorf("case 2: exp %x got %x", exp, root)
- }
-}
-
-func TestGet(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(db)
- updateString(trie, "doe", "reindeer")
- updateString(trie, "dog", "puppy")
- updateString(trie, "dogglesworth", "cat")
-
- for i := 0; i < 2; i++ {
- res := getString(trie, "dog")
- if !bytes.Equal(res, []byte("puppy")) {
- t.Errorf("expected puppy got %x", res)
- }
- unknown := getString(trie, "unknown")
- if unknown != nil {
- t.Errorf("expected nil got %x", unknown)
- }
- if i == 1 {
- return
- }
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- trie, _ = New(TrieID(root), db)
- }
-}
-
-func TestDelete(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"ether", ""},
- {"dog", "puppy"},
- {"shaman", ""},
- }
- for _, val := range vals {
- if val.v != "" {
- updateString(trie, val.k, val.v)
- } else {
- deleteString(trie, val.k)
- }
- }
-
- hash := trie.Hash()
- exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84")
- if hash != exp {
- t.Errorf("expected %x got %x", exp, hash)
- }
-}
-
-func TestEmptyValues(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
-
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"ether", ""},
- {"dog", "puppy"},
- {"shaman", ""},
- }
- for _, val := range vals {
- updateString(trie, val.k, val.v)
- }
-
- hash := trie.Hash()
- exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84")
- if hash != exp {
- t.Errorf("expected %x got %x", exp, hash)
- }
-}
-
-func TestReplication(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(db)
- vals := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- {"shaman", "horse"},
- {"doge", "coin"},
- {"dog", "puppy"},
- {"somethingveryoddindeedthis is", "myothernodedata"},
- }
- for _, val := range vals {
- updateString(trie, val.k, val.v)
- }
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
-
- // create a new trie on top of the database and check that lookups work.
- trie2, err := New(TrieID(root), db)
- if err != nil {
- t.Fatalf("can't recreate trie at %x: %v", root, err)
- }
- for _, kv := range vals {
- if string(getString(trie2, kv.k)) != kv.v {
- t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v)
- }
- }
- hash, nodes, _ := trie2.Commit(false)
- if hash != root {
- t.Errorf("root failure. expected %x got %x", root, hash)
- }
-
- // recreate the trie after commit
- if nodes != nil {
- db.Update(hash, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- }
- trie2, err = New(TrieID(hash), db)
- if err != nil {
- t.Fatalf("can't recreate trie at %x: %v", hash, err)
- }
- // perform some insertions on the new trie.
- vals2 := []struct{ k, v string }{
- {"do", "verb"},
- {"ether", "wookiedoo"},
- {"horse", "stallion"},
- // {"shaman", "horse"},
- // {"doge", "coin"},
- // {"ether", ""},
- // {"dog", "puppy"},
- // {"somethingveryoddindeedthis is", "myothernodedata"},
- // {"shaman", ""},
- }
- for _, val := range vals2 {
- updateString(trie2, val.k, val.v)
- }
- if trie2.Hash() != hash {
- t.Errorf("root failure. expected %x got %x", hash, hash)
- }
-}
-
-func TestLargeValue(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- trie.MustUpdate([]byte("key1"), []byte{99, 99, 99, 99})
- trie.MustUpdate([]byte("key2"), bytes.Repeat([]byte{1}, 32))
- trie.Hash()
-}
-
-// TestRandomCases tests som cases that were found via random fuzzing
-func TestRandomCases(t *testing.T) {
- var rt = []randTestStep{
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 0
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 1
- {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000002")}, // step 2
- {op: 2, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("")}, // step 3
- {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 4
- {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 5
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 6
- {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 7
- {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000008")}, // step 8
- {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000009")}, // step 9
- {op: 2, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 10
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 11
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 12
- {op: 0, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("000000000000000d")}, // step 13
- {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 14
- {op: 1, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("")}, // step 15
- {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 16
- {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000011")}, // step 17
- {op: 5, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 18
- {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 19
- {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000014")}, // step 20
- {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000015")}, // step 21
- {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000016")}, // step 22
- {op: 5, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 23
- {op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24
- {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25
- }
- if err := runRandTest(rt); err != nil {
- t.Fatal(err)
- }
-}
-
-// randTest performs random trie operations.
-// Instances of this test are created by Generate.
-type randTest []randTestStep
-
-// compile-time interface check
-var _ quick.Generator = (randTest)(nil)
-
-type randTestStep struct {
- op int
- key []byte // for opUpdate, opDelete, opGet
- value []byte // for opUpdate
- err error // for debugging
-}
-
-const (
- opUpdate = iota
- opDelete
- opGet
- opHash
- opCommit
- opItercheckhash
- opNodeDiff
- opProve
- opMax // boundary value, not an actual op
-)
-
-func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
- var finishedFn = func() bool {
- size--
- return size == 0
- }
- return reflect.ValueOf(generateSteps(finishedFn, r))
-}
-
-func generateSteps(finished func() bool, r io.Reader) randTest {
- var allKeys [][]byte
- var one = []byte{0}
- genKey := func() []byte {
- r.Read(one)
- if len(allKeys) < 2 || one[0]%100 > 90 {
- // new key
- size := one[0] % 50
- key := make([]byte, size)
- r.Read(key)
- allKeys = append(allKeys, key)
- return key
- }
- // use existing key
- idx := int(one[0]) % len(allKeys)
- return allKeys[idx]
- }
- var steps randTest
- for !finished() {
- r.Read(one)
- step := randTestStep{op: int(one[0]) % opMax}
- switch step.op {
- case opUpdate:
- step.key = genKey()
- step.value = make([]byte, 8)
- binary.BigEndian.PutUint64(step.value, uint64(len(steps)))
- case opGet, opDelete, opProve:
- step.key = genKey()
- }
- steps = append(steps, step)
- }
- return steps
-}
-
-func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error {
- deletes, inserts, updates := diffTries(old, new)
-
- // Check insertion set
- for path := range inserts {
- n, ok := set.Nodes[path]
- if !ok || n.IsDeleted() {
- return errors.New("expect new node")
- }
- //if len(n.Prev) > 0 {
- // return errors.New("unexpected origin value")
- //}
- }
- // Check deletion set
- for path := range deletes {
- n, ok := set.Nodes[path]
- if !ok || !n.IsDeleted() {
- return errors.New("expect deleted node")
- }
- //if len(n.Prev) == 0 {
- // return errors.New("expect origin value")
- //}
- //if !bytes.Equal(n.Prev, blob) {
- // return errors.New("invalid origin value")
- //}
- }
- // Check update set
- for path := range updates {
- n, ok := set.Nodes[path]
- if !ok || n.IsDeleted() {
- return errors.New("expect updated node")
- }
- //if len(n.Prev) == 0 {
- // return errors.New("expect origin value")
- //}
- //if !bytes.Equal(n.Prev, blob) {
- // return errors.New("invalid origin value")
- //}
- }
- return nil
-}
-
-// runRandTestBool coerces error to boolean, for use in quick.Check
-func runRandTestBool(rt randTest) bool {
- return runRandTest(rt) == nil
-}
-
-func runRandTest(rt randTest) error {
- var scheme = rawdb.HashScheme
- if rand.Intn(2) == 0 {
- scheme = rawdb.PathScheme
- }
- var (
- origin = types.EmptyRootHash
- triedb = newTestDatabase(rawdb.NewMemoryDatabase(), scheme)
- tr = NewEmpty(triedb)
- values = make(map[string]string) // tracks content of the trie
- origTrie = NewEmpty(triedb)
- )
- for i, step := range rt {
- // fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n",
- // step.op, step.key, step.value, i)
-
- switch step.op {
- case opUpdate:
- tr.MustUpdate(step.key, step.value)
- values[string(step.key)] = string(step.value)
- case opDelete:
- tr.MustDelete(step.key)
- delete(values, string(step.key))
- case opGet:
- v := tr.MustGet(step.key)
- want := values[string(step.key)]
- if string(v) != want {
- rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
- }
- case opProve:
- hash := tr.Hash()
- if hash == types.EmptyRootHash {
- continue
- }
- proofDb := rawdb.NewMemoryDatabase()
- err := tr.Prove(step.key, proofDb)
- if err != nil {
- rt[i].err = fmt.Errorf("failed for proving key %#x, %v", step.key, err)
- }
- _, err = VerifyProof(hash, step.key, proofDb)
- if err != nil {
- rt[i].err = fmt.Errorf("failed for verifying key %#x, %v", step.key, err)
- }
- case opHash:
- tr.Hash()
- case opCommit:
- root, nodes, _ := tr.Commit(true)
- if nodes != nil {
- triedb.Update(root, origin, 0, trienode.NewWithNodeSet(nodes), nil)
- }
- newtr, err := New(TrieID(root), triedb)
- if err != nil {
- rt[i].err = err
- return err
- }
- if nodes != nil {
- if err := verifyAccessList(origTrie, newtr, nodes); err != nil {
- rt[i].err = err
- return err
- }
- }
- tr = newtr
- origTrie = tr.Copy()
- origin = root
- case opItercheckhash:
- checktr := NewEmpty(triedb)
- it := NewIterator(tr.MustNodeIterator(nil))
- for it.Next() {
- checktr.MustUpdate(it.Key, it.Value)
- }
- if tr.Hash() != checktr.Hash() {
- rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash")
- }
- case opNodeDiff:
- var (
- origIter = origTrie.MustNodeIterator(nil)
- curIter = tr.MustNodeIterator(nil)
- origSeen = make(map[string]struct{})
- curSeen = make(map[string]struct{})
- )
- for origIter.Next(true) {
- if origIter.Leaf() {
- continue
- }
- origSeen[string(origIter.Path())] = struct{}{}
- }
- for curIter.Next(true) {
- if curIter.Leaf() {
- continue
- }
- curSeen[string(curIter.Path())] = struct{}{}
- }
- var (
- insertExp = make(map[string]struct{})
- deleteExp = make(map[string]struct{})
- )
- for path := range curSeen {
- _, present := origSeen[path]
- if !present {
- insertExp[path] = struct{}{}
- }
- }
- for path := range origSeen {
- _, present := curSeen[path]
- if !present {
- deleteExp[path] = struct{}{}
- }
- }
- if len(insertExp) != len(tr.tracer.inserts) {
- rt[i].err = fmt.Errorf("insert set mismatch")
- }
- if len(deleteExp) != len(tr.tracer.deletes) {
- rt[i].err = fmt.Errorf("delete set mismatch")
- }
- for insert := range tr.tracer.inserts {
- if _, present := insertExp[insert]; !present {
- rt[i].err = fmt.Errorf("missing inserted node")
- }
- }
- for del := range tr.tracer.deletes {
- if _, present := deleteExp[del]; !present {
- rt[i].err = fmt.Errorf("missing deleted node")
- }
- }
- }
- // Abort the test on error.
- if rt[i].err != nil {
- return rt[i].err
- }
- }
- return nil
-}
-
-func TestRandom(t *testing.T) {
- if err := quick.Check(runRandTestBool, nil); err != nil {
- if cerr, ok := err.(*quick.CheckError); ok {
- t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In))
- }
- t.Fatal(err)
- }
-}
-
-func BenchmarkGet(b *testing.B) { benchGet(b) }
-func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) }
-func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) }
-
-const benchElemCount = 20000
-
-func benchGet(b *testing.B) {
- triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(triedb)
- k := make([]byte, 32)
- for i := 0; i < benchElemCount; i++ {
- binary.LittleEndian.PutUint64(k, uint64(i))
- v := make([]byte, 32)
- binary.LittleEndian.PutUint64(v, uint64(i))
- trie.MustUpdate(k, v)
- }
- binary.LittleEndian.PutUint64(k, benchElemCount/2)
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- trie.MustGet(k)
- }
- b.StopTimer()
-}
-
-func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- k := make([]byte, 32)
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- v := make([]byte, 32)
- e.PutUint64(k, uint64(i))
- e.PutUint64(v, uint64(i))
- trie.MustUpdate(k, v)
- }
- return trie
-}
-
-// Benchmarks the trie hashing. Since the trie caches the result of any operation,
-// we cannot use b.N as the number of hashing rounds, since all rounds apart from
-// the first one will be NOOP. As such, we'll use b.N as the number of account to
-// insert into the trie before measuring the hashing.
-// BenchmarkHash-6 288680 4561 ns/op 682 B/op 9 allocs/op
-// BenchmarkHash-6 275095 4800 ns/op 685 B/op 9 allocs/op
-// pure hasher:
-// BenchmarkHash-6 319362 4230 ns/op 675 B/op 9 allocs/op
-// BenchmarkHash-6 257460 4674 ns/op 689 B/op 9 allocs/op
-// With hashing in-between and pure hasher:
-// BenchmarkHash-6 225417 7150 ns/op 982 B/op 12 allocs/op
-// BenchmarkHash-6 220378 6197 ns/op 983 B/op 12 allocs/op
-// same with old hasher
-// BenchmarkHash-6 229758 6437 ns/op 981 B/op 12 allocs/op
-// BenchmarkHash-6 212610 7137 ns/op 986 B/op 12 allocs/op
-func BenchmarkHash(b *testing.B) {
- // Create a realistic account trie to hash. We're first adding and hashing N
- // entries, then adding N more.
- addresses, accounts := makeAccounts(2 * b.N)
- // Insert the accounts into the trie and hash it
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- i := 0
- for ; i < len(addresses)/2; i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- trie.Hash()
- for ; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- b.ResetTimer()
- b.ReportAllocs()
- //trie.hashRoot(nil, nil)
- trie.Hash()
-}
-
-// Benchmarks the trie Commit following a Hash. Since the trie caches the result of any operation,
-// we cannot use b.N as the number of hashing rounds, since all rounds apart from
-// the first one will be NOOP. As such, we'll use b.N as the number of account to
-// insert into the trie before measuring the hashing.
-func BenchmarkCommitAfterHash(b *testing.B) {
- b.Run("no-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, false)
- })
- b.Run("with-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, true)
- })
-}
-
-func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) {
- // Make the random benchmark deterministic
- addresses, accounts := makeAccounts(b.N)
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for i := 0; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- // Insert the accounts into the trie and hash it
- trie.Hash()
- b.ResetTimer()
- b.ReportAllocs()
- trie.Commit(collectLeaf)
-}
-
-func TestTinyTrie(t *testing.T) {
- // Create a realistic account trie to hash
- _, accounts := makeAccounts(5)
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3])
- if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root {
- t.Errorf("1: got %x, exp %x", root, exp)
- }
- trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4])
- if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root {
- t.Errorf("2: got %x, exp %x", root, exp)
- }
- trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4])
- if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
- t.Errorf("3: got %x, exp %x", root, exp)
- }
- checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- it := NewIterator(trie.MustNodeIterator(nil))
- for it.Next() {
- checktr.MustUpdate(it.Key, it.Value)
- }
- if troot, itroot := trie.Hash(), checktr.Hash(); troot != itroot {
- t.Fatalf("hash mismatch in opItercheckhash, trie: %x, check: %x", troot, itroot)
- }
-}
-
-func TestCommitAfterHash(t *testing.T) {
- // Create a realistic account trie to hash
- addresses, accounts := makeAccounts(1000)
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for i := 0; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- // Insert the accounts into the trie and hash it
- trie.Hash()
- trie.Commit(false)
- root := trie.Hash()
- exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6")
- if exp != root {
- t.Errorf("got %x, exp %x", root, exp)
- }
- root, _, _ = trie.Commit(false)
- if exp != root {
- t.Errorf("got %x, exp %x", root, exp)
- }
-}
-
-func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
- // Make the random benchmark deterministic
- random := rand.New(rand.NewSource(0))
- // Create a realistic account trie to hash
- addresses = make([][20]byte, size)
- for i := 0; i < len(addresses); i++ {
- data := make([]byte, 20)
- random.Read(data)
- copy(addresses[i][:], data)
- }
- accounts = make([][]byte, len(addresses))
- for i := 0; i < len(accounts); i++ {
- var (
- nonce = uint64(random.Int63())
- root = types.EmptyRootHash
- code = crypto.Keccak256(nil)
- )
- // The big.Rand function is not deterministic with regards to 64 vs 32 bit systems,
- // and will consume different amount of data from the rand source.
- //balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
- // Therefore, we instead just read via byte buffer
- numBytes := random.Uint32() % 33 // [0, 32] bytes
- balanceBytes := make([]byte, numBytes)
- random.Read(balanceBytes)
- balance := new(big.Int).SetBytes(balanceBytes)
- data, _ := rlp.EncodeToBytes(&types.StateAccount{Nonce: nonce, Balance: balance, Root: root, CodeHash: code})
- accounts[i] = data
- }
- return addresses, accounts
-}
-
-// spongeDb is a dummy db backend which accumulates writes in a sponge
-type spongeDb struct {
- sponge hash.Hash
- id string
- journal []string
-}
-
-func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
-func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
-func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
-func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} }
-func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} }
-func (s *spongeDb) NewSnapshot() (ethdb.Snapshot, error) { panic("implement me") }
-func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") }
-func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") }
-func (s *spongeDb) Close() error { return nil }
-func (s *spongeDb) Put(key []byte, value []byte) error {
- var (
- keybrief = key
- valbrief = value
- )
- if len(keybrief) > 8 {
- keybrief = keybrief[:8]
- }
- if len(valbrief) > 8 {
- valbrief = valbrief[:8]
- }
- s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, keybrief, len(value), valbrief))
- s.sponge.Write(key)
- s.sponge.Write(value)
- return nil
-}
-func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") }
-
-// spongeBatch is a dummy batch which immediately writes to the underlying spongedb
-type spongeBatch struct {
- db *spongeDb
-}
-
-func (b *spongeBatch) Put(key, value []byte) error {
- b.db.Put(key, value)
- return nil
-}
-func (b *spongeBatch) Delete(key []byte) error { panic("implement me") }
-func (b *spongeBatch) ValueSize() int { return 100 }
-func (b *spongeBatch) Write() error { return nil }
-func (b *spongeBatch) Reset() {}
-func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil }
-
-// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie
-// in the expected order.
-// The test data was based on the 'master' code, and is basically random. It can be used
-// to check whether changes to the trie modifies the write order or data in any way.
-func TestCommitSequence(t *testing.T) {
- for i, tc := range []struct {
- count int
- expWriteSeqHash []byte
- }{
- {20, common.FromHex("873c78df73d60e59d4a2bcf3716e8bfe14554549fea2fc147cb54129382a8066")},
- {200, common.FromHex("ba03d891bb15408c940eea5ee3d54d419595102648d02774a0268d892add9c8e")},
- {2000, common.FromHex("f7a184f20df01c94f09537401d11e68d97ad0c00115233107f51b9c287ce60c7")},
- } {
- addresses, accounts := makeAccounts(tc.count)
- // This spongeDb is used to check the sequence of disk-db-writes
- s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- db := NewDatabase(rawdb.NewDatabase(s), nil)
- trie := NewEmpty(db)
- // Fill the trie with elements
- for i := 0; i < tc.count; i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- // Flush trie -> database
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- // Flush memdb -> disk (sponge)
- db.Commit(root, false)
- if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
- t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp)
- }
- }
-}
-
-// TestCommitSequenceRandomBlobs is identical to TestCommitSequence
-// but uses random blobs instead of 'accounts'
-func TestCommitSequenceRandomBlobs(t *testing.T) {
- for i, tc := range []struct {
- count int
- expWriteSeqHash []byte
- }{
- {20, common.FromHex("8e4a01548551d139fa9e833ebc4e66fc1ba40a4b9b7259d80db32cff7b64ebbc")},
- {200, common.FromHex("6869b4e7b95f3097a19ddb30ff735f922b915314047e041614df06958fc50554")},
- {2000, common.FromHex("444200e6f4e2df49f77752f629a96ccf7445d4698c164f962bbd85a0526ef424")},
- } {
- prng := rand.New(rand.NewSource(int64(i)))
- // This spongeDb is used to check the sequence of disk-db-writes
- s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
- db := NewDatabase(rawdb.NewDatabase(s), nil)
- trie := NewEmpty(db)
- // Fill the trie with elements
- for i := 0; i < tc.count; i++ {
- key := make([]byte, 32)
- var val []byte
- // 50% short elements, 50% large elements
- if prng.Intn(2) == 0 {
- val = make([]byte, 1+prng.Intn(32))
- } else {
- val = make([]byte, 1+prng.Intn(4096))
- }
- prng.Read(key)
- prng.Read(val)
- trie.MustUpdate(key, val)
- }
- // Flush trie -> database
- root, nodes, _ := trie.Commit(false)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- // Flush memdb -> disk (sponge)
- db.Commit(root, false)
- if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
- t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp)
- }
- }
-}
-
-func TestCommitSequenceStackTrie(t *testing.T) {
- for count := 1; count < 200; count++ {
- prng := rand.New(rand.NewSource(int64(count)))
- // This spongeDb is used to check the sequence of disk-db-writes
- s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"}
- db := NewDatabase(rawdb.NewDatabase(s), nil)
- trie := NewEmpty(db)
- // Another sponge is used for the stacktrie commits
- stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
-
- options := NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme())
- })
- stTrie := NewStackTrie(options)
- // Fill the trie with elements
- for i := 0; i < count; i++ {
- // For the stack trie, we need to do inserts in proper order
- key := make([]byte, 32)
- binary.BigEndian.PutUint64(key, uint64(i))
- var val []byte
- // 50% short elements, 50% large elements
- if prng.Intn(2) == 0 {
- val = make([]byte, 1+prng.Intn(32))
- } else {
- val = make([]byte, 1+prng.Intn(1024))
- }
- prng.Read(val)
- trie.Update(key, val)
- stTrie.Update(key, val)
- }
- // Flush trie -> database
- root, nodes, _ := trie.Commit(false)
- // Flush memdb -> disk (sponge)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- db.Commit(root, false)
- // And flush stacktrie -> disk
- stRoot := stTrie.Commit()
- if stRoot != root {
- t.Fatalf("root wrong, got %x exp %x", stRoot, root)
- }
- if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) {
- // Show the journal
- t.Logf("Expected:")
- for i, v := range s.journal {
- t.Logf("op %d: %v", i, v)
- }
- t.Logf("Stacktrie:")
- for i, v := range stackTrieSponge.journal {
- t.Logf("op %d: %v", i, v)
- }
- t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp)
- }
- }
-}
-
-// TestCommitSequenceSmallRoot tests that a trie which is essentially only a
-// small (<32 byte) shortnode with an included value is properly committed to a
-// database.
-// This case might not matter, since in practice, all keys are 32 bytes, which means
-// that even a small trie which contains a leaf will have an extension making it
-// not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do.
-func TestCommitSequenceSmallRoot(t *testing.T) {
- s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"}
- db := NewDatabase(rawdb.NewDatabase(s), nil)
- trie := NewEmpty(db)
- // Another sponge is used for the stacktrie commits
- stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
-
- options := NewStackTrieOptions()
- options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
- rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme())
- })
- stTrie := NewStackTrie(options)
- // Add a single small-element to the trie(s)
- key := make([]byte, 5)
- key[0] = 1
- trie.Update(key, []byte{0x1})
- stTrie.Update(key, []byte{0x1})
- // Flush trie -> database
- root, nodes, _ := trie.Commit(false)
- // Flush memdb -> disk (sponge)
- db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- db.Commit(root, false)
- // And flush stacktrie -> disk
- stRoot := stTrie.Commit()
- if stRoot != root {
- t.Fatalf("root wrong, got %x exp %x", stRoot, root)
- }
-
- t.Logf("root: %x\n", stRoot)
- if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) {
- t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp)
- }
-}
-
-// BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie.
-// This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically,
-// storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple
-// of thousand entries)
-func BenchmarkHashFixedSize(b *testing.B) {
- b.Run("10", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(20)
- for i := 0; i < b.N; i++ {
- benchmarkHashFixedSize(b, acc, add)
- }
- })
- b.Run("100", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100)
- for i := 0; i < b.N; i++ {
- benchmarkHashFixedSize(b, acc, add)
- }
- })
-
- b.Run("1K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(1000)
- for i := 0; i < b.N; i++ {
- benchmarkHashFixedSize(b, acc, add)
- }
- })
- b.Run("10K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(10000)
- for i := 0; i < b.N; i++ {
- benchmarkHashFixedSize(b, acc, add)
- }
- })
- b.Run("100K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100000)
- for i := 0; i < b.N; i++ {
- benchmarkHashFixedSize(b, acc, add)
- }
- })
-}
-
-func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
- b.ReportAllocs()
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for i := 0; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- // Insert the accounts into the trie and hash it
- b.StartTimer()
- trie.Hash()
- b.StopTimer()
-}
-
-func BenchmarkCommitAfterHashFixedSize(b *testing.B) {
- b.Run("10", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(20)
- for i := 0; i < b.N; i++ {
- benchmarkCommitAfterHashFixedSize(b, acc, add)
- }
- })
- b.Run("100", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100)
- for i := 0; i < b.N; i++ {
- benchmarkCommitAfterHashFixedSize(b, acc, add)
- }
- })
-
- b.Run("1K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(1000)
- for i := 0; i < b.N; i++ {
- benchmarkCommitAfterHashFixedSize(b, acc, add)
- }
- })
- b.Run("10K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(10000)
- for i := 0; i < b.N; i++ {
- benchmarkCommitAfterHashFixedSize(b, acc, add)
- }
- })
- b.Run("100K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100000)
- for i := 0; i < b.N; i++ {
- benchmarkCommitAfterHashFixedSize(b, acc, add)
- }
- })
-}
-
-func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
- b.ReportAllocs()
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil))
- for i := 0; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- // Insert the accounts into the trie and hash it
- trie.Hash()
- b.StartTimer()
- trie.Commit(false)
- b.StopTimer()
-}
-
-func BenchmarkDerefRootFixedSize(b *testing.B) {
- b.Run("10", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(20)
- for i := 0; i < b.N; i++ {
- benchmarkDerefRootFixedSize(b, acc, add)
- }
- })
- b.Run("100", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100)
- for i := 0; i < b.N; i++ {
- benchmarkDerefRootFixedSize(b, acc, add)
- }
- })
-
- b.Run("1K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(1000)
- for i := 0; i < b.N; i++ {
- benchmarkDerefRootFixedSize(b, acc, add)
- }
- })
- b.Run("10K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(10000)
- for i := 0; i < b.N; i++ {
- benchmarkDerefRootFixedSize(b, acc, add)
- }
- })
- b.Run("100K", func(b *testing.B) {
- b.StopTimer()
- acc, add := makeAccounts(100000)
- for i := 0; i < b.N; i++ {
- benchmarkDerefRootFixedSize(b, acc, add)
- }
- })
-}
-
-func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
- b.ReportAllocs()
- triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil)
- trie := NewEmpty(triedb)
- for i := 0; i < len(addresses); i++ {
- trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i])
- }
- h := trie.Hash()
- root, nodes, _ := trie.Commit(false)
- triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil)
- b.StartTimer()
- triedb.Dereference(h)
- b.StopTimer()
-}
-
-func getString(trie *Trie, k string) []byte {
- return trie.MustGet([]byte(k))
-}
-
-func updateString(trie *Trie, k, v string) {
- trie.MustUpdate([]byte(k), []byte(v))
-}
-
-func deleteString(trie *Trie, k string) {
- trie.MustDelete([]byte(k))
-}
-
-func TestDecodeNode(t *testing.T) {
- t.Parallel()
-
- var (
- hash = make([]byte, 20)
- elems = make([]byte, 20)
- )
- for i := 0; i < 5000000; i++ {
- prng.Read(hash)
- prng.Read(elems)
- decodeNode(hash, elems)
- }
-}
-
-func FuzzTrie(f *testing.F) {
- f.Fuzz(func(t *testing.T, data []byte) {
- var steps = 500
- var input = bytes.NewReader(data)
- var finishedFn = func() bool {
- steps--
- return steps < 0 || input.Len() == 0
- }
- if err := runRandTest(generateSteps(finishedFn, input)); err != nil {
- t.Fatal(err)
- }
- })
-}
diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go
deleted file mode 100644
index e45ccdba32..0000000000
--- a/trie/triedb/hashdb/database.go
+++ /dev/null
@@ -1,651 +0,0 @@
-// Copyright 2018 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package hashdb
-
-import (
- "errors"
- "fmt"
- "reflect"
- "sync"
- "time"
-
- "github.com/VictoriaMetrics/fastcache"
- "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/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-var (
- memcacheCleanHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/hit", nil)
- memcacheCleanMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/miss", nil)
- memcacheCleanReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/read", nil)
- memcacheCleanWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/write", nil)
-
- memcacheDirtyHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/hit", nil)
- memcacheDirtyMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/miss", nil)
- memcacheDirtyReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/read", nil)
- memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/write", nil)
-
- memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/flush/time", nil)
- memcacheFlushNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/nodes", nil)
- memcacheFlushBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/bytes", nil)
-
- memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/gc/time", nil)
- memcacheGCNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/nodes", nil)
- memcacheGCBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/bytes", nil)
-
- memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/commit/time", nil)
- memcacheCommitNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/nodes", nil)
- memcacheCommitBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/bytes", nil)
-)
-
-// ChildResolver defines the required method to decode the provided
-// trie node and iterate the children on top.
-type ChildResolver interface {
- ForEach(node []byte, onChild func(common.Hash))
-}
-
-// Config contains the settings for database.
-type Config struct {
- CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
-}
-
-// Defaults is the default setting for database if it's not specified.
-// Notably, clean cache is disabled explicitly,
-var Defaults = &Config{
- // Explicitly set clean cache size to 0 to avoid creating fastcache,
- // otherwise database must be closed when it's no longer needed to
- // prevent memory leak.
- CleanCacheSize: 0,
-}
-
-// Database is an intermediate write layer between the trie data structures and
-// the disk database. The aim is to accumulate trie writes in-memory and only
-// periodically flush a couple tries to disk, garbage collecting the remainder.
-type Database struct {
- diskdb ethdb.Database // Persistent storage for matured trie nodes
- resolver ChildResolver // The handler to resolve children of nodes
-
- cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs
- dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes
- oldest common.Hash // Oldest tracked node, flush-list head
- newest common.Hash // Newest tracked node, flush-list tail
-
- gctime time.Duration // Time spent on garbage collection since last commit
- gcnodes uint64 // Nodes garbage collected since last commit
- gcsize common.StorageSize // Data storage garbage collected since last commit
-
- flushtime time.Duration // Time spent on data flushing since last commit
- flushnodes uint64 // Nodes flushed since last commit
- flushsize common.StorageSize // Data storage flushed since last commit
-
- dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata)
- childrenSize common.StorageSize // Storage size of the external children tracking
-
- lock sync.RWMutex
-}
-
-// cachedNode is all the information we know about a single cached trie node
-// in the memory database write layer.
-type cachedNode struct {
- node []byte // Encoded node blob, immutable
- parents uint32 // Number of live nodes referencing this one
- external map[common.Hash]struct{} // The set of external children
- flushPrev common.Hash // Previous node in the flush-list
- flushNext common.Hash // Next node in the flush-list
-}
-
-// cachedNodeSize is the raw size of a cachedNode data structure without any
-// node data included. It's an approximate size, but should be a lot better
-// than not counting them.
-var cachedNodeSize = int(reflect.TypeOf(cachedNode{}).Size())
-
-// forChildren invokes the callback for all the tracked children of this node,
-// both the implicit ones from inside the node as well as the explicit ones
-// from outside the node.
-func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash common.Hash)) {
- for child := range n.external {
- onChild(child)
- }
- resolver.ForEach(n.node, onChild)
-}
-
-// New initializes the hash-based node database.
-func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database {
- if config == nil {
- config = Defaults
- }
- var cleans *fastcache.Cache
- if config.CleanCacheSize > 0 {
- cleans = fastcache.New(config.CleanCacheSize)
- }
- return &Database{
- diskdb: diskdb,
- resolver: resolver,
- cleans: cleans,
- dirties: make(map[common.Hash]*cachedNode),
- }
-}
-
-// insert inserts a trie node into the memory database. All nodes inserted by
-// this function will be reference tracked. This function assumes the lock is
-// already held.
-func (db *Database) insert(hash common.Hash, node []byte) {
- // If the node's already cached, skip
- if _, ok := db.dirties[hash]; ok {
- return
- }
- memcacheDirtyWriteMeter.Mark(int64(len(node)))
-
- // Create the cached entry for this node
- entry := &cachedNode{
- node: node,
- flushPrev: db.newest,
- }
- entry.forChildren(db.resolver, func(child common.Hash) {
- if c := db.dirties[child]; c != nil {
- c.parents++
- }
- })
- db.dirties[hash] = entry
-
- // Update the flush-list endpoints
- if db.oldest == (common.Hash{}) {
- db.oldest, db.newest = hash, hash
- } else {
- db.dirties[db.newest].flushNext, db.newest = hash, hash
- }
- db.dirtiesSize += common.StorageSize(common.HashLength + len(node))
-}
-
-// node retrieves an encoded cached trie node from memory. If it cannot be found
-// cached, the method queries the persistent database for the content.
-func (db *Database) node(hash common.Hash) ([]byte, error) {
- // It doesn't make sense to retrieve the metaroot
- if hash == (common.Hash{}) {
- return nil, errors.New("not found")
- }
- // Retrieve the node from the clean cache if available
- if db.cleans != nil {
- if enc := db.cleans.Get(nil, hash[:]); enc != nil {
- memcacheCleanHitMeter.Mark(1)
- memcacheCleanReadMeter.Mark(int64(len(enc)))
- return enc, nil
- }
- }
- // Retrieve the node from the dirty cache if available.
- db.lock.RLock()
- dirty := db.dirties[hash]
- db.lock.RUnlock()
-
- // Return the cached node if it's found in the dirty set.
- // The dirty.node field is immutable and safe to read it
- // even without lock guard.
- if dirty != nil {
- memcacheDirtyHitMeter.Mark(1)
- memcacheDirtyReadMeter.Mark(int64(len(dirty.node)))
- return dirty.node, nil
- }
- memcacheDirtyMissMeter.Mark(1)
-
- // Content unavailable in memory, attempt to retrieve from disk
- enc := rawdb.ReadLegacyTrieNode(db.diskdb, hash)
- if len(enc) != 0 {
- if db.cleans != nil {
- db.cleans.Set(hash[:], enc)
- memcacheCleanMissMeter.Mark(1)
- memcacheCleanWriteMeter.Mark(int64(len(enc)))
- }
- return enc, nil
- }
- return nil, errors.New("not found")
-}
-
-// Reference adds a new reference from a parent node to a child node.
-// This function is used to add reference between internal trie node
-// and external node(e.g. storage trie root), all internal trie nodes
-// are referenced together by database itself.
-func (db *Database) Reference(child common.Hash, parent common.Hash) {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- db.reference(child, parent)
-}
-
-// reference is the private locked version of Reference.
-func (db *Database) reference(child common.Hash, parent common.Hash) {
- // If the node does not exist, it's a node pulled from disk, skip
- node, ok := db.dirties[child]
- if !ok {
- return
- }
- // The reference is for state root, increase the reference counter.
- if parent == (common.Hash{}) {
- node.parents += 1
- return
- }
- // The reference is for external storage trie, don't duplicate if
- // the reference is already existent.
- if db.dirties[parent].external == nil {
- db.dirties[parent].external = make(map[common.Hash]struct{})
- }
- if _, ok := db.dirties[parent].external[child]; ok {
- return
- }
- node.parents++
- db.dirties[parent].external[child] = struct{}{}
- db.childrenSize += common.HashLength
-}
-
-// Dereference removes an existing reference from a root node.
-func (db *Database) Dereference(root common.Hash) {
- // Sanity check to ensure that the meta-root is not removed
- if root == (common.Hash{}) {
- log.Error("Attempted to dereference the trie cache meta root")
- return
- }
- db.lock.Lock()
- defer db.lock.Unlock()
-
- nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
- db.dereference(root)
-
- db.gcnodes += uint64(nodes - len(db.dirties))
- db.gcsize += storage - db.dirtiesSize
- db.gctime += time.Since(start)
-
- memcacheGCTimeTimer.Update(time.Since(start))
- memcacheGCBytesMeter.Mark(int64(storage - db.dirtiesSize))
- memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties)))
-
- log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start),
- "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
-}
-
-// dereference is the private locked version of Dereference.
-func (db *Database) dereference(hash common.Hash) {
- // If the node does not exist, it's a previously committed node.
- node, ok := db.dirties[hash]
- if !ok {
- return
- }
- // If there are no more references to the node, delete it and cascade
- if node.parents > 0 {
- // This is a special cornercase where a node loaded from disk (i.e. not in the
- // memcache any more) gets reinjected as a new node (short node split into full,
- // then reverted into short), causing a cached node to have no parents. That is
- // no problem in itself, but don't make maxint parents out of it.
- node.parents--
- }
- if node.parents == 0 {
- // Remove the node from the flush-list
- switch hash {
- case db.oldest:
- db.oldest = node.flushNext
- if node.flushNext != (common.Hash{}) {
- db.dirties[node.flushNext].flushPrev = common.Hash{}
- }
- case db.newest:
- db.newest = node.flushPrev
- if node.flushPrev != (common.Hash{}) {
- db.dirties[node.flushPrev].flushNext = common.Hash{}
- }
- default:
- db.dirties[node.flushPrev].flushNext = node.flushNext
- db.dirties[node.flushNext].flushPrev = node.flushPrev
- }
- // Dereference all children and delete the node
- node.forChildren(db.resolver, func(child common.Hash) {
- db.dereference(child)
- })
- delete(db.dirties, hash)
- db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node))
- if node.external != nil {
- db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength)
- }
- }
-}
-
-// Cap iteratively flushes old but still referenced trie nodes until the total
-// memory usage goes below the given threshold.
-func (db *Database) Cap(limit common.StorageSize) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Create a database batch to flush persistent data out. It is important that
- // outside code doesn't see an inconsistent state (referenced data removed from
- // memory cache during commit but not yet in persistent storage). This is ensured
- // by only uncaching existing data when the database write finalizes.
- batch := db.diskdb.NewBatch()
- nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
-
- // db.dirtiesSize only contains the useful data in the cache, but when reporting
- // the total memory consumption, the maintenance metadata is also needed to be
- // counted.
- size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize)
- size += db.childrenSize
-
- // Keep committing nodes from the flush-list until we're below allowance
- oldest := db.oldest
- for size > limit && oldest != (common.Hash{}) {
- // Fetch the oldest referenced node and push into the batch
- node := db.dirties[oldest]
- rawdb.WriteLegacyTrieNode(batch, oldest, node.node)
-
- // If we exceeded the ideal batch size, commit and reset
- if batch.ValueSize() >= ethdb.IdealBatchSize {
- if err := batch.Write(); err != nil {
- log.Error("Failed to write flush list to disk", "err", err)
- return err
- }
- batch.Reset()
- }
- // Iterate to the next flush item, or abort if the size cap was achieved. Size
- // is the total size, including the useful cached data (hash -> blob), the
- // cache item metadata, as well as external children mappings.
- size -= common.StorageSize(common.HashLength + len(node.node) + cachedNodeSize)
- if node.external != nil {
- size -= common.StorageSize(len(node.external) * common.HashLength)
- }
- oldest = node.flushNext
- }
- // Flush out any remainder data from the last batch
- if err := batch.Write(); err != nil {
- log.Error("Failed to write flush list to disk", "err", err)
- return err
- }
- // Write successful, clear out the flushed data
- for db.oldest != oldest {
- node := db.dirties[db.oldest]
- delete(db.dirties, db.oldest)
- db.oldest = node.flushNext
-
- db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node))
- if node.external != nil {
- db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength)
- }
- }
- if db.oldest != (common.Hash{}) {
- db.dirties[db.oldest].flushPrev = common.Hash{}
- }
- db.flushnodes += uint64(nodes - len(db.dirties))
- db.flushsize += storage - db.dirtiesSize
- db.flushtime += time.Since(start)
-
- memcacheFlushTimeTimer.Update(time.Since(start))
- memcacheFlushBytesMeter.Mark(int64(storage - db.dirtiesSize))
- memcacheFlushNodesMeter.Mark(int64(nodes - len(db.dirties)))
-
- log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start),
- "flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
-
- return nil
-}
-
-// Commit iterates over all the children of a particular node, writes them out
-// to disk, forcefully tearing down all references in both directions. As a side
-// effect, all pre-images accumulated up to this point are also written.
-func (db *Database) Commit(node common.Hash, report bool) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Create a database batch to flush persistent data out. It is important that
- // outside code doesn't see an inconsistent state (referenced data removed from
- // memory cache during commit but not yet in persistent storage). This is ensured
- // by only uncaching existing data when the database write finalizes.
- start := time.Now()
- batch := db.diskdb.NewBatch()
-
- // Move the trie itself into the batch, flushing if enough data is accumulated
- nodes, storage := len(db.dirties), db.dirtiesSize
-
- uncacher := &cleaner{db}
- if err := db.commit(node, batch, uncacher); err != nil {
- log.Error("Failed to commit trie from trie database", "err", err)
- return err
- }
- // Trie mostly committed to disk, flush any batch leftovers
- if err := batch.Write(); err != nil {
- log.Error("Failed to write trie to disk", "err", err)
- return err
- }
- // Uncache any leftovers in the last batch
- if err := batch.Replay(uncacher); err != nil {
- return err
- }
- batch.Reset()
-
- // Reset the storage counters and bumped metrics
- memcacheCommitTimeTimer.Update(time.Since(start))
- memcacheCommitBytesMeter.Mark(int64(storage - db.dirtiesSize))
- memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties)))
-
- logger := log.Info
- if !report {
- logger = log.Debug
- }
- logger("Persisted trie from memory database", "nodes", nodes-len(db.dirties)+int(db.flushnodes), "size", storage-db.dirtiesSize+db.flushsize, "time", time.Since(start)+db.flushtime,
- "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize)
-
- // Reset the garbage collection statistics
- db.gcnodes, db.gcsize, db.gctime = 0, 0, 0
- db.flushnodes, db.flushsize, db.flushtime = 0, 0, 0
-
- return nil
-}
-
-// commit is the private locked version of Commit.
-func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {
- // If the node does not exist, it's a previously committed node
- node, ok := db.dirties[hash]
- if !ok {
- return nil
- }
- var err error
-
- // Dereference all children and delete the node
- node.forChildren(db.resolver, func(child common.Hash) {
- if err == nil {
- err = db.commit(child, batch, uncacher)
- }
- })
- if err != nil {
- return err
- }
- // If we've reached an optimal batch size, commit and start over
- rawdb.WriteLegacyTrieNode(batch, hash, node.node)
- if batch.ValueSize() >= ethdb.IdealBatchSize {
- if err := batch.Write(); err != nil {
- return err
- }
- err := batch.Replay(uncacher)
- if err != nil {
- return err
- }
- batch.Reset()
- }
- return nil
-}
-
-// cleaner is a database batch replayer that takes a batch of write operations
-// and cleans up the trie database from anything written to disk.
-type cleaner struct {
- db *Database
-}
-
-// Put reacts to database writes and implements dirty data uncaching. This is the
-// post-processing step of a commit operation where the already persisted trie is
-// removed from the dirty cache and moved into the clean cache. The reason behind
-// the two-phase commit is to ensure data availability while moving from memory
-// to disk.
-func (c *cleaner) Put(key []byte, rlp []byte) error {
- hash := common.BytesToHash(key)
-
- // If the node does not exist, we're done on this path
- node, ok := c.db.dirties[hash]
- if !ok {
- return nil
- }
- // Node still exists, remove it from the flush-list
- switch hash {
- case c.db.oldest:
- c.db.oldest = node.flushNext
- if node.flushNext != (common.Hash{}) {
- c.db.dirties[node.flushNext].flushPrev = common.Hash{}
- }
- case c.db.newest:
- c.db.newest = node.flushPrev
- if node.flushPrev != (common.Hash{}) {
- c.db.dirties[node.flushPrev].flushNext = common.Hash{}
- }
- default:
- c.db.dirties[node.flushPrev].flushNext = node.flushNext
- c.db.dirties[node.flushNext].flushPrev = node.flushPrev
- }
- // Remove the node from the dirty cache
- delete(c.db.dirties, hash)
- c.db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node))
- if node.external != nil {
- c.db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength)
- }
- // Move the flushed node into the clean cache to prevent insta-reloads
- if c.db.cleans != nil {
- c.db.cleans.Set(hash[:], rlp)
- memcacheCleanWriteMeter.Mark(int64(len(rlp)))
- }
- return nil
-}
-
-func (c *cleaner) Delete(key []byte) error {
- panic("not implemented")
-}
-
-// Initialized returns an indicator if state data is already initialized
-// in hash-based scheme by checking the presence of genesis state.
-func (db *Database) Initialized(genesisRoot common.Hash) bool {
- return rawdb.HasLegacyTrieNode(db.diskdb, genesisRoot)
-}
-
-// Update inserts the dirty nodes in provided nodeset into database and link the
-// account trie with multiple storage tries if necessary.
-func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
- // Ensure the parent state is present and signal a warning if not.
- if parent != types.EmptyRootHash {
- if blob, _ := db.node(parent); len(blob) == 0 {
- log.Error("parent state is not present")
- }
- }
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Insert dirty nodes into the database. In the same tree, it must be
- // ensured that children are inserted first, then parent so that children
- // can be linked with their parent correctly.
- //
- // Note, the storage tries must be flushed before the account trie to
- // retain the invariant that children go into the dirty cache first.
- var order []common.Hash
- for owner := range nodes.Sets {
- if owner == (common.Hash{}) {
- continue
- }
- order = append(order, owner)
- }
- if _, ok := nodes.Sets[common.Hash{}]; ok {
- order = append(order, common.Hash{})
- }
- for _, owner := range order {
- subset := nodes.Sets[owner]
- subset.ForEachWithOrder(func(path string, n *trienode.Node) {
- if n.IsDeleted() {
- return // ignore deletion
- }
- db.insert(n.Hash, n.Blob)
- })
- }
- // Link up the account trie and storage trie if the node points
- // to an account trie leaf.
- if set, present := nodes.Sets[common.Hash{}]; present {
- for _, n := range set.Leaves {
- var account types.StateAccount
- if err := rlp.DecodeBytes(n.Blob, &account); err != nil {
- return err
- }
- if account.Root != types.EmptyRootHash {
- db.reference(account.Root, n.Parent)
- }
- }
- }
- return nil
-}
-
-// Size returns the current storage size of the memory cache in front of the
-// persistent database layer.
-//
-// The first return will always be 0, representing the memory stored in unbounded
-// diff layers above the dirty cache. This is only available in pathdb.
-func (db *Database) Size() (common.StorageSize, common.StorageSize) {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- // db.dirtiesSize only contains the useful data in the cache, but when reporting
- // the total memory consumption, the maintenance metadata is also needed to be
- // counted.
- var metadataSize = common.StorageSize(len(db.dirties) * cachedNodeSize)
- return 0, db.dirtiesSize + db.childrenSize + metadataSize
-}
-
-// Close closes the trie database and releases all held resources.
-func (db *Database) Close() error {
- if db.cleans != nil {
- db.cleans.Reset()
- db.cleans = 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.
-// An error will be returned if the requested state is not available.
-func (db *Database) Reader(root common.Hash) (*reader, error) {
- if _, err := db.node(root); err != nil {
- return nil, fmt.Errorf("state %#x is not available, %v", root, err)
- }
- return &reader{db: db}, nil
-}
-
-// reader is a state reader of Database which implements the Reader interface.
-type reader struct {
- db *Database
-}
-
-// Node retrieves the trie node with the given node hash. No error will be
-// returned if the node is not found.
-func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
- blob, _ := reader.db.node(hash)
- return blob, nil
-}
diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go
deleted file mode 100644
index f2d6cea635..0000000000
--- a/trie/triedb/pathdb/database.go
+++ /dev/null
@@ -1,485 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "errors"
- "fmt"
- "io"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-const (
- // maxDiffLayers is the maximum diff layers allowed in the layer tree.
- maxDiffLayers = 128
-
- // defaultCleanSize is the default memory allowance of clean cache.
- defaultCleanSize = 16 * 1024 * 1024
-
- // maxBufferSize is the maximum memory allowance of node buffer.
- // Too large nodebuffer will cause the system to pause for a long
- // time when write happens. Also, the largest batch that pebble can
- // support is 4GB, node will panic if batch size exceeds this limit.
- maxBufferSize = 256 * 1024 * 1024
-
- // DefaultBufferSize is the default memory allowance of node buffer
- // that aggregates the writes from above until it's flushed into the
- // disk. It's meant to be used once the initial sync is finished.
- // Do not increase the buffer size arbitrarily, otherwise the system
- // pause time will increase when the database writes happen.
- DefaultBufferSize = 64 * 1024 * 1024
-)
-
-// layer is the interface implemented by all state layers which includes some
-// public methods and some additional methods for internal usage.
-type layer interface {
- // 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
- // stale, or the associated state is regarded as corrupted. Notably, no error
- // will be returned if the requested node is not found in database.
- Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
-
- // rootHash returns the root hash for which this layer was made.
- rootHash() common.Hash
-
- // stateID returns the associated state id of layer.
- stateID() uint64
-
- // parentLayer returns the subsequent layer of it, or nil if the disk was reached.
- parentLayer() layer
-
- // update creates a new layer on top of the existing layer diff tree with
- // the provided dirty trie nodes along with the state change set.
- //
- // 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
-
- // 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
- // flattening everything down (bad for reorgs).
- journal(w io.Writer) error
-}
-
-// Config contains the settings for database.
-type Config struct {
- StateHistory uint64 // Number of recent blocks to maintain state history for
- CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes
- DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes
- ReadOnly bool // Flag whether the database is opened in read only mode.
-}
-
-// sanitize checks the provided user configurations and changes anything that's
-// unreasonable or unworkable.
-func (c *Config) sanitize() *Config {
- conf := *c
- if conf.DirtyCacheSize > maxBufferSize {
- log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize))
- conf.DirtyCacheSize = maxBufferSize
- }
- return &conf
-}
-
-// Defaults contains default settings for Ethereum mainnet.
-var Defaults = &Config{
- StateHistory: params.FullImmutabilityThreshold,
- CleanCacheSize: defaultCleanSize,
- DirtyCacheSize: DefaultBufferSize,
-}
-
-// ReadOnly is the config in order to open database in read only mode.
-var ReadOnly = &Config{ReadOnly: true}
-
-// 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
-// of which arbitrarily many in-memory diff layers are stacked. The memory diffs
-// can form a tree with branching, but the disk layer is singleton and common to
-// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can
-// be applied to rollback. The deepest reorg that can be handled depends on the
-// amount of state histories tracked in the disk.
-//
-// At most one readable and writable database can be opened at the same time in
-// the whole system which ensures that only one database writer can operate disk
-// state. Unexpected open operations can cause the system to panic.
-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
- bufferSize int // Memory allowance (in bytes) for caching dirty nodes
- config *Config // Configuration for database
- diskdb ethdb.Database // Persistent storage for matured trie nodes
- tree *layerTree // The group for all known layers
- freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests
- 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
-// 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.
-func New(diskdb ethdb.Database, config *Config) *Database {
- if config == nil {
- config = Defaults
- }
- config = config.sanitize()
-
- db := &Database{
- readOnly: config.ReadOnly,
- bufferSize: config.DirtyCacheSize,
- config: config,
- diskdb: diskdb,
- }
- // Construct the layer tree by resolving the in-disk singleton state
- // and in-memory layer journal.
- db.tree = newLayerTree(db.loadLayers())
-
- // Open the freezer for state history if the passed database contains an
- // ancient store. Otherwise, all the relevant functionalities are disabled.
- //
- // Because the freezer can only be opened once at the same time, this
- // mechanism also ensures that at most one **non-readOnly** database
- // is opened at the same time to prevent accidental mutation.
- if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly {
- freezer, err := rawdb.NewStateFreezer(ancient, false)
- if err != nil {
- log.Crit("Failed to open state history freezer", "err", err)
- }
- db.freezer = freezer
-
- diskLayerID := db.tree.bottom().stateID()
- if diskLayerID == 0 {
- // Reset the entire state histories in case the trie database is
- // not initialized yet, as these state histories are not expected.
- frozen, err := db.freezer.Ancients()
- if err != nil {
- log.Crit("Failed to retrieve head of state history", "err", err)
- }
- if frozen != 0 {
- err := db.freezer.Reset()
- if err != nil {
- log.Crit("Failed to reset state histories", "err", err)
- }
- log.Info("Truncated extraneous state history")
- }
- } else {
- // Truncate the extra state histories above in freezer in case
- // it's not aligned with the disk layer.
- pruned, err := truncateFromHead(db.diskdb, freezer, diskLayerID)
- if err != nil {
- log.Crit("Failed to truncate extra state histories", "err", err)
- }
- if pruned != 0 {
- log.Warn("Truncated extra state histories", "number", pruned)
- }
- }
- }
- // Disable database in case node is still in the initial state sync stage.
- if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly {
- if err := db.Disable(); err != nil {
- log.Crit("Failed to disable database", "err", err) // impossible to happen
- }
- }
- log.Warn("Path-based state scheme is an experimental feature")
- return db
-}
-
-// Reader retrieves a layer belonging to the given state root.
-func (db *Database) Reader(root common.Hash) (layer, error) {
- l := db.tree.get(root)
- if l == nil {
- return nil, fmt.Errorf("state %#x is not available", root)
- }
- return l, nil
-}
-
-// Update adds a new layer into the tree, if that can be linked to an existing
-// old parent. It is disallowed to insert a disk layer (the origin of all). Apart
-// from that this function will flatten the extra diff layers at bottom into disk
-// to only keep 128 diff layers in memory by default.
-//
-// The passed in maps(nodes, states) will be retained to avoid copying everything.
-// Therefore, these maps must not be changed afterwards.
-func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
- // Hold the lock to prevent concurrent mutations.
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the mutation is not allowed.
- if err := db.modifyAllowed(); err != nil {
- return err
- }
- if err := db.tree.add(root, parentRoot, block, nodes, states); err != nil {
- return err
- }
- // Keep 128 diff layers in the memory, persistent layer is 129th.
- // - head layer is paired with HEAD state
- // - head-1 layer is paired with HEAD-1 state
- // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
- // - head-128 layer(disk layer) is paired with HEAD-128 state
- return db.tree.cap(root, maxDiffLayers)
-}
-
-// Commit traverses downwards the layer tree from a specified layer with the
-// provided state root and all the layers below are flattened downwards. It
-// can be used alone and mostly for test purposes.
-func (db *Database) Commit(root common.Hash, report bool) error {
- // Hold the lock to prevent concurrent mutations.
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the mutation is not allowed.
- if err := db.modifyAllowed(); err != nil {
- return err
- }
- return db.tree.cap(root, 0)
-}
-
-// Disable deactivates the database and invalidates all available state layers
-// as stale to prevent access to the persistent state, which is in the syncing
-// stage.
-func (db *Database) Disable() error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the database is in read only mode.
- if db.readOnly {
- return errDatabaseReadOnly
- }
- // Prevent duplicated disable operation.
- if db.waitSync {
- log.Error("Reject duplicated disable operation")
- return nil
- }
- db.waitSync = true
-
- // Mark the disk layer as stale to prevent access to persistent state.
- db.tree.bottom().markStale()
-
- // Write the initial sync flag to persist it across restarts.
- rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncRunning)
- log.Info("Disabled trie database due to state sync")
- return nil
-}
-
-// Enable activates database and resets the state tree with the provided persistent
-// state root once the state sync is finished.
-func (db *Database) Enable(root common.Hash) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the database is in read only mode.
- if db.readOnly {
- return errDatabaseReadOnly
- }
- // Ensure the provided state root matches the stored one.
- root = types.TrieRootHash(root)
- _, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
- if stored != root {
- return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
- }
- // Drop the stale state journal in persistent database and
- // reset the persistent state id back to zero.
- batch := db.diskdb.NewBatch()
- rawdb.DeleteTrieJournal(batch)
- rawdb.WritePersistentStateID(batch, 0)
- if err := batch.Write(); err != nil {
- return err
- }
- // Clean up all state histories in freezer. Theoretically
- // all root->id mappings should be removed as well. Since
- // mappings can be huge and might take a while to clear
- // them, just leave them in disk and wait for overwriting.
- if db.freezer != nil {
- if err := db.freezer.Reset(); err != nil {
- return err
- }
- }
- // Re-construct a new disk layer backed by persistent state
- // with **empty clean cache and node buffer**.
- db.tree.reset(newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0)))
-
- // Re-enable the database as the final step.
- db.waitSync = false
- rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished)
- log.Info("Rebuilt trie database", "root", root)
- return nil
-}
-
-// 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.
-func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if rollback operation is not supported.
- if err := db.modifyAllowed(); err != nil {
- return err
- }
- if db.freezer == nil {
- 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
- }
- // Apply the state histories upon the disk layer in order.
- var (
- start = time.Now()
- dl = db.tree.bottom()
- )
- for dl.rootHash() != root {
- h, err := readHistory(db.freezer, dl.stateID())
- if err != nil {
- return err
- }
- dl, err = dl.revert(h, loader)
- if err != nil {
- return err
- }
- // reset layer with newly created disk layer. It must be
- // done after each revert operation, otherwise the new
- // disk layer won't be accessible from outside.
- db.tree.reset(dl)
- }
- rawdb.DeleteTrieJournal(db.diskdb)
- _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID())
- if err != nil {
- return err
- }
- log.Debug("Recovered state", "root", root, "elapsed", common.PrettyDuration(time.Since(start)))
- return nil
-}
-
-// Recoverable returns the indicator if the specified state is recoverable.
-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
- }
- // Recoverable state must below the disk layer. The recoverable
- // state only refers the state that is currently not available,
- // but can be restored by applying state history.
- dl := db.tree.bottom()
- if *id >= dl.stateID() {
- return false
- }
- // Ensure the requested state is a canonical state and all state
- // histories in range [id+1, disklayer.ID] are present and complete.
- parent := root
- return checkHistories(db.freezer, *id+1, dl.stateID()-*id, func(m *meta) error {
- if m.parent != parent {
- return errors.New("unexpected state history")
- }
- if len(m.incomplete) > 0 {
- return errors.New("incomplete state history")
- }
- parent = m.root
- return nil
- }) == nil
-}
-
-// Close closes the trie database and the held freezer.
-func (db *Database) Close() error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Set the database to read-only mode to prevent all
- // following mutations.
- db.readOnly = true
-
- // Release the memory held by clean cache.
- db.tree.bottom().resetCache()
-
- // Close the attached state history freezer.
- if db.freezer == nil {
- return nil
- }
- return db.freezer.Close()
-}
-
-// Size returns the current storage size of the memory cache in front of the
-// persistent database layer.
-func (db *Database) Size() (diffs common.StorageSize, nodes common.StorageSize) {
- db.tree.forEach(func(layer layer) {
- if diff, ok := layer.(*diffLayer); ok {
- diffs += common.StorageSize(diff.memory)
- }
- if disk, ok := layer.(*diskLayer); ok {
- nodes += disk.size()
- }
- })
- return diffs, nodes
-}
-
-// Initialized returns an indicator if the state data is already
-// initialized in path-based scheme.
-func (db *Database) Initialized(genesisRoot common.Hash) bool {
- var inited bool
- db.tree.forEach(func(layer layer) {
- if layer.rootHash() != types.EmptyRootHash {
- inited = true
- }
- })
- if !inited {
- inited = rawdb.ReadSnapSyncStatusFlag(db.diskdb) != rawdb.StateSyncUnknown
- }
- return inited
-}
-
-// SetBufferSize sets the node buffer size to the provided value(in bytes).
-func (db *Database) SetBufferSize(size int) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- if size > maxBufferSize {
- log.Info("Capped node buffer size", "provided", common.StorageSize(size), "adjusted", common.StorageSize(maxBufferSize))
- size = maxBufferSize
- }
- db.bufferSize = size
- 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
-// assumes the db.lock is already held.
-func (db *Database) modifyAllowed() error {
- if db.readOnly {
- return errDatabaseReadOnly
- }
- if db.waitSync {
- return errDatabaseWaitSync
- }
- return nil
-}
diff --git a/trie/triedb/pathdb/database_test.go b/trie/triedb/pathdb/database_test.go
deleted file mode 100644
index 5509682c39..0000000000
--- a/trie/triedb/pathdb/database_test.go
+++ /dev/null
@@ -1,608 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "bytes"
- "errors"
- "fmt"
- "math/big"
- "math/rand"
- "testing"
-
- "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/rlp"
- "github.com/ethereum/go-ethereum/trie/testutil"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) {
- h, err := newTestHasher(addrHash, root, cleans)
- if err != nil {
- panic(fmt.Errorf("failed to create hasher, err: %w", err))
- }
- for key, val := range dirties {
- if len(val) == 0 {
- h.Delete(key.Bytes())
- } else {
- h.Update(key.Bytes(), val)
- }
- }
- root, nodes, _ := h.Commit(false)
- return root, nodes
-}
-
-func generateAccount(storageRoot common.Hash) types.StateAccount {
- return types.StateAccount{
- Nonce: uint64(rand.Intn(100)),
- Balance: big.NewInt(rand.Int63()),
- CodeHash: testutil.RandBytes(32),
- Root: storageRoot,
- }
-}
-
-const (
- createAccountOp int = iota
- modifyAccountOp
- deleteAccountOp
- opLen
-)
-
-type genctx struct {
- accounts map[common.Hash][]byte
- storages map[common.Hash]map[common.Hash][]byte
- accountOrigin map[common.Address][]byte
- storageOrigin map[common.Address]map[common.Hash][]byte
- nodes *trienode.MergedNodeSet
-}
-
-func newCtx() *genctx {
- return &genctx{
- accounts: make(map[common.Hash][]byte),
- storages: make(map[common.Hash]map[common.Hash][]byte),
- accountOrigin: make(map[common.Address][]byte),
- storageOrigin: make(map[common.Address]map[common.Hash][]byte),
- nodes: trienode.NewMergedNodeSet(),
- }
-}
-
-type tester struct {
- db *Database
- roots []common.Hash
- preimages map[common.Hash]common.Address
- accounts map[common.Hash][]byte
- storages map[common.Hash]map[common.Hash][]byte
-
- // state snapshots
- snapAccounts map[common.Hash]map[common.Hash][]byte
- snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte
-}
-
-func newTester(t *testing.T, historyLimit uint64) *tester {
- var (
- disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
- db = New(disk, &Config{
- StateHistory: historyLimit,
- CleanCacheSize: 256 * 1024,
- DirtyCacheSize: 256 * 1024,
- })
- obj = &tester{
- db: db,
- preimages: make(map[common.Hash]common.Address),
- accounts: make(map[common.Hash][]byte),
- storages: make(map[common.Hash]map[common.Hash][]byte),
- snapAccounts: make(map[common.Hash]map[common.Hash][]byte),
- snapStorages: make(map[common.Hash]map[common.Hash]map[common.Hash][]byte),
- }
- )
- for i := 0; i < 2*128; i++ {
- var parent = types.EmptyRootHash
- if len(obj.roots) != 0 {
- parent = obj.roots[len(obj.roots)-1]
- }
- root, nodes, states := obj.generate(parent)
- if err := db.Update(root, parent, uint64(i), nodes, states); err != nil {
- panic(fmt.Errorf("failed to update state changes, err: %w", err))
- }
- obj.roots = append(obj.roots, root)
- }
- return obj
-}
-
-func (t *tester) release() {
- t.db.Close()
- t.db.diskdb.Close()
-}
-
-func (t *tester) randAccount() (common.Address, []byte) {
- for addrHash, account := range t.accounts {
- return t.preimages[addrHash], account
- }
- return common.Address{}, nil
-}
-
-func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash {
- var (
- addrHash = crypto.Keccak256Hash(addr.Bytes())
- storage = make(map[common.Hash][]byte)
- origin = make(map[common.Hash][]byte)
- )
- for i := 0; i < 10; i++ {
- v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32)))
- hash := testutil.RandomHash()
-
- storage[hash] = v
- origin[hash] = nil
- }
- root, set := updateTrie(addrHash, types.EmptyRootHash, storage, nil)
-
- ctx.storages[addrHash] = storage
- ctx.storageOrigin[addr] = origin
- ctx.nodes.Merge(set)
- return root
-}
-
-func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash {
- var (
- addrHash = crypto.Keccak256Hash(addr.Bytes())
- storage = make(map[common.Hash][]byte)
- origin = make(map[common.Hash][]byte)
- )
- for hash, val := range t.storages[addrHash] {
- origin[hash] = val
- storage[hash] = nil
-
- if len(origin) == 3 {
- break
- }
- }
- for i := 0; i < 3; i++ {
- v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32)))
- hash := testutil.RandomHash()
-
- storage[hash] = v
- origin[hash] = nil
- }
- root, set := updateTrie(crypto.Keccak256Hash(addr.Bytes()), root, storage, t.storages[addrHash])
-
- ctx.storages[addrHash] = storage
- ctx.storageOrigin[addr] = origin
- ctx.nodes.Merge(set)
- return root
-}
-
-func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash {
- var (
- addrHash = crypto.Keccak256Hash(addr.Bytes())
- storage = make(map[common.Hash][]byte)
- origin = make(map[common.Hash][]byte)
- )
- for hash, val := range t.storages[addrHash] {
- origin[hash] = val
- storage[hash] = nil
- }
- root, set := updateTrie(addrHash, root, storage, t.storages[addrHash])
- if root != types.EmptyRootHash {
- panic("failed to clear storage trie")
- }
- ctx.storages[addrHash] = storage
- ctx.storageOrigin[addr] = origin
- ctx.nodes.Merge(set)
- return root
-}
-
-func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) {
- var (
- ctx = newCtx()
- dirties = make(map[common.Hash]struct{})
- )
- for i := 0; i < 20; i++ {
- switch rand.Intn(opLen) {
- case createAccountOp:
- // account creation
- addr := testutil.RandomAddress()
- addrHash := crypto.Keccak256Hash(addr.Bytes())
- if _, ok := t.accounts[addrHash]; ok {
- continue
- }
- if _, ok := dirties[addrHash]; ok {
- continue
- }
- dirties[addrHash] = struct{}{}
-
- root := t.generateStorage(ctx, addr)
- ctx.accounts[addrHash] = types.SlimAccountRLP(generateAccount(root))
- ctx.accountOrigin[addr] = nil
- t.preimages[addrHash] = addr
-
- case modifyAccountOp:
- // account mutation
- addr, account := t.randAccount()
- if addr == (common.Address{}) {
- continue
- }
- addrHash := crypto.Keccak256Hash(addr.Bytes())
- if _, ok := dirties[addrHash]; ok {
- continue
- }
- dirties[addrHash] = struct{}{}
-
- acct, _ := types.FullAccount(account)
- stRoot := t.mutateStorage(ctx, addr, acct.Root)
- newAccount := types.SlimAccountRLP(generateAccount(stRoot))
-
- ctx.accounts[addrHash] = newAccount
- ctx.accountOrigin[addr] = account
-
- case deleteAccountOp:
- // account deletion
- addr, account := t.randAccount()
- if addr == (common.Address{}) {
- continue
- }
- addrHash := crypto.Keccak256Hash(addr.Bytes())
- if _, ok := dirties[addrHash]; ok {
- continue
- }
- dirties[addrHash] = struct{}{}
-
- acct, _ := types.FullAccount(account)
- if acct.Root != types.EmptyRootHash {
- t.clearStorage(ctx, addr, acct.Root)
- }
- ctx.accounts[addrHash] = nil
- ctx.accountOrigin[addr] = account
- }
- }
- root, set := updateTrie(common.Hash{}, parent, ctx.accounts, t.accounts)
- ctx.nodes.Merge(set)
-
- // Save state snapshot before commit
- t.snapAccounts[parent] = copyAccounts(t.accounts)
- t.snapStorages[parent] = copyStorages(t.storages)
-
- // Commit all changes to live state set
- for addrHash, account := range ctx.accounts {
- if len(account) == 0 {
- delete(t.accounts, addrHash)
- } else {
- t.accounts[addrHash] = account
- }
- }
- for addrHash, slots := range ctx.storages {
- if _, ok := t.storages[addrHash]; !ok {
- t.storages[addrHash] = make(map[common.Hash][]byte)
- }
- for sHash, slot := range slots {
- if len(slot) == 0 {
- delete(t.storages[addrHash], sHash)
- } else {
- t.storages[addrHash][sHash] = slot
- }
- }
- }
- return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin, nil)
-}
-
-// lastRoot returns the latest root hash, or empty if nothing is cached.
-func (t *tester) lastHash() common.Hash {
- if len(t.roots) == 0 {
- return common.Hash{}
- }
- return t.roots[len(t.roots)-1]
-}
-
-func (t *tester) verifyState(root common.Hash) error {
- reader, err := t.db.Reader(root)
- if err != nil {
- return err
- }
- _, err = reader.Node(common.Hash{}, nil, root)
- if err != nil {
- return errors.New("root node is not available")
- }
- for addrHash, account := range t.snapAccounts[root] {
- blob, err := reader.Node(common.Hash{}, addrHash.Bytes(), crypto.Keccak256Hash(account))
- if err != nil || !bytes.Equal(blob, account) {
- return fmt.Errorf("account is mismatched: %w", err)
- }
- }
- for addrHash, slots := range t.snapStorages[root] {
- for hash, slot := range slots {
- blob, err := reader.Node(addrHash, hash.Bytes(), crypto.Keccak256Hash(slot))
- if err != nil || !bytes.Equal(blob, slot) {
- return fmt.Errorf("slot is mismatched: %w", err)
- }
- }
- }
- return nil
-}
-
-func (t *tester) verifyHistory() error {
- bottom := t.bottomIndex()
- for i, root := range t.roots {
- // The state history related to the state above disk layer should not exist.
- if i > bottom {
- _, err := readHistory(t.db.freezer, uint64(i+1))
- if err == nil {
- return errors.New("unexpected state history")
- }
- continue
- }
- // The state history related to the state below or equal to the disk layer
- // should exist.
- obj, err := readHistory(t.db.freezer, uint64(i+1))
- if err != nil {
- return err
- }
- parent := types.EmptyRootHash
- if i != 0 {
- parent = t.roots[i-1]
- }
- if obj.meta.parent != parent {
- return fmt.Errorf("unexpected parent, want: %x, got: %x", parent, obj.meta.parent)
- }
- if obj.meta.root != root {
- return fmt.Errorf("unexpected root, want: %x, got: %x", root, obj.meta.root)
- }
- }
- return nil
-}
-
-// bottomIndex returns the index of current disk layer.
-func (t *tester) bottomIndex() int {
- bottom := t.db.tree.bottom()
- for i := 0; i < len(t.roots); i++ {
- if t.roots[i] == bottom.rootHash() {
- return i
- }
- }
- return -1
-}
-
-func TestDatabaseRollback(t *testing.T) {
- // Verify state histories
- tester := newTester(t, 0)
- defer tester.release()
-
- if err := tester.verifyHistory(); err != nil {
- t.Fatalf("Invalid state history, err: %v", err)
- }
- // Revert database from top to bottom
- for i := tester.bottomIndex(); i >= 0; i-- {
- root := tester.roots[i]
- parent := types.EmptyRootHash
- if i > 0 {
- parent = tester.roots[i-1]
- }
- loader := newHashLoader(tester.snapAccounts[root], tester.snapStorages[root])
- if err := tester.db.Recover(parent, loader); err != nil {
- t.Fatalf("Failed to revert db, err: %v", err)
- }
- tester.verifyState(parent)
- }
- if tester.db.tree.len() != 1 {
- t.Fatal("Only disk layer is expected")
- }
-}
-
-func TestDatabaseRecoverable(t *testing.T) {
- var (
- tester = newTester(t, 0)
- index = tester.bottomIndex()
- )
- defer tester.release()
-
- var cases = []struct {
- root common.Hash
- expect bool
- }{
- // Unknown state should be unrecoverable
- {common.Hash{0x1}, false},
-
- // Initial state should be recoverable
- {types.EmptyRootHash, true},
-
- // Initial state should be recoverable
- {common.Hash{}, true},
-
- // Layers below current disk layer are recoverable
- {tester.roots[index-1], true},
-
- // Disklayer itself is not recoverable, since it's
- // available for accessing.
- {tester.roots[index], false},
-
- // Layers above current disk layer are not recoverable
- // since they are available for accessing.
- {tester.roots[index+1], false},
- }
- for i, c := range cases {
- result := tester.db.Recoverable(c.root)
- if result != c.expect {
- t.Fatalf("case: %d, unexpected result, want %t, got %t", i, c.expect, result)
- }
- }
-}
-
-func TestDisable(t *testing.T) {
- tester := newTester(t, 0)
- defer tester.release()
-
- _, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
- if err := tester.db.Disable(); err != nil {
- t.Fatal("Failed to deactivate database")
- }
- if err := tester.db.Enable(types.EmptyRootHash); err == nil {
- t.Fatalf("Invalid activation should be rejected")
- }
- if err := tester.db.Enable(stored); err != nil {
- t.Fatal("Failed to activate database")
- }
-
- // Ensure journal is deleted from disk
- if blob := rawdb.ReadTrieJournal(tester.db.diskdb); len(blob) != 0 {
- t.Fatal("Failed to clean journal")
- }
- // Ensure all trie histories are removed
- n, err := tester.db.freezer.Ancients()
- if err != nil {
- t.Fatal("Failed to clean state history")
- }
- if n != 0 {
- t.Fatal("Failed to clean state history")
- }
- // Verify layer tree structure, single disk layer is expected
- if tester.db.tree.len() != 1 {
- t.Fatalf("Extra layer kept %d", tester.db.tree.len())
- }
- if tester.db.tree.bottom().rootHash() != stored {
- t.Fatalf("Root hash is not matched exp %x got %x", stored, tester.db.tree.bottom().rootHash())
- }
-}
-
-func TestCommit(t *testing.T) {
- tester := newTester(t, 0)
- defer tester.release()
-
- if err := tester.db.Commit(tester.lastHash(), false); err != nil {
- t.Fatalf("Failed to cap database, err: %v", err)
- }
- // Verify layer tree structure, single disk layer is expected
- if tester.db.tree.len() != 1 {
- t.Fatal("Layer tree structure is invalid")
- }
- if tester.db.tree.bottom().rootHash() != tester.lastHash() {
- t.Fatal("Layer tree structure is invalid")
- }
- // Verify states
- if err := tester.verifyState(tester.lastHash()); err != nil {
- t.Fatalf("State is invalid, err: %v", err)
- }
- // Verify state histories
- if err := tester.verifyHistory(); err != nil {
- t.Fatalf("State history is invalid, err: %v", err)
- }
-}
-
-func TestJournal(t *testing.T) {
- tester := newTester(t, 0)
- defer tester.release()
-
- if err := tester.db.Journal(tester.lastHash()); err != nil {
- t.Errorf("Failed to journal, err: %v", err)
- }
- tester.db.Close()
- tester.db = New(tester.db.diskdb, nil)
-
- // Verify states including disk layer and all diff on top.
- for i := 0; i < len(tester.roots); i++ {
- if i >= tester.bottomIndex() {
- if err := tester.verifyState(tester.roots[i]); err != nil {
- t.Fatalf("Invalid state, err: %v", err)
- }
- continue
- }
- if err := tester.verifyState(tester.roots[i]); err == nil {
- t.Fatal("Unexpected state")
- }
- }
-}
-
-func TestCorruptedJournal(t *testing.T) {
- tester := newTester(t, 0)
- defer tester.release()
-
- if err := tester.db.Journal(tester.lastHash()); err != nil {
- t.Errorf("Failed to journal, err: %v", err)
- }
- tester.db.Close()
- _, root := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
-
- // Mutate the journal in disk, it should be regarded as invalid
- blob := rawdb.ReadTrieJournal(tester.db.diskdb)
- blob[0] = 1
- rawdb.WriteTrieJournal(tester.db.diskdb, blob)
-
- // Verify states, all not-yet-written states should be discarded
- tester.db = New(tester.db.diskdb, nil)
- for i := 0; i < len(tester.roots); i++ {
- if tester.roots[i] == root {
- if err := tester.verifyState(root); err != nil {
- t.Fatalf("Disk state is corrupted, err: %v", err)
- }
- continue
- }
- if err := tester.verifyState(tester.roots[i]); err == nil {
- t.Fatal("Unexpected state")
- }
- }
-}
-
-// TestTailTruncateHistory function is designed to test a specific edge case where,
-// when history objects are removed from the end, it should trigger a state flush
-// if the ID of the new tail object is even higher than the persisted state ID.
-//
-// For example, let's say the ID of the persistent state is 10, and the current
-// history objects range from ID(5) to ID(15). As we accumulate six more objects,
-// the history will expand to cover ID(11) to ID(21). ID(11) then becomes the
-// oldest history object, and its ID is even higher than the stored state.
-//
-// In this scenario, it is mandatory to update the persistent state before
-// truncating the tail histories. This ensures that the ID of the persistent state
-// always falls within the range of [oldest-history-id, latest-history-id].
-func TestTailTruncateHistory(t *testing.T) {
- tester := newTester(t, 10)
- defer tester.release()
-
- tester.db.Close()
- tester.db = New(tester.db.diskdb, &Config{StateHistory: 10})
-
- head, err := tester.db.freezer.Ancients()
- if err != nil {
- t.Fatalf("Failed to obtain freezer head")
- }
- stored := rawdb.ReadPersistentStateID(tester.db.diskdb)
- if head != stored {
- t.Fatalf("Failed to truncate excess history object above, stored: %d, head: %d", stored, head)
- }
-}
-
-// copyAccounts returns a deep-copied account set of the provided one.
-func copyAccounts(set map[common.Hash][]byte) map[common.Hash][]byte {
- copied := make(map[common.Hash][]byte, len(set))
- for key, val := range set {
- copied[key] = common.CopyBytes(val)
- }
- return copied
-}
-
-// copyStorages returns a deep-copied storage set of the provided one.
-func copyStorages(set map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte {
- copied := make(map[common.Hash]map[common.Hash][]byte, len(set))
- for addrHash, subset := range set {
- copied[addrHash] = make(map[common.Hash][]byte, len(subset))
- for key, val := range subset {
- copied[addrHash][key] = common.CopyBytes(val)
- }
- }
- return copied
-}
diff --git a/trie/triedb/pathdb/difflayer.go b/trie/triedb/pathdb/difflayer.go
deleted file mode 100644
index 10567715d2..0000000000
--- a/trie/triedb/pathdb/difflayer.go
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "fmt"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-// diffLayer represents a collection of modifications made to the in-memory tries
-// along with associated state changes after running a block on top.
-//
-// The goal of a diff layer is to act as a journal, tracking recent modifications
-// made to the state, that have not yet graduated into a semi-immutable state.
-type diffLayer struct {
- // Immutables
- root common.Hash // Root hash to which this layer diff belongs to
- id uint64 // Corresponding state id
- block uint64 // Associated block number
- nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path
- states *triestate.Set // Associated state change set for building history
- memory uint64 // Approximate guess as to how much memory we use
-
- parent layer // Parent layer modified by this one, never nil, **can be changed**
- lock sync.RWMutex // Lock used to protect parent
-}
-
-// 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 {
- var (
- size int64
- count int
- )
- dl := &diffLayer{
- root: root,
- id: id,
- block: block,
- nodes: nodes,
- states: states,
- parent: parent,
- }
- for _, subset := range nodes {
- for path, n := range subset {
- dl.memory += uint64(n.Size() + len(path))
- size += int64(len(n.Blob) + len(path))
- }
- count += len(subset)
- }
- if states != nil {
- dl.memory += uint64(states.Size())
- }
- dirtyWriteMeter.Mark(size)
- diffLayerNodesMeter.Mark(int64(count))
- diffLayerBytesMeter.Mark(int64(dl.memory))
- log.Debug("Created new diff layer", "id", id, "block", block, "nodes", count, "size", common.StorageSize(dl.memory))
- return dl
-}
-
-// rootHash implements the layer interface, returning the root hash of
-// corresponding state.
-func (dl *diffLayer) rootHash() common.Hash {
- return dl.root
-}
-
-// stateID implements the layer interface, returning the state id of the layer.
-func (dl *diffLayer) stateID() uint64 {
- return dl.id
-}
-
-// parentLayer implements the layer interface, returning the subsequent
-// layer of the diff layer.
-func (dl *diffLayer) parentLayer() layer {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- return dl.parent
-}
-
-// node retrieves the node with provided node information. It's the internal
-// version of Node function with additional accessed layer tracked. No error
-// will be returned if node is not found.
-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
- // state accessing.
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- // If the trie node is known locally, return it
- subset, ok := dl.nodes[owner]
- if ok {
- n, ok := subset[string(path)]
- 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)
- dirtyNodeHitDepthHist.Update(int64(depth))
- dirtyReadMeter.Mark(int64(len(n.Blob)))
- return n.Blob, nil
- }
- }
- // Trie node unknown to this layer, resolve from parent
- if diff, ok := dl.parent.(*diffLayer); ok {
- 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
-// 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 {
- return newDiffLayer(dl, root, id, block, nodes, states)
-}
-
-// persist flushes the diff layer and all its parent layers to disk layer.
-func (dl *diffLayer) persist(force bool) (layer, error) {
- if parent, ok := dl.parentLayer().(*diffLayer); ok {
- // Hold the lock to prevent any read operation until the new
- // parent is linked correctly.
- dl.lock.Lock()
-
- // The merging of diff layers starts at the bottom-most layer,
- // therefore we recurse down here, flattening on the way up
- // (diffToDisk).
- result, err := parent.persist(force)
- if err != nil {
- dl.lock.Unlock()
- return nil, err
- }
- dl.parent = result
- dl.lock.Unlock()
- }
- return diffToDisk(dl, force)
-}
-
-// diffToDisk merges a bottom-most diff into the persistent disk layer underneath
-// it. The method will panic if called onto a non-bottom-most diff layer.
-func diffToDisk(layer *diffLayer, force bool) (layer, error) {
- disk, ok := layer.parentLayer().(*diskLayer)
- if !ok {
- panic(fmt.Sprintf("unknown layer type: %T", layer.parentLayer()))
- }
- return disk.commit(layer, force)
-}
diff --git a/trie/triedb/pathdb/difflayer_test.go b/trie/triedb/pathdb/difflayer_test.go
deleted file mode 100644
index 9b5907c3c5..0000000000
--- a/trie/triedb/pathdb/difflayer_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "bytes"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/trie/testutil"
- "github.com/ethereum/go-ethereum/trie/trienode"
-)
-
-func emptyLayer() *diskLayer {
- return &diskLayer{
- db: New(rawdb.NewMemoryDatabase(), nil),
- buffer: newNodeBuffer(DefaultBufferSize, nil, 0),
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkSearch128Layers
-// BenchmarkSearch128Layers-8 243826 4755 ns/op
-func BenchmarkSearch128Layers(b *testing.B) { benchmarkSearch(b, 0, 128) }
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkSearch512Layers
-// BenchmarkSearch512Layers-8 49686 24256 ns/op
-func BenchmarkSearch512Layers(b *testing.B) { benchmarkSearch(b, 0, 512) }
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkSearch1Layer
-// BenchmarkSearch1Layer-8 14062725 88.40 ns/op
-func BenchmarkSearch1Layer(b *testing.B) { benchmarkSearch(b, 127, 128) }
-
-func benchmarkSearch(b *testing.B, depth int, total int) {
- var (
- npath []byte
- nhash common.Hash
- nblob []byte
- )
- // First, we set up 128 diff layers, with 3K items each
- fill := func(parent layer, index int) *diffLayer {
- nodes := make(map[common.Hash]map[string]*trienode.Node)
- nodes[common.Hash{}] = make(map[string]*trienode.Node)
- for i := 0; i < 3000; i++ {
- var (
- path = testutil.RandBytes(32)
- node = testutil.RandomNode()
- )
- nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
- if npath == nil && depth == index {
- npath = common.CopyBytes(path)
- nblob = common.CopyBytes(node.Blob)
- nhash = node.Hash
- }
- }
- return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
- }
- var layer layer
- layer = emptyLayer()
- for i := 0; i < total; i++ {
- layer = fill(layer, i)
- }
- b.ResetTimer()
-
- var (
- have []byte
- err error
- )
- for i := 0; i < b.N; i++ {
- have, err = layer.Node(common.Hash{}, npath, nhash)
- if err != nil {
- b.Fatal(err)
- }
- }
- if !bytes.Equal(have, nblob) {
- b.Fatalf("have %x want %x", have, nblob)
- }
-}
-
-// goos: darwin
-// goarch: arm64
-// pkg: github.com/ethereum/go-ethereum/trie
-// BenchmarkPersist
-// BenchmarkPersist-8 10 111252975 ns/op
-func BenchmarkPersist(b *testing.B) {
- // First, we set up 128 diff layers, with 3K items each
- fill := func(parent layer) *diffLayer {
- nodes := make(map[common.Hash]map[string]*trienode.Node)
- nodes[common.Hash{}] = make(map[string]*trienode.Node)
- for i := 0; i < 3000; i++ {
- var (
- path = testutil.RandBytes(32)
- node = testutil.RandomNode()
- )
- nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
- }
- return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
- }
- for i := 0; i < b.N; i++ {
- b.StopTimer()
- var layer layer
- layer = emptyLayer()
- for i := 1; i < 128; i++ {
- layer = fill(layer)
- }
- b.StartTimer()
-
- dl, ok := layer.(*diffLayer)
- if !ok {
- break
- }
- dl.persist(false)
- }
-}
-
-// BenchmarkJournal benchmarks the performance for journaling the layers.
-//
-// BenchmarkJournal
-// BenchmarkJournal-8 10 110969279 ns/op
-func BenchmarkJournal(b *testing.B) {
- b.SkipNow()
-
- // First, we set up 128 diff layers, with 3K items each
- fill := func(parent layer) *diffLayer {
- nodes := make(map[common.Hash]map[string]*trienode.Node)
- nodes[common.Hash{}] = make(map[string]*trienode.Node)
- for i := 0; i < 3000; i++ {
- var (
- path = testutil.RandBytes(32)
- node = testutil.RandomNode()
- )
- nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob)
- }
- // TODO(rjl493456442) a non-nil state set is expected.
- return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil)
- }
- var layer layer
- layer = emptyLayer()
- for i := 0; i < 128; i++ {
- layer = fill(layer)
- }
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- layer.journal(new(bytes.Buffer))
- }
-}
diff --git a/trie/triedb/pathdb/disklayer.go b/trie/triedb/pathdb/disklayer.go
deleted file mode 100644
index ef697cbce8..0000000000
--- a/trie/triedb/pathdb/disklayer.go
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "errors"
- "fmt"
- "sync"
-
- "github.com/VictoriaMetrics/fastcache"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "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.
-type diskLayer struct {
- root common.Hash // Immutable, root hash to which this layer was made for
- id uint64 // Immutable, corresponding state id
- db *Database // Path-based trie database
- cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs
- buffer *nodebuffer // Node buffer to aggregate writes
- stale bool // Signals that the layer became stale (state progressed)
- lock sync.RWMutex // Lock used to protect stale flag
-}
-
-// newDiskLayer creates a new disk layer based on the passing arguments.
-func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.Cache, buffer *nodebuffer) *diskLayer {
- // Initialize a clean cache if the memory allowance is not zero
- // or reuse the provided cache if it is not nil (inherited from
- // the original disk layer).
- if cleans == nil && db.config.CleanCacheSize != 0 {
- cleans = fastcache.New(db.config.CleanCacheSize)
- }
- return &diskLayer{
- root: root,
- id: id,
- db: db,
- cleans: cleans,
- buffer: buffer,
- }
-}
-
-// root implements the layer interface, returning root hash of corresponding state.
-func (dl *diskLayer) rootHash() common.Hash {
- return dl.root
-}
-
-// stateID implements the layer interface, returning the state id of disk layer.
-func (dl *diskLayer) stateID() uint64 {
- return dl.id
-}
-
-// parent implements the layer interface, returning nil as there's no layer
-// below the disk.
-func (dl *diskLayer) parentLayer() layer {
- return nil
-}
-
-// isStale return whether this layer has become stale (was flattened across) or if
-// it's still live.
-func (dl *diskLayer) isStale() bool {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- return dl.stale
-}
-
-// markStale sets the stale flag as true.
-func (dl *diskLayer) markStale() {
- dl.lock.Lock()
- defer dl.lock.Unlock()
-
- if dl.stale {
- panic("triedb disk layer is stale") // we've committed into the same base from two children, boom
- }
- dl.stale = true
-}
-
-// 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.
-func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- if dl.stale {
- return nil, errSnapshotStale
- }
- // Try to retrieve the trie node from the not-yet-written
- // node buffer first. Note the buffer is lock free since
- // it's impossible to mutate the buffer before tagging the
- // layer as stale.
- n, err := dl.buffer.node(owner, path, hash)
- if err != nil {
- return nil, err
- }
- if n != nil {
- dirtyHitMeter.Mark(1)
- dirtyReadMeter.Mark(int64(len(n.Blob)))
- return n.Blob, nil
- }
- dirtyMissMeter.Mark(1)
-
- // Try to retrieve the trie node from the clean memory cache
- key := cacheKey(owner, path)
- if dl.cleans != nil {
- 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)
- cleanReadMeter.Mark(int64(len(blob)))
- return blob, nil
- }
- cleanFalseMeter.Mark(1)
- log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got)
- }
- cleanMissMeter.Mark(1)
- }
- // Try to retrieve the trie node from the disk.
- var (
- nBlob []byte
- nHash common.Hash
- )
- if owner == (common.Hash{}) {
- nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path)
- } else {
- nBlob, nHash = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path)
- }
- if nHash != hash {
- diskFalseMeter.Mark(1)
- log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash)
- return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path, nBlob)
- }
- if dl.cleans != nil && len(nBlob) > 0 {
- 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
-// 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 {
- return newDiffLayer(dl, root, id, block, nodes, states)
-}
-
-// commit merges the given bottom-most diff layer into the node buffer
-// and returns a newly constructed disk layer. Note the current disk
-// layer must be tagged as stale first to prevent re-access.
-func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
- dl.lock.Lock()
- defer dl.lock.Unlock()
-
- // Construct and store the state history first. If crash happens after storing
- // the state history but without flushing the corresponding states(journal),
- // the stored state history will be truncated from head in the next restart.
- var (
- overflow bool
- oldest uint64
- )
- if dl.db.freezer != nil {
- err := writeHistory(dl.db.freezer, bottom)
- if err != nil {
- return nil, err
- }
- // Determine if the persisted history object has exceeded the configured
- // limitation, set the overflow as true if so.
- tail, err := dl.db.freezer.Tail()
- if err != nil {
- return nil, err
- }
- limit := dl.db.config.StateHistory
- if limit != 0 && bottom.stateID()-tail > limit {
- overflow = true
- oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
- }
- }
- // Mark the diskLayer as stale before applying any mutations on top.
- dl.stale = true
-
- // Store the root->id lookup afterwards. All stored lookups are identified
- // by the **unique** state root. It's impossible that in the same chain
- // blocks are not adjacent but have the same root.
- if dl.id == 0 {
- rawdb.WriteStateID(dl.db.diskdb, dl.root, 0)
- }
- rawdb.WriteStateID(dl.db.diskdb, bottom.rootHash(), bottom.stateID())
-
- // Construct a new disk layer by merging the nodes from the provided diff
- // layer, and flush the content in disk layer if there are too many nodes
- // cached. The clean cache is inherited from the original disk layer.
- ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.cleans, dl.buffer.commit(bottom.nodes))
-
- // In a unique scenario where the ID of the oldest history object (after tail
- // truncation) surpasses the persisted state ID, we take the necessary action
- // of forcibly committing the cached dirty nodes to ensure that the persisted
- // state ID remains higher.
- if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest {
- force = true
- }
- if err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force); err != nil {
- return nil, err
- }
- // To remove outdated history objects from the end, we set the 'tail' parameter
- // to 'oldest-1' due to the offset between the freezer index and the history ID.
- if overflow {
- pruned, err := truncateFromTail(ndl.db.diskdb, ndl.db.freezer, oldest-1)
- if err != nil {
- return nil, err
- }
- log.Debug("Pruned state history", "items", pruned, "tailid", oldest)
- }
- return ndl, nil
-}
-
-// revert applies the given state history and return a reverted disk layer.
-func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer, error) {
- if h.meta.root != dl.rootHash() {
- return nil, errUnexpectedHistory
- }
- // Reject if the provided state history is incomplete. It's due to
- // a large construct SELF-DESTRUCT which can't be handled because
- // of memory limitation.
- if len(h.meta.incomplete) > 0 {
- return nil, errors.New("incomplete state history")
- }
- if dl.id == 0 {
- return nil, fmt.Errorf("%w: zero state id", errStateUnrecoverable)
- }
- // Apply the reverse state changes upon the current state. This must
- // be done before holding the lock in order to access state in "this"
- // layer.
- nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader)
- if err != nil {
- return nil, err
- }
- // Mark the diskLayer as stale before applying any mutations on top.
- dl.lock.Lock()
- defer dl.lock.Unlock()
-
- dl.stale = true
-
- // State change may be applied to node buffer, or the persistent
- // state, depends on if node buffer is empty or not. If the node
- // buffer is not empty, it means that the state transition that
- // needs to be reverted is not yet flushed and cached in node
- // buffer, otherwise, manipulate persistent state directly.
- if !dl.buffer.empty() {
- err := dl.buffer.revert(dl.db.diskdb, nodes)
- if err != nil {
- return nil, err
- }
- } else {
- batch := dl.db.diskdb.NewBatch()
- writeNodes(batch, nodes, dl.cleans)
- rawdb.WritePersistentStateID(batch, dl.id-1)
- if err := batch.Write(); err != nil {
- log.Crit("Failed to write states", "err", err)
- }
- }
- return newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.cleans, dl.buffer), nil
-}
-
-// setBufferSize sets the node buffer size to the provided value.
-func (dl *diskLayer) setBufferSize(size int) error {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- if dl.stale {
- return errSnapshotStale
- }
- return dl.buffer.setSize(size, dl.db.diskdb, dl.cleans, dl.id)
-}
-
-// size returns the approximate size of cached nodes in the disk layer.
-func (dl *diskLayer) size() common.StorageSize {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- if dl.stale {
- return 0
- }
- return common.StorageSize(dl.buffer.size)
-}
-
-// resetCache releases the memory held by clean cache to prevent memory leak.
-func (dl *diskLayer) resetCache() {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- // Stale disk layer loses the ownership of clean cache.
- if dl.stale {
- return
- }
- if dl.cleans != nil {
- 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)
-}
diff --git a/trie/triedb/pathdb/errors.go b/trie/triedb/pathdb/errors.go
deleted file mode 100644
index 78ee4459fe..0000000000
--- a/trie/triedb/pathdb/errors.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// 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
-
-package pathdb
-
-import (
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-var (
- // errDatabaseReadOnly is returned if the database is opened in read only mode
- // to prevent any mutation.
- errDatabaseReadOnly = errors.New("read only")
-
- // errDatabaseWaitSync is returned if the initial state sync is not completed
- // yet and database is disabled to prevent accessing state.
- errDatabaseWaitSync = errors.New("waiting for sync")
-
- // errSnapshotStale is returned from data accessors if the underlying layer
- // layer had been invalidated due to the chain progressing forward far enough
- // to not maintain the layer's original state.
- errSnapshotStale = errors.New("layer stale")
-
- // errUnexpectedHistory is returned if an unmatched state history is applied
- // to the database for state rollback.
- errUnexpectedHistory = errors.New("unexpected state history")
-
- // errStateUnrecoverable is returned if state is required to be reverted to
- // a destination without associated state history available.
- 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)
-}
diff --git a/trie/triedb/pathdb/history.go b/trie/triedb/pathdb/history.go
deleted file mode 100644
index 6e3f3faaed..0000000000
--- a/trie/triedb/pathdb/history.go
+++ /dev/null
@@ -1,649 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see
-
-package pathdb
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/triestate"
- "golang.org/x/exp/slices"
-)
-
-// State history records the state changes involved in executing a block. The
-// state can be reverted to the previous version by applying the associated
-// history object (state reverse diff). State history objects are kept to
-// guarantee that the system can perform state rollbacks in case of deep reorg.
-//
-// Each state transition will generate a state history object. Note that not
-// every block has a corresponding state history object. If a block performs
-// no state changes whatsoever, no state is created for it. Each state history
-// will have a sequentially increasing number acting as its unique identifier.
-//
-// The state history is written to disk (ancient store) when the corresponding
-// diff layer is merged into the disk layer. At the same time, system can prune
-// the oldest histories according to config.
-//
-// Disk State
-// ^
-// |
-// +------------+ +---------+ +---------+ +---------+
-// | Init State |---->| State 1 |---->| ... |---->| State n |
-// +------------+ +---------+ +---------+ +---------+
-//
-// +-----------+ +------+ +-----------+
-// | History 1 |----> | ... |---->| History n |
-// +-----------+ +------+ +-----------+
-//
-// # Rollback
-//
-// If the system wants to roll back to a previous state n, it needs to ensure
-// all history objects from n+1 up to the current disk layer are existent. The
-// history objects are applied to the state in reverse order, starting from the
-// current disk layer.
-
-const (
- accountIndexSize = common.AddressLength + 13 // The length of encoded account index
- slotIndexSize = common.HashLength + 5 // The length of encoded slot index
- historyMetaSize = 9 + 2*common.HashLength // The length of fixed size part of meta object
-
- stateHistoryVersion = uint8(0) // initial version of state history structure.
-)
-
-// Each state history entry is consisted of five elements:
-//
-// # metadata
-// This object contains a few meta fields, such as the associated state root,
-// block number, version tag and so on. This object may contain an extra
-// accountHash list which means the storage changes belong to these accounts
-// are not complete due to large contract destruction. The incomplete history
-// can not be used for rollback and serving archive state request.
-//
-// # account index
-// This object contains some index information of account. For example, offset
-// and length indicate the location of the data belonging to the account. Besides,
-// storageOffset and storageSlots indicate the storage modification location
-// belonging to the account.
-//
-// The size of each account index is *fixed*, and all indexes are sorted
-// lexicographically. Thus binary search can be performed to quickly locate a
-// specific account.
-//
-// # account data
-// Account data is a concatenated byte stream composed of all account data.
-// The account data can be solved by the offset and length info indicated
-// by corresponding account index.
-//
-// fixed size
-// ^ ^
-// / \
-// +-----------------+-----------------+----------------+-----------------+
-// | Account index 1 | Account index 2 | ... | Account index N |
-// +-----------------+-----------------+----------------+-----------------+
-// |
-// | length
-// offset |----------------+
-// v v
-// +----------------+----------------+----------------+----------------+
-// | Account data 1 | Account data 2 | ... | Account data N |
-// +----------------+----------------+----------------+----------------+
-//
-// # storage index
-// This object is similar with account index. It's also fixed size and contains
-// the location info of storage slot data.
-//
-// # storage data
-// Storage data is a concatenated byte stream composed of all storage slot data.
-// The storage slot data can be solved by the location info indicated by
-// corresponding account index and storage slot index.
-//
-// fixed size
-// ^ ^
-// / \
-// +-----------------+-----------------+----------------+-----------------+
-// | Account index 1 | Account index 2 | ... | Account index N |
-// +-----------------+-----------------+----------------+-----------------+
-// |
-// | storage slots
-// storage offset |-----------------------------------------------------+
-// v v
-// +-----------------+-----------------+-----------------+
-// | storage index 1 | storage index 2 | storage index 3 |
-// +-----------------+-----------------+-----------------+
-// | length
-// offset |-------------+
-// v v
-// +-------------+
-// | slot data 1 |
-// +-------------+
-
-// accountIndex describes the metadata belonging to an account.
-type accountIndex struct {
- address common.Address // The address of account
- length uint8 // The length of account data, size limited by 255
- offset uint32 // The offset of item in account data table
- storageOffset uint32 // The offset of storage index in storage index table
- storageSlots uint32 // The number of mutated storage slots belonging to the account
-}
-
-// encode packs account index into byte stream.
-func (i *accountIndex) encode() []byte {
- var buf [accountIndexSize]byte
- copy(buf[:], i.address.Bytes())
- buf[common.AddressLength] = i.length
- binary.BigEndian.PutUint32(buf[common.AddressLength+1:], i.offset)
- binary.BigEndian.PutUint32(buf[common.AddressLength+5:], i.storageOffset)
- binary.BigEndian.PutUint32(buf[common.AddressLength+9:], i.storageSlots)
- return buf[:]
-}
-
-// decode unpacks account index from byte stream.
-func (i *accountIndex) decode(blob []byte) {
- i.address = common.BytesToAddress(blob[:common.AddressLength])
- i.length = blob[common.AddressLength]
- i.offset = binary.BigEndian.Uint32(blob[common.AddressLength+1:])
- i.storageOffset = binary.BigEndian.Uint32(blob[common.AddressLength+5:])
- i.storageSlots = binary.BigEndian.Uint32(blob[common.AddressLength+9:])
-}
-
-// slotIndex describes the metadata belonging to a storage slot.
-type slotIndex struct {
- hash common.Hash // The hash of slot key
- length uint8 // The length of storage slot, up to 32 bytes defined in protocol
- offset uint32 // The offset of item in storage slot data table
-}
-
-// encode packs slot index into byte stream.
-func (i *slotIndex) encode() []byte {
- var buf [slotIndexSize]byte
- copy(buf[:common.HashLength], i.hash.Bytes())
- buf[common.HashLength] = i.length
- binary.BigEndian.PutUint32(buf[common.HashLength+1:], i.offset)
- return buf[:]
-}
-
-// decode unpack slot index from the byte stream.
-func (i *slotIndex) decode(blob []byte) {
- i.hash = common.BytesToHash(blob[:common.HashLength])
- i.length = blob[common.HashLength]
- i.offset = binary.BigEndian.Uint32(blob[common.HashLength+1:])
-}
-
-// meta describes the meta data of state history object.
-type meta struct {
- version uint8 // version tag of history object
- parent common.Hash // prev-state root before the state transition
- root common.Hash // post-state root after the state transition
- block uint64 // associated block number
- incomplete []common.Address // list of address whose storage set is incomplete
-}
-
-// encode packs the meta object into byte stream.
-func (m *meta) encode() []byte {
- buf := make([]byte, historyMetaSize+len(m.incomplete)*common.AddressLength)
- buf[0] = m.version
- copy(buf[1:1+common.HashLength], m.parent.Bytes())
- copy(buf[1+common.HashLength:1+2*common.HashLength], m.root.Bytes())
- binary.BigEndian.PutUint64(buf[1+2*common.HashLength:historyMetaSize], m.block)
- for i, h := range m.incomplete {
- copy(buf[i*common.AddressLength+historyMetaSize:], h.Bytes())
- }
- return buf[:]
-}
-
-// decode unpacks the meta object from byte stream.
-func (m *meta) decode(blob []byte) error {
- if len(blob) < 1 {
- return fmt.Errorf("no version tag")
- }
- switch blob[0] {
- case stateHistoryVersion:
- if len(blob) < historyMetaSize {
- return fmt.Errorf("invalid state history meta, len: %d", len(blob))
- }
- if (len(blob)-historyMetaSize)%common.AddressLength != 0 {
- return fmt.Errorf("corrupted state history meta, len: %d", len(blob))
- }
- m.version = blob[0]
- m.parent = common.BytesToHash(blob[1 : 1+common.HashLength])
- m.root = common.BytesToHash(blob[1+common.HashLength : 1+2*common.HashLength])
- m.block = binary.BigEndian.Uint64(blob[1+2*common.HashLength : historyMetaSize])
- for pos := historyMetaSize; pos < len(blob); {
- m.incomplete = append(m.incomplete, common.BytesToAddress(blob[pos:pos+common.AddressLength]))
- pos += common.AddressLength
- }
- return nil
- default:
- return fmt.Errorf("unknown version %d", blob[0])
- }
-}
-
-// history represents a set of state changes belong to a block along with
-// the metadata including the state roots involved in the state transition.
-// State history objects in disk are linked with each other by a unique id
-// (8-bytes integer), the oldest state history object can be pruned on demand
-// in order to control the storage size.
-type history struct {
- meta *meta // Meta data of history
- accounts map[common.Address][]byte // Account data keyed by its address hash
- accountList []common.Address // Sorted account hash list
- storages map[common.Address]map[common.Hash][]byte // Storage data keyed by its address hash and slot hash
- storageList map[common.Address][]common.Hash // Sorted slot hash list
-}
-
-// newHistory constructs the state history object with provided state change set.
-func newHistory(root common.Hash, parent common.Hash, block uint64, states *triestate.Set) *history {
- var (
- accountList []common.Address
- storageList = make(map[common.Address][]common.Hash)
- incomplete []common.Address
- )
- for addr := range states.Accounts {
- accountList = append(accountList, addr)
- }
- slices.SortFunc(accountList, common.Address.Cmp)
-
- for addr, slots := range states.Storages {
- slist := make([]common.Hash, 0, len(slots))
- for slotHash := range slots {
- slist = append(slist, slotHash)
- }
- slices.SortFunc(slist, common.Hash.Cmp)
- storageList[addr] = slist
- }
- for addr := range states.Incomplete {
- incomplete = append(incomplete, addr)
- }
- slices.SortFunc(incomplete, common.Address.Cmp)
-
- return &history{
- meta: &meta{
- version: stateHistoryVersion,
- parent: parent,
- root: root,
- block: block,
- incomplete: incomplete,
- },
- accounts: states.Accounts,
- accountList: accountList,
- storages: states.Storages,
- storageList: storageList,
- }
-}
-
-// encode serializes the state history and returns four byte streams represent
-// concatenated account/storage data, account/storage indexes respectively.
-func (h *history) encode() ([]byte, []byte, []byte, []byte) {
- var (
- slotNumber uint32 // the number of processed slots
- accountData []byte // the buffer for concatenated account data
- storageData []byte // the buffer for concatenated storage data
- accountIndexes []byte // the buffer for concatenated account index
- storageIndexes []byte // the buffer for concatenated storage index
- )
- for _, addr := range h.accountList {
- accIndex := accountIndex{
- address: addr,
- length: uint8(len(h.accounts[addr])),
- offset: uint32(len(accountData)),
- }
- slots, exist := h.storages[addr]
- if exist {
- // Encode storage slots in order
- for _, slotHash := range h.storageList[addr] {
- sIndex := slotIndex{
- hash: slotHash,
- length: uint8(len(slots[slotHash])),
- offset: uint32(len(storageData)),
- }
- storageData = append(storageData, slots[slotHash]...)
- storageIndexes = append(storageIndexes, sIndex.encode()...)
- }
- // Fill up the storage meta in account index
- accIndex.storageOffset = slotNumber
- accIndex.storageSlots = uint32(len(slots))
- slotNumber += uint32(len(slots))
- }
- accountData = append(accountData, h.accounts[addr]...)
- accountIndexes = append(accountIndexes, accIndex.encode()...)
- }
- return accountData, storageData, accountIndexes, storageIndexes
-}
-
-// decoder wraps the byte streams for decoding with extra meta fields.
-type decoder struct {
- accountData []byte // the buffer for concatenated account data
- storageData []byte // the buffer for concatenated storage data
- accountIndexes []byte // the buffer for concatenated account index
- storageIndexes []byte // the buffer for concatenated storage index
-
- lastAccount *common.Address // the address of last resolved account
- lastAccountRead uint32 // the read-cursor position of account data
- lastSlotIndexRead uint32 // the read-cursor position of storage slot index
- lastSlotDataRead uint32 // the read-cursor position of storage slot data
-}
-
-// verify validates the provided byte streams for decoding state history. A few
-// checks will be performed to quickly detect data corruption. The byte stream
-// is regarded as corrupted if:
-//
-// - account indexes buffer is empty(empty state set is invalid)
-// - account indexes/storage indexer buffer is not aligned
-//
-// note, these situations are allowed:
-//
-// - empty account data: all accounts were not present
-// - empty storage set: no slots are modified
-func (r *decoder) verify() error {
- if len(r.accountIndexes)%accountIndexSize != 0 || len(r.accountIndexes) == 0 {
- return fmt.Errorf("invalid account index, len: %d", len(r.accountIndexes))
- }
- if len(r.storageIndexes)%slotIndexSize != 0 {
- return fmt.Errorf("invalid storage index, len: %d", len(r.storageIndexes))
- }
- return nil
-}
-
-// readAccount parses the account from the byte stream with specified position.
-func (r *decoder) readAccount(pos int) (accountIndex, []byte, error) {
- // Decode account index from the index byte stream.
- var index accountIndex
- if (pos+1)*accountIndexSize > len(r.accountIndexes) {
- return accountIndex{}, nil, errors.New("account data buffer is corrupted")
- }
- index.decode(r.accountIndexes[pos*accountIndexSize : (pos+1)*accountIndexSize])
-
- // Perform validation before parsing account data, ensure
- // - account is sorted in order in byte stream
- // - account data is strictly encoded with no gap inside
- // - account data is not out-of-slice
- if r.lastAccount != nil { // zero address is possible
- if bytes.Compare(r.lastAccount.Bytes(), index.address.Bytes()) >= 0 {
- return accountIndex{}, nil, errors.New("account is not in order")
- }
- }
- if index.offset != r.lastAccountRead {
- return accountIndex{}, nil, errors.New("account data buffer is gaped")
- }
- last := index.offset + uint32(index.length)
- if uint32(len(r.accountData)) < last {
- return accountIndex{}, nil, errors.New("account data buffer is corrupted")
- }
- data := r.accountData[index.offset:last]
-
- r.lastAccount = &index.address
- r.lastAccountRead = last
-
- return index, data, nil
-}
-
-// readStorage parses the storage slots from the byte stream with specified account.
-func (r *decoder) readStorage(accIndex accountIndex) ([]common.Hash, map[common.Hash][]byte, error) {
- var (
- last common.Hash
- list []common.Hash
- storage = make(map[common.Hash][]byte)
- )
- for j := 0; j < int(accIndex.storageSlots); j++ {
- var (
- index slotIndex
- start = (accIndex.storageOffset + uint32(j)) * uint32(slotIndexSize)
- end = (accIndex.storageOffset + uint32(j+1)) * uint32(slotIndexSize)
- )
- // Perform validation before parsing storage slot data, ensure
- // - slot index is not out-of-slice
- // - slot data is not out-of-slice
- // - slot is sorted in order in byte stream
- // - slot indexes is strictly encoded with no gap inside
- // - slot data is strictly encoded with no gap inside
- if start != r.lastSlotIndexRead {
- return nil, nil, errors.New("storage index buffer is gapped")
- }
- if uint32(len(r.storageIndexes)) < end {
- return nil, nil, errors.New("storage index buffer is corrupted")
- }
- index.decode(r.storageIndexes[start:end])
-
- if bytes.Compare(last.Bytes(), index.hash.Bytes()) >= 0 {
- return nil, nil, errors.New("storage slot is not in order")
- }
- if index.offset != r.lastSlotDataRead {
- return nil, nil, errors.New("storage data buffer is gapped")
- }
- sEnd := index.offset + uint32(index.length)
- if uint32(len(r.storageData)) < sEnd {
- return nil, nil, errors.New("storage data buffer is corrupted")
- }
- storage[index.hash] = r.storageData[r.lastSlotDataRead:sEnd]
- list = append(list, index.hash)
-
- last = index.hash
- r.lastSlotIndexRead = end
- r.lastSlotDataRead = sEnd
- }
- return list, storage, nil
-}
-
-// decode deserializes the account and storage data from the provided byte stream.
-func (h *history) decode(accountData, storageData, accountIndexes, storageIndexes []byte) error {
- var (
- accounts = make(map[common.Address][]byte)
- storages = make(map[common.Address]map[common.Hash][]byte)
- accountList []common.Address
- storageList = make(map[common.Address][]common.Hash)
-
- r = &decoder{
- accountData: accountData,
- storageData: storageData,
- accountIndexes: accountIndexes,
- storageIndexes: storageIndexes,
- }
- )
- if err := r.verify(); err != nil {
- return err
- }
- for i := 0; i < len(accountIndexes)/accountIndexSize; i++ {
- // Resolve account first
- accIndex, accData, err := r.readAccount(i)
- if err != nil {
- return err
- }
- accounts[accIndex.address] = accData
- accountList = append(accountList, accIndex.address)
-
- // Resolve storage slots
- slotList, slotData, err := r.readStorage(accIndex)
- if err != nil {
- return err
- }
- if len(slotList) > 0 {
- storageList[accIndex.address] = slotList
- storages[accIndex.address] = slotData
- }
- }
- h.accounts = accounts
- h.accountList = accountList
- h.storages = storages
- h.storageList = storageList
- return nil
-}
-
-// readHistory reads and decodes the state history object by the given id.
-func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) {
- blob := rawdb.ReadStateHistoryMeta(freezer, id)
- if len(blob) == 0 {
- return nil, fmt.Errorf("state history not found %d", id)
- }
- var m meta
- if err := m.decode(blob); err != nil {
- return nil, err
- }
- var (
- dec = history{meta: &m}
- accountData = rawdb.ReadStateAccountHistory(freezer, id)
- storageData = rawdb.ReadStateStorageHistory(freezer, id)
- accountIndexes = rawdb.ReadStateAccountIndex(freezer, id)
- storageIndexes = rawdb.ReadStateStorageIndex(freezer, id)
- )
- if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
- return nil, err
- }
- return &dec, nil
-}
-
-// writeHistory persists the state history with the provided state set.
-func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error {
- // Short circuit if state set is not available.
- if dl.states == nil {
- return errors.New("state change set is not available")
- }
- var (
- start = time.Now()
- history = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states)
- )
- accountData, storageData, accountIndex, storageIndex := history.encode()
- dataSize := common.StorageSize(len(accountData) + len(storageData))
- indexSize := common.StorageSize(len(accountIndex) + len(storageIndex))
-
- // Write history data into five freezer table respectively.
- rawdb.WriteStateHistory(freezer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData)
-
- historyDataBytesMeter.Mark(int64(dataSize))
- historyIndexBytesMeter.Mark(int64(indexSize))
- historyBuildTimeMeter.UpdateSince(start)
- log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start)))
-
- return nil
-}
-
-// checkHistories retrieves a batch of meta objects with the specified range
-// and performs the callback on each item.
-func checkHistories(freezer *rawdb.ResettableFreezer, start, count uint64, check func(*meta) error) error {
- for count > 0 {
- number := count
- if number > 10000 {
- number = 10000 // split the big read into small chunks
- }
- blobs, err := rawdb.ReadStateHistoryMetaList(freezer, start, number)
- if err != nil {
- return err
- }
- for _, blob := range blobs {
- var dec meta
- if err := dec.decode(blob); err != nil {
- return err
- }
- if err := check(&dec); err != nil {
- return err
- }
- }
- count -= uint64(len(blobs))
- start += uint64(len(blobs))
- }
- return nil
-}
-
-// truncateFromHead removes the extra state histories from the head with the given
-// parameters. It returns the number of items removed from the head.
-func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead uint64) (int, error) {
- ohead, err := freezer.Ancients()
- if err != nil {
- return 0, err
- }
- otail, err := freezer.Tail()
- if err != nil {
- return 0, err
- }
- // Ensure that the truncation target falls within the specified range.
- if ohead < nhead || nhead < otail {
- return 0, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", otail, ohead, nhead)
- }
- // Short circuit if nothing to truncate.
- if ohead == nhead {
- return 0, nil
- }
- // Load the meta objects in range [nhead+1, ohead]
- blobs, err := rawdb.ReadStateHistoryMetaList(freezer, nhead+1, ohead-nhead)
- if err != nil {
- return 0, err
- }
- batch := db.NewBatch()
- for _, blob := range blobs {
- var m meta
- if err := m.decode(blob); err != nil {
- return 0, err
- }
- rawdb.DeleteStateID(batch, m.root)
- }
- if err := batch.Write(); err != nil {
- return 0, err
- }
- ohead, err = freezer.TruncateHead(nhead)
- if err != nil {
- return 0, err
- }
- return int(ohead - nhead), nil
-}
-
-// truncateFromTail removes the extra state histories from the tail with the given
-// parameters. It returns the number of items removed from the tail.
-func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail uint64) (int, error) {
- ohead, err := freezer.Ancients()
- if err != nil {
- return 0, err
- }
- otail, err := freezer.Tail()
- if err != nil {
- return 0, err
- }
- // Ensure that the truncation target falls within the specified range.
- if otail > ntail || ntail > ohead {
- return 0, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", otail, ohead, ntail)
- }
- // Short circuit if nothing to truncate.
- if otail == ntail {
- return 0, nil
- }
- // Load the meta objects in range [otail+1, ntail]
- blobs, err := rawdb.ReadStateHistoryMetaList(freezer, otail+1, ntail-otail)
- if err != nil {
- return 0, err
- }
- batch := db.NewBatch()
- for _, blob := range blobs {
- var m meta
- if err := m.decode(blob); err != nil {
- return 0, err
- }
- rawdb.DeleteStateID(batch, m.root)
- }
- if err := batch.Write(); err != nil {
- return 0, err
- }
- otail, err = freezer.TruncateTail(ntail)
- if err != nil {
- return 0, err
- }
- return int(ntail - otail), nil
-}
diff --git a/trie/triedb/pathdb/history_test.go b/trie/triedb/pathdb/history_test.go
deleted file mode 100644
index a3257441de..0000000000
--- a/trie/triedb/pathdb/history_test.go
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see
-
-package pathdb
-
-import (
- "bytes"
- "fmt"
- "reflect"
- "testing"
-
- "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/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/testutil"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-// randomStateSet generates a random state change set.
-func randomStateSet(n int) *triestate.Set {
- var (
- accounts = make(map[common.Address][]byte)
- storages = make(map[common.Address]map[common.Hash][]byte)
- )
- for i := 0; i < n; i++ {
- addr := testutil.RandomAddress()
- storages[addr] = make(map[common.Hash][]byte)
- for j := 0; j < 3; j++ {
- v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32)))
- storages[addr][testutil.RandomHash()] = v
- }
- account := generateAccount(types.EmptyRootHash)
- accounts[addr] = types.SlimAccountRLP(account)
- }
- return triestate.New(accounts, storages, nil)
-}
-
-func makeHistory() *history {
- return newHistory(testutil.RandomHash(), types.EmptyRootHash, 0, randomStateSet(3))
-}
-
-func makeHistories(n int) []*history {
- var (
- parent = types.EmptyRootHash
- result []*history
- )
- for i := 0; i < n; i++ {
- root := testutil.RandomHash()
- h := newHistory(root, parent, uint64(i), randomStateSet(3))
- parent = root
- result = append(result, h)
- }
- return result
-}
-
-func TestEncodeDecodeHistory(t *testing.T) {
- var (
- m meta
- dec history
- obj = makeHistory()
- )
- // check if meta data can be correctly encode/decode
- blob := obj.meta.encode()
- if err := m.decode(blob); err != nil {
- t.Fatalf("Failed to decode %v", err)
- }
- if !reflect.DeepEqual(&m, obj.meta) {
- t.Fatal("meta is mismatched")
- }
-
- // check if account/storage data can be correctly encode/decode
- accountData, storageData, accountIndexes, storageIndexes := obj.encode()
- if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil {
- t.Fatalf("Failed to decode, err: %v", err)
- }
- if !compareSet(dec.accounts, obj.accounts) {
- t.Fatal("account data is mismatched")
- }
- if !compareStorages(dec.storages, obj.storages) {
- t.Fatal("storage data is mismatched")
- }
- if !compareList(dec.accountList, obj.accountList) {
- t.Fatal("account list is mismatched")
- }
- if !compareStorageList(dec.storageList, obj.storageList) {
- t.Fatal("storage list is mismatched")
- }
-}
-
-func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, id uint64, root common.Hash, exist bool) {
- blob := rawdb.ReadStateHistoryMeta(freezer, id)
- if exist && len(blob) == 0 {
- t.Fatalf("Failed to load trie history, %d", id)
- }
- if !exist && len(blob) != 0 {
- t.Fatalf("Unexpected trie history, %d", id)
- }
- if exist && rawdb.ReadStateID(db, root) == nil {
- t.Fatalf("Root->ID mapping is not found, %d", id)
- }
- if !exist && rawdb.ReadStateID(db, root) != nil {
- t.Fatalf("Unexpected root->ID mapping, %d", id)
- }
-}
-
-func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, from, to uint64, roots []common.Hash, exist bool) {
- for i, j := from, 0; i <= to; i, j = i+1, j+1 {
- checkHistory(t, db, freezer, i, roots[j], exist)
- }
-}
-
-func TestTruncateHeadHistory(t *testing.T) {
- var (
- roots []common.Hash
- hs = makeHistories(10)
- db = rawdb.NewMemoryDatabase()
- freezer, _ = openFreezer(t.TempDir(), false)
- )
- defer freezer.Close()
-
- for i := 0; i < len(hs); i++ {
- accountData, storageData, accountIndex, storageIndex := hs[i].encode()
- rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData)
- rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1))
- roots = append(roots, hs[i].meta.root)
- }
- for size := len(hs); size > 0; size-- {
- pruned, err := truncateFromHead(db, freezer, uint64(size-1))
- if err != nil {
- t.Fatalf("Failed to truncate from head %v", err)
- }
- if pruned != 1 {
- t.Error("Unexpected pruned items", "want", 1, "got", pruned)
- }
- checkHistoriesInRange(t, db, freezer, uint64(size), uint64(10), roots[size-1:], false)
- checkHistoriesInRange(t, db, freezer, uint64(1), uint64(size-1), roots[:size-1], true)
- }
-}
-
-func TestTruncateTailHistory(t *testing.T) {
- var (
- roots []common.Hash
- hs = makeHistories(10)
- db = rawdb.NewMemoryDatabase()
- freezer, _ = openFreezer(t.TempDir(), false)
- )
- defer freezer.Close()
-
- for i := 0; i < len(hs); i++ {
- accountData, storageData, accountIndex, storageIndex := hs[i].encode()
- rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData)
- rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1))
- roots = append(roots, hs[i].meta.root)
- }
- for newTail := 1; newTail < len(hs); newTail++ {
- pruned, _ := truncateFromTail(db, freezer, uint64(newTail))
- if pruned != 1 {
- t.Error("Unexpected pruned items", "want", 1, "got", pruned)
- }
- checkHistoriesInRange(t, db, freezer, uint64(1), uint64(newTail), roots[:newTail], false)
- checkHistoriesInRange(t, db, freezer, uint64(newTail+1), uint64(10), roots[newTail:], true)
- }
-}
-
-func TestTruncateTailHistories(t *testing.T) {
- var cases = []struct {
- limit uint64
- expPruned int
- maxPruned uint64
- minUnpruned uint64
- empty bool
- }{
- {
- 1, 9, 9, 10, false,
- },
- {
- 0, 10, 10, 0 /* no meaning */, true,
- },
- {
- 10, 0, 0, 1, false,
- },
- }
- for i, c := range cases {
- var (
- roots []common.Hash
- hs = makeHistories(10)
- db = rawdb.NewMemoryDatabase()
- freezer, _ = openFreezer(t.TempDir()+fmt.Sprintf("%d", i), false)
- )
- defer freezer.Close()
-
- for i := 0; i < len(hs); i++ {
- accountData, storageData, accountIndex, storageIndex := hs[i].encode()
- rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData)
- rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1))
- roots = append(roots, hs[i].meta.root)
- }
- pruned, _ := truncateFromTail(db, freezer, uint64(10)-c.limit)
- if pruned != c.expPruned {
- t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned)
- }
- if c.empty {
- checkHistoriesInRange(t, db, freezer, uint64(1), uint64(10), roots, false)
- } else {
- tail := 10 - int(c.limit)
- checkHistoriesInRange(t, db, freezer, uint64(1), c.maxPruned, roots[:tail], false)
- checkHistoriesInRange(t, db, freezer, c.minUnpruned, uint64(10), roots[tail:], true)
- }
- }
-}
-
-func TestTruncateOutOfRange(t *testing.T) {
- var (
- hs = makeHistories(10)
- db = rawdb.NewMemoryDatabase()
- freezer, _ = openFreezer(t.TempDir(), false)
- )
- defer freezer.Close()
-
- for i := 0; i < len(hs); i++ {
- accountData, storageData, accountIndex, storageIndex := hs[i].encode()
- rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData)
- rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1))
- }
- truncateFromTail(db, freezer, uint64(len(hs)/2))
-
- // Ensure of-out-range truncations are rejected correctly.
- head, _ := freezer.Ancients()
- tail, _ := freezer.Tail()
-
- cases := []struct {
- mode int
- target uint64
- expErr error
- }{
- {0, head, nil}, // nothing to delete
- {0, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)},
- {0, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)},
- {1, tail, nil}, // nothing to delete
- {1, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)},
- {1, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)},
- }
- for _, c := range cases {
- var gotErr error
- if c.mode == 0 {
- _, gotErr = truncateFromHead(db, freezer, c.target)
- } else {
- _, gotErr = truncateFromTail(db, freezer, c.target)
- }
- if !reflect.DeepEqual(gotErr, c.expErr) {
- t.Errorf("Unexpected error, want: %v, got: %v", c.expErr, gotErr)
- }
- }
-}
-
-// openFreezer initializes the freezer instance for storing state histories.
-func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) {
- return rawdb.NewStateFreezer(datadir, readOnly)
-}
-
-func compareSet[k comparable](a, b map[k][]byte) bool {
- if len(a) != len(b) {
- return false
- }
- for key, valA := range a {
- valB, ok := b[key]
- if !ok {
- return false
- }
- if !bytes.Equal(valA, valB) {
- return false
- }
- }
- return true
-}
-
-func compareList[k comparable](a, b []k) bool {
- if len(a) != len(b) {
- return false
- }
- for i := 0; i < len(a); i++ {
- if a[i] != b[i] {
- return false
- }
- }
- return true
-}
-
-func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool {
- if len(a) != len(b) {
- return false
- }
- for h, subA := range a {
- subB, ok := b[h]
- if !ok {
- return false
- }
- if !compareSet(subA, subB) {
- return false
- }
- }
- return true
-}
-
-func compareStorageList(a, b map[common.Address][]common.Hash) bool {
- if len(a) != len(b) {
- return false
- }
- for h, la := range a {
- lb, ok := b[h]
- if !ok {
- return false
- }
- if !compareList(la, lb) {
- return false
- }
- }
- return true
-}
diff --git a/trie/triedb/pathdb/journal.go b/trie/triedb/pathdb/journal.go
deleted file mode 100644
index ac770763e3..0000000000
--- a/trie/triedb/pathdb/journal.go
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-var (
- errMissJournal = errors.New("journal not found")
- errMissVersion = errors.New("version not found")
- errUnexpectedVersion = errors.New("unexpected journal version")
- errMissDiskRoot = errors.New("disk layer root not found")
- errUnmatchedJournal = errors.New("unmatched journal")
-)
-
-const journalVersion uint64 = 0
-
-// journalNode represents a trie node persisted in the journal.
-type journalNode struct {
- Path []byte // Path of the node in the trie
- Blob []byte // RLP-encoded trie node blob, nil means the node is deleted
-}
-
-// journalNodes represents a list trie nodes belong to a single account
-// or the main account trie.
-type journalNodes struct {
- Owner common.Hash
- Nodes []journalNode
-}
-
-// journalAccounts represents a list accounts belong to the layer.
-type journalAccounts struct {
- Addresses []common.Address
- Accounts [][]byte
-}
-
-// journalStorage represents a list of storage slots belong to an account.
-type journalStorage struct {
- Incomplete bool
- Account common.Address
- Hashes []common.Hash
- Slots [][]byte
-}
-
-// loadJournal tries to parse the layer journal from the disk.
-func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
- journal := rawdb.ReadTrieJournal(db.diskdb)
- if len(journal) == 0 {
- return nil, errMissJournal
- }
- r := rlp.NewStream(bytes.NewReader(journal), 0)
-
- // Firstly, resolve the first element as the journal version
- version, err := r.Uint64()
- if err != nil {
- return nil, errMissVersion
- }
- if version != journalVersion {
- return nil, fmt.Errorf("%w want %d got %d", errUnexpectedVersion, journalVersion, version)
- }
- // Secondly, resolve the disk layer root, ensure it's continuous
- // with disk layer. Note now we can ensure it's the layer journal
- // correct version, so we expect everything can be resolved properly.
- var root common.Hash
- if err := r.Decode(&root); err != nil {
- return nil, errMissDiskRoot
- }
- // The journal is not matched with persistent state, discard them.
- // It can happen that geth crashes without persisting the journal.
- if !bytes.Equal(root.Bytes(), diskRoot.Bytes()) {
- return nil, fmt.Errorf("%w want %x got %x", errUnmatchedJournal, root, diskRoot)
- }
- // Load the disk layer from the journal
- base, err := db.loadDiskLayer(r)
- if err != nil {
- return nil, err
- }
- // Load all the diff layers from the journal
- head, err := db.loadDiffLayer(base, r)
- if err != nil {
- return nil, err
- }
- log.Debug("Loaded layer journal", "diskroot", diskRoot, "diffhead", head.rootHash())
- return head, nil
-}
-
-// 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.
- _, root := rawdb.ReadAccountTrieNode(db.diskdb, nil)
- root = types.TrieRootHash(root)
-
- // Load the layers by resolving the journal
- head, err := db.loadJournal(root)
- if err == nil {
- return head
- }
- // journal is not matched(or missing) with the persistent state, discard
- // it. Display log for discarding journal, but try to avoid showing
- // useless information when the db is created from scratch.
- if !(root == types.EmptyRootHash && errors.Is(err, errMissJournal)) {
- log.Info("Failed to load journal, discard it", "err", err)
- }
- // Return single layer with persistent state.
- return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newNodeBuffer(db.bufferSize, nil, 0))
-}
-
-// loadDiskLayer reads the binary blob from the layer journal, reconstructing
-// a new disk layer on it.
-func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) {
- // Resolve disk layer root
- var root common.Hash
- if err := r.Decode(&root); err != nil {
- return nil, fmt.Errorf("load disk root: %v", err)
- }
- // Resolve the state id of disk layer, it can be different
- // with the persistent id tracked in disk, the id distance
- // is the number of transitions aggregated in disk layer.
- var id uint64
- if err := r.Decode(&id); err != nil {
- return nil, fmt.Errorf("load state id: %v", err)
- }
- stored := rawdb.ReadPersistentStateID(db.diskdb)
- if stored > id {
- return nil, fmt.Errorf("invalid state id: stored %d resolved %d", stored, id)
- }
- // Resolve nodes cached in node buffer
- var encoded []journalNodes
- if err := r.Decode(&encoded); err != nil {
- return nil, fmt.Errorf("load disk nodes: %v", err)
- }
- nodes := make(map[common.Hash]map[string]*trienode.Node)
- for _, entry := range encoded {
- subset := make(map[string]*trienode.Node)
- for _, n := range entry.Nodes {
- if len(n.Blob) > 0 {
- subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
- } else {
- subset[string(n.Path)] = trienode.NewDeleted()
- }
- }
- nodes[entry.Owner] = subset
- }
- // Calculate the internal state transitions by id difference.
- base := newDiskLayer(root, id, db, nil, newNodeBuffer(db.bufferSize, nodes, id-stored))
- return base, nil
-}
-
-// loadDiffLayer reads the next sections of a layer journal, reconstructing a new
-// diff and verifying that it can be linked to the requested parent.
-func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) {
- // Read the next diff journal entry
- var root common.Hash
- if err := r.Decode(&root); err != nil {
- // The first read may fail with EOF, marking the end of the journal
- if err == io.EOF {
- return parent, nil
- }
- return nil, fmt.Errorf("load diff root: %v", err)
- }
- var block uint64
- if err := r.Decode(&block); err != nil {
- return nil, fmt.Errorf("load block number: %v", err)
- }
- // Read in-memory trie nodes from journal
- var encoded []journalNodes
- if err := r.Decode(&encoded); err != nil {
- return nil, fmt.Errorf("load diff nodes: %v", err)
- }
- nodes := make(map[common.Hash]map[string]*trienode.Node)
- for _, entry := range encoded {
- subset := make(map[string]*trienode.Node)
- for _, n := range entry.Nodes {
- if len(n.Blob) > 0 {
- subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
- } else {
- subset[string(n.Path)] = trienode.NewDeleted()
- }
- }
- nodes[entry.Owner] = subset
- }
- // Read state changes from journal
- var (
- jaccounts journalAccounts
- jstorages []journalStorage
- accounts = make(map[common.Address][]byte)
- storages = make(map[common.Address]map[common.Hash][]byte)
- incomplete = make(map[common.Address]struct{})
- )
- if err := r.Decode(&jaccounts); err != nil {
- return nil, fmt.Errorf("load diff accounts: %v", err)
- }
- for i, addr := range jaccounts.Addresses {
- accounts[addr] = jaccounts.Accounts[i]
- }
- if err := r.Decode(&jstorages); err != nil {
- return nil, fmt.Errorf("load diff storages: %v", err)
- }
- for _, entry := range jstorages {
- set := make(map[common.Hash][]byte)
- for i, h := range entry.Hashes {
- if len(entry.Slots[i]) > 0 {
- set[h] = entry.Slots[i]
- } else {
- set[h] = nil
- }
- }
- if entry.Incomplete {
- incomplete[entry.Account] = struct{}{}
- }
- storages[entry.Account] = set
- }
- return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages, incomplete)), r)
-}
-
-// journal implements the layer interface, marshaling the un-flushed trie nodes
-// along with layer meta data into provided byte buffer.
-func (dl *diskLayer) journal(w io.Writer) error {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- // Ensure the layer didn't get stale
- if dl.stale {
- return errSnapshotStale
- }
- // Step one, write the disk root into the journal.
- if err := rlp.Encode(w, dl.root); err != nil {
- return err
- }
- // Step two, write the corresponding state id into the journal
- if err := rlp.Encode(w, dl.id); err != nil {
- return err
- }
- // Step three, write all unwritten nodes into the journal
- nodes := make([]journalNodes, 0, len(dl.buffer.nodes))
- for owner, subset := range dl.buffer.nodes {
- entry := journalNodes{Owner: owner}
- for path, node := range subset {
- entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob})
- }
- nodes = append(nodes, entry)
- }
- if err := rlp.Encode(w, nodes); err != nil {
- return err
- }
- log.Debug("Journaled pathdb disk layer", "root", dl.root, "nodes", len(dl.buffer.nodes))
- return nil
-}
-
-// journal implements the layer interface, writing the memory layer contents
-// into a buffer to be stored in the database as the layer journal.
-func (dl *diffLayer) journal(w io.Writer) error {
- dl.lock.RLock()
- defer dl.lock.RUnlock()
-
- // journal the parent first
- if err := dl.parent.journal(w); err != nil {
- return err
- }
- // Everything below was journaled, persist this layer too
- if err := rlp.Encode(w, dl.root); err != nil {
- return err
- }
- if err := rlp.Encode(w, dl.block); err != nil {
- return err
- }
- // Write the accumulated trie nodes into buffer
- nodes := make([]journalNodes, 0, len(dl.nodes))
- for owner, subset := range dl.nodes {
- entry := journalNodes{Owner: owner}
- for path, node := range subset {
- entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob})
- }
- nodes = append(nodes, entry)
- }
- if err := rlp.Encode(w, nodes); err != nil {
- return err
- }
- // Write the accumulated state changes into buffer
- var jacct journalAccounts
- for addr, account := range dl.states.Accounts {
- jacct.Addresses = append(jacct.Addresses, addr)
- jacct.Accounts = append(jacct.Accounts, account)
- }
- if err := rlp.Encode(w, jacct); err != nil {
- return err
- }
- storage := make([]journalStorage, 0, len(dl.states.Storages))
- for addr, slots := range dl.states.Storages {
- entry := journalStorage{Account: addr}
- if _, ok := dl.states.Incomplete[addr]; ok {
- entry.Incomplete = true
- }
- for slotHash, slot := range slots {
- entry.Hashes = append(entry.Hashes, slotHash)
- entry.Slots = append(entry.Slots, slot)
- }
- storage = append(storage, entry)
- }
- if err := rlp.Encode(w, storage); err != nil {
- return err
- }
- log.Debug("Journaled pathdb diff layer", "root", dl.root, "parent", dl.parent.rootHash(), "id", dl.stateID(), "block", dl.block, "nodes", len(dl.nodes))
- return nil
-}
-
-// 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
-// flattening everything down (bad for reorgs). And this function will mark the
-// database as read-only to prevent all following mutation to disk.
-func (db *Database) Journal(root common.Hash) error {
- // Retrieve the head layer to journal from.
- l := db.tree.get(root)
- if l == nil {
- return fmt.Errorf("triedb layer [%#x] missing", root)
- }
- disk := db.tree.bottom()
- if l, ok := l.(*diffLayer); ok {
- log.Info("Persisting dirty state to disk", "head", l.block, "root", root, "layers", l.id-disk.id+disk.buffer.layers)
- } else { // disk layer only on noop runs (likely) or deep reorgs (unlikely)
- log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers)
- }
- start := time.Now()
-
- // Run the journaling
- db.lock.Lock()
- defer db.lock.Unlock()
-
- // Short circuit if the database is in read only mode.
- if db.readOnly {
- return errDatabaseReadOnly
- }
- // Firstly write out the metadata of journal
- journal := new(bytes.Buffer)
- if err := rlp.Encode(journal, journalVersion); err != nil {
- 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
- // on top are continuous with disk.
- if err := rlp.Encode(journal, diskroot); err != nil {
- return err
- }
- // Finally write out the journal of each layer in reverse order.
- if err := l.journal(journal); err != nil {
- return err
- }
- // Store the journal into the database and return
- rawdb.WriteTrieJournal(db.diskdb, journal.Bytes())
-
- // Set the db in read only mode to reject all following mutations
- db.readOnly = true
- log.Info("Persisted dirty state to disk", "size", common.StorageSize(journal.Len()), "elapsed", common.PrettyDuration(time.Since(start)))
- return nil
-}
diff --git a/trie/triedb/pathdb/layertree.go b/trie/triedb/pathdb/layertree.go
deleted file mode 100644
index d314779910..0000000000
--- a/trie/triedb/pathdb/layertree.go
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see
-
-package pathdb
-
-import (
- "errors"
- "fmt"
- "sync"
-
- "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/trie/triestate"
-)
-
-// layerTree is a group of state layers identified by the state root.
-// This structure defines a few basic operations for manipulating
-// state layers linked with each other in a tree structure. It's
-// thread-safe to use. However, callers need to ensure the thread-safety
-// of the referenced layer by themselves.
-type layerTree struct {
- lock sync.RWMutex
- layers map[common.Hash]layer
-}
-
-// newLayerTree constructs the layerTree with the given head layer.
-func newLayerTree(head layer) *layerTree {
- tree := new(layerTree)
- tree.reset(head)
- return tree
-}
-
-// reset initializes the layerTree by the given head layer.
-// All the ancestors will be iterated out and linked in the tree.
-func (tree *layerTree) reset(head layer) {
- tree.lock.Lock()
- defer tree.lock.Unlock()
-
- var layers = make(map[common.Hash]layer)
- for head != nil {
- layers[head.rootHash()] = head
- head = head.parentLayer()
- }
- tree.layers = layers
-}
-
-// get retrieves a layer belonging to the given state root.
-func (tree *layerTree) get(root common.Hash) layer {
- tree.lock.RLock()
- defer tree.lock.RUnlock()
-
- return tree.layers[types.TrieRootHash(root)]
-}
-
-// forEach iterates the stored layers inside and applies the
-// given callback on them.
-func (tree *layerTree) forEach(onLayer func(layer)) {
- tree.lock.RLock()
- defer tree.lock.RUnlock()
-
- for _, layer := range tree.layers {
- onLayer(layer)
- }
-}
-
-// len returns the number of layers cached.
-func (tree *layerTree) len() int {
- tree.lock.RLock()
- defer tree.lock.RUnlock()
-
- return len(tree.layers)
-}
-
-// add inserts a new layer into the tree if it can be linked to an existing old parent.
-func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error {
- // Reject noop updates to avoid self-loops. This is a special case that can
- // happen for clique networks and proof-of-stake networks where empty blocks
- // don't modify the state (0 block subsidy).
- //
- // 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")
- }
- parent := tree.get(parentRoot)
- if parent == nil {
- return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot)
- }
- l := parent.update(root, parent.stateID()+1, block, nodes.Flatten(), states)
-
- tree.lock.Lock()
- tree.layers[l.rootHash()] = l
- tree.lock.Unlock()
- return nil
-}
-
-// cap traverses downwards the diff tree until the number of allowed diff layers
-// 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)
- }
- diff, ok := l.(*diffLayer)
- if !ok {
- return fmt.Errorf("triedb layer [%#x] is disk layer", root)
- }
- tree.lock.Lock()
- defer tree.lock.Unlock()
-
- // If full commit was requested, flatten the diffs and merge onto disk
- if layers == 0 {
- base, err := diff.persist(true)
- if err != nil {
- return err
- }
- // Replace the entire layer tree with the flat base
- tree.layers = map[common.Hash]layer{base.rootHash(): base}
- return nil
- }
- // Dive until we run out of layers or reach the persistent database
- for i := 0; i < layers-1; i++ {
- // If we still have diff layers below, continue down
- if parent, ok := diff.parentLayer().(*diffLayer); ok {
- diff = parent
- } else {
- // Diff stack too shallow, return without modifications
- return nil
- }
- }
- // We're out of layers, flatten anything below, stopping if it's the disk or if
- // the memory limit is not yet exceeded.
- switch parent := diff.parentLayer().(type) {
- case *diskLayer:
- return nil
-
- case *diffLayer:
- // Hold the lock to prevent any read operations until the new
- // parent is linked correctly.
- diff.lock.Lock()
-
- base, err := parent.persist(false)
- if err != nil {
- diff.lock.Unlock()
- return err
- }
- tree.layers[base.rootHash()] = base
- diff.parent = base
-
- diff.lock.Unlock()
-
- default:
- panic(fmt.Sprintf("unknown data layer in triedb: %T", parent))
- }
- // Remove any layer that is stale or links into a stale layer
- children := make(map[common.Hash][]common.Hash)
- for root, layer := range tree.layers {
- if dl, ok := layer.(*diffLayer); ok {
- parent := dl.parentLayer().rootHash()
- children[parent] = append(children[parent], root)
- }
- }
- var remove func(root common.Hash)
- remove = func(root common.Hash) {
- delete(tree.layers, root)
- for _, child := range children[root] {
- remove(child)
- }
- delete(children, root)
- }
- for root, layer := range tree.layers {
- if dl, ok := layer.(*diskLayer); ok && dl.isStale() {
- remove(root)
- }
- }
- return nil
-}
-
-// bottom returns the bottom-most disk layer in this tree.
-func (tree *layerTree) bottom() *diskLayer {
- tree.lock.RLock()
- defer tree.lock.RUnlock()
-
- if len(tree.layers) == 0 {
- return nil // Shouldn't happen, empty tree
- }
- // pick a random one as the entry point
- var current layer
- for _, layer := range tree.layers {
- current = layer
- break
- }
- for current.parentLayer() != nil {
- current = current.parentLayer()
- }
- return current.(*diskLayer)
-}
diff --git a/trie/triedb/pathdb/metrics.go b/trie/triedb/pathdb/metrics.go
deleted file mode 100644
index 9e2b1dcbf5..0000000000
--- a/trie/triedb/pathdb/metrics.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see
-
-package pathdb
-
-import "github.com/ethereum/go-ethereum/metrics"
-
-var (
- cleanHitMeter = metrics.NewRegisteredMeter("pathdb/clean/hit", nil)
- cleanMissMeter = metrics.NewRegisteredMeter("pathdb/clean/miss", nil)
- cleanReadMeter = metrics.NewRegisteredMeter("pathdb/clean/read", nil)
- cleanWriteMeter = metrics.NewRegisteredMeter("pathdb/clean/write", nil)
-
- dirtyHitMeter = metrics.NewRegisteredMeter("pathdb/dirty/hit", nil)
- dirtyMissMeter = metrics.NewRegisteredMeter("pathdb/dirty/miss", nil)
- dirtyReadMeter = metrics.NewRegisteredMeter("pathdb/dirty/read", nil)
- dirtyWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/write", nil)
- dirtyNodeHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/depth", nil, metrics.NewExpDecaySample(1028, 0.015))
-
- cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil)
- dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil)
- diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil)
-
- commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil)
- commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil)
- commitBytesMeter = metrics.NewRegisteredMeter("pathdb/commit/bytes", nil)
-
- gcNodesMeter = metrics.NewRegisteredMeter("pathdb/gc/nodes", nil)
- gcBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/bytes", nil)
-
- diffLayerBytesMeter = metrics.NewRegisteredMeter("pathdb/diff/bytes", nil)
- diffLayerNodesMeter = metrics.NewRegisteredMeter("pathdb/diff/nodes", nil)
-
- historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil)
- historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil)
- historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil)
-)
diff --git a/trie/triedb/pathdb/nodebuffer.go b/trie/triedb/pathdb/nodebuffer.go
deleted file mode 100644
index 4a7d328b9a..0000000000
--- a/trie/triedb/pathdb/nodebuffer.go
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "fmt"
- "time"
-
- "github.com/VictoriaMetrics/fastcache"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "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
-// write. The content of the nodebuffer must be checked before diving into
-// disk (since it basically is not-yet-written data).
-type nodebuffer struct {
- layers uint64 // The number of diff layers aggregated inside
- size uint64 // The size of aggregated writes
- 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
-}
-
-// newNodeBuffer initializes the node buffer with the provided nodes.
-func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer {
- if nodes == nil {
- nodes = make(map[common.Hash]map[string]*trienode.Node)
- }
- var size uint64
- for _, subset := range nodes {
- for path, n := range subset {
- size += uint64(len(n.Blob) + len(path))
- }
- }
- return &nodebuffer{
- layers: layers,
- nodes: nodes,
- size: size,
- limit: uint64(limit),
- }
-}
-
-// node retrieves the trie node with given node info.
-func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*trienode.Node, error) {
- subset, ok := b.nodes[owner]
- if !ok {
- return nil, nil
- }
- n, ok := subset[string(path)]
- if !ok {
- return nil, nil
- }
- if n.Hash != hash {
- 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
-// 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
-// copy.
-func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer {
- var (
- delta int64
- overwrite int64
- overwriteSize int64
- )
- for owner, subset := range nodes {
- current, exist := b.nodes[owner]
- if !exist {
- // Allocate a new map for the subset instead of claiming it directly
- // from the passed map to avoid potential concurrent map read/write.
- // The nodes belong to original diff layer are still accessible even
- // after merging, thus the ownership of nodes map should still belong
- // to original layer and any mutation on it should be prevented.
- current = make(map[string]*trienode.Node)
- for path, n := range subset {
- current[path] = n
- delta += int64(len(n.Blob) + len(path))
- }
- b.nodes[owner] = current
- continue
- }
- for path, n := range subset {
- if orig, exist := current[path]; !exist {
- delta += int64(len(n.Blob) + len(path))
- } else {
- delta += int64(len(n.Blob) - len(orig.Blob))
- overwrite++
- overwriteSize += int64(len(orig.Blob) + len(path))
- }
- current[path] = n
- }
- b.nodes[owner] = current
- }
- b.updateSize(delta)
- b.layers++
- gcNodesMeter.Mark(overwrite)
- gcBytesMeter.Mark(overwriteSize)
- return b
-}
-
-// 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
-// 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 {
- // Short circuit if no embedded state transition to revert.
- if b.layers == 0 {
- return errStateUnrecoverable
- }
- b.layers--
-
- // Reset the entire buffer if only a single transition left.
- if b.layers == 0 {
- b.reset()
- return nil
- }
- var delta int64
- for owner, subset := range nodes {
- current, ok := b.nodes[owner]
- if !ok {
- panic(fmt.Sprintf("non-existent subset (%x)", owner))
- }
- for path, n := range subset {
- orig, ok := current[path]
- if !ok {
- // There is a special case in MPT that one child is removed from
- // a fullNode which only has two children, and then a new child
- // with different position is immediately inserted into the fullNode.
- // In this case, the clean child of the fullNode will also be
- // marked as dirty because of node collapse and expansion.
- //
- // In case of database rollback, don't panic if this "clean"
- // node occurs which is not present in buffer.
- var nhash common.Hash
- if owner == (common.Hash{}) {
- _, nhash = rawdb.ReadAccountTrieNode(db, []byte(path))
- } else {
- _, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
- }
- // Ignore the clean node in the case described above.
- if nhash == n.Hash {
- continue
- }
- panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
- }
- current[path] = n
- delta += int64(len(n.Blob)) - int64(len(orig.Blob))
- }
- }
- b.updateSize(delta)
- return nil
-}
-
-// updateSize updates the total cache size by the given delta.
-func (b *nodebuffer) updateSize(delta int64) {
- size := int64(b.size) + delta
- if size >= 0 {
- b.size = uint64(size)
- return
- }
- s := b.size
- b.size = 0
- log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta))
-}
-
-// reset cleans up the disk cache.
-func (b *nodebuffer) reset() {
- b.layers = 0
- b.size = 0
- b.nodes = make(map[common.Hash]map[string]*trienode.Node)
-}
-
-// empty returns an indicator if nodebuffer contains any state transition inside.
-func (b *nodebuffer) empty() bool {
- return b.layers == 0
-}
-
-// setSize sets the buffer size to the provided number, and invokes a flush
-// operation if the current memory usage exceeds the new limit.
-func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error {
- b.limit = uint64(size)
- return b.flush(db, clean, id, false)
-}
-
-// flush persists the in-memory dirty trie node into the disk if the configured
-// memory threshold is reached. Note, all data must be written atomically.
-func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error {
- if b.size <= b.limit && !force {
- return nil
- }
- // Ensure the target state id is aligned with the internal counter.
- head := rawdb.ReadPersistentStateID(db)
- if head+b.layers != id {
- return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
- }
- var (
- start = time.Now()
- batch = db.NewBatchWithSize(int(b.size))
- )
- nodes := writeNodes(batch, b.nodes, clean)
- rawdb.WritePersistentStateID(batch, id)
-
- // Flush all mutations in a single batch
- size := batch.ValueSize()
- if err := batch.Write(); err != nil {
- return err
- }
- commitBytesMeter.Mark(int64(size))
- commitNodesMeter.Mark(int64(nodes))
- commitTimeTimer.UpdateSince(start)
- log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
- b.reset()
- return nil
-}
-
-// writeNodes writes the trie nodes into the provided database batch.
-// Note this function will also inject all the newly written nodes
-// into clean cache.
-func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) {
- for owner, subset := range nodes {
- for path, n := range subset {
- if n.IsDeleted() {
- if owner == (common.Hash{}) {
- rawdb.DeleteAccountTrieNode(batch, []byte(path))
- } else {
- rawdb.DeleteStorageTrieNode(batch, owner, []byte(path))
- }
- if clean != nil {
- clean.Del(cacheKey(owner, []byte(path)))
- }
- } else {
- if owner == (common.Hash{}) {
- rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob)
- } else {
- rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob)
- }
- if clean != nil {
- clean.Set(cacheKey(owner, []byte(path)), n.Blob)
- }
- }
- }
- total += len(subset)
- }
- return total
-}
-
-// cacheKey constructs the unique key of clean cache.
-func cacheKey(owner common.Hash, path []byte) []byte {
- if owner == (common.Hash{}) {
- return path
- }
- return append(owner.Bytes(), path...)
-}
diff --git a/trie/triedb/pathdb/testutils.go b/trie/triedb/pathdb/testutils.go
deleted file mode 100644
index d6fdacb421..0000000000
--- a/trie/triedb/pathdb/testutils.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// 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 .
-
-package pathdb
-
-import (
- "bytes"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
- "golang.org/x/exp/slices"
-)
-
-// testHasher is a test utility for computing root hash of a batch of state
-// elements. The hash algorithm is to sort all the elements in lexicographical
-// order, concat the key and value in turn, and perform hash calculation on
-// the concatenated bytes. Except the root hash, a nodeset will be returned
-// once Commit is called, which contains all the changes made to hasher.
-type testHasher struct {
- owner common.Hash // owner identifier
- root common.Hash // original root
- dirties map[common.Hash][]byte // dirty states
- cleans map[common.Hash][]byte // clean states
-}
-
-// newTestHasher constructs a hasher object with provided states.
-func newTestHasher(owner common.Hash, root common.Hash, cleans map[common.Hash][]byte) (*testHasher, error) {
- if cleans == nil {
- cleans = make(map[common.Hash][]byte)
- }
- if got, _ := hash(cleans); got != root {
- return nil, fmt.Errorf("state root mismatched, want: %x, got: %x", root, got)
- }
- return &testHasher{
- owner: owner,
- root: root,
- dirties: make(map[common.Hash][]byte),
- cleans: cleans,
- }, nil
-}
-
-// Get returns the value for key stored in the trie.
-func (h *testHasher) Get(key []byte) ([]byte, error) {
- hash := common.BytesToHash(key)
- val, ok := h.dirties[hash]
- if ok {
- return val, nil
- }
- return h.cleans[hash], nil
-}
-
-// Update associates key with value in the trie.
-func (h *testHasher) Update(key, value []byte) error {
- h.dirties[common.BytesToHash(key)] = common.CopyBytes(value)
- return nil
-}
-
-// Delete removes any existing value for key from the trie.
-func (h *testHasher) Delete(key []byte) error {
- h.dirties[common.BytesToHash(key)] = nil
- return nil
-}
-
-// Commit computes the new hash of the states and returns the set with all
-// state changes.
-func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) {
- var (
- nodes = make(map[common.Hash][]byte)
- set = trienode.NewNodeSet(h.owner)
- )
- for hash, val := range h.cleans {
- nodes[hash] = val
- }
- for hash, val := range h.dirties {
- nodes[hash] = val
- if bytes.Equal(val, h.cleans[hash]) {
- continue
- }
- if len(val) == 0 {
- set.AddNode(hash.Bytes(), trienode.NewDeleted())
- } else {
- set.AddNode(hash.Bytes(), trienode.New(crypto.Keccak256Hash(val), val))
- }
- }
- root, blob := hash(nodes)
-
- // Include the dirty root node as well.
- if root != types.EmptyRootHash && root != h.root {
- set.AddNode(nil, trienode.New(root, blob))
- }
- if root == types.EmptyRootHash && h.root != types.EmptyRootHash {
- set.AddNode(nil, trienode.NewDeleted())
- }
- return root, set, nil
-}
-
-// hash performs the hash computation upon the provided states.
-func hash(states map[common.Hash][]byte) (common.Hash, []byte) {
- var hs []common.Hash
- for hash := range states {
- hs = append(hs, hash)
- }
- slices.SortFunc(hs, common.Hash.Cmp)
-
- var input []byte
- for _, hash := range hs {
- if len(states[hash]) == 0 {
- continue
- }
- input = append(input, hash.Bytes()...)
- input = append(input, states[hash]...)
- }
- if len(input) == 0 {
- return types.EmptyRootHash, nil
- }
- return crypto.Keccak256Hash(input), input
-}
-
-type hashLoader struct {
- accounts map[common.Hash][]byte
- storages map[common.Hash]map[common.Hash][]byte
-}
-
-func newHashLoader(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *hashLoader {
- return &hashLoader{
- accounts: accounts,
- storages: storages,
- }
-}
-
-// OpenTrie opens the main account trie.
-func (l *hashLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
- return newTestHasher(common.Hash{}, root, l.accounts)
-}
-
-// OpenStorageTrie opens the storage trie of an account.
-func (l *hashLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
- return newTestHasher(addrHash, root, l.storages[addrHash])
-}
diff --git a/trie/trienode/node.go b/trie/trienode/node.go
deleted file mode 100644
index 95315c2e9a..0000000000
--- a/trie/trienode/node.go
+++ /dev/null
@@ -1,199 +0,0 @@
-// 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
-
-package trienode
-
-import (
- "fmt"
- "sort"
- "strings"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-// Node is a wrapper which contains the encoded blob of the trie node and its
-// node hash. It is general enough that can be used to represent trie node
-// corresponding to different trie implementations.
-type Node struct {
- Hash common.Hash // Node hash, empty for 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.
-func (n *Node) IsDeleted() bool {
- return len(n.Blob) == 0
-}
-
-// New constructs a node with provided node information.
-func New(hash common.Hash, blob []byte) *Node {
- return &Node{Hash: hash, Blob: blob}
-}
-
-// NewDeleted constructs a node which is deleted.
-func NewDeleted() *Node { return New(common.Hash{}, nil) }
-
-// leaf represents a trie leaf node
-type leaf struct {
- Blob []byte // raw blob of leaf
- Parent common.Hash // the hash of parent node
-}
-
-// NodeSet contains a set of nodes collected during the commit operation.
-// Each node is keyed by path. It's not thread-safe to use.
-type NodeSet struct {
- Owner common.Hash
- Leaves []*leaf
- Nodes map[string]*Node
- updates int // the count of updated and inserted nodes
- deletes int // the count of deleted nodes
-}
-
-// NewNodeSet initializes a node set. The owner is zero for the account trie and
-// the owning account address hash for storage tries.
-func NewNodeSet(owner common.Hash) *NodeSet {
- return &NodeSet{
- Owner: owner,
- Nodes: make(map[string]*Node),
- }
-}
-
-// ForEachWithOrder iterates the nodes with the order from bottom to top,
-// right to left, nodes with the longest path will be iterated first.
-func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) {
- var paths []string
- for path := range set.Nodes {
- paths = append(paths, path)
- }
- // Bottom-up, the longest path first
- sort.Sort(sort.Reverse(sort.StringSlice(paths)))
- for _, path := range paths {
- callback(path, set.Nodes[path])
- }
-}
-
-// AddNode adds the provided node into set.
-func (set *NodeSet) AddNode(path []byte, n *Node) {
- if n.IsDeleted() {
- set.deletes += 1
- } else {
- set.updates += 1
- }
- set.Nodes[string(path)] = n
-}
-
-// Merge adds a set of nodes into the set.
-func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error {
- if set.Owner != owner {
- return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, owner)
- }
- for path, node := range nodes {
- prev, ok := set.Nodes[path]
- if ok {
- // overwrite happens, revoke the counter
- if prev.IsDeleted() {
- set.deletes -= 1
- } else {
- set.updates -= 1
- }
- }
- set.AddNode([]byte(path), node)
- }
- return nil
-}
-
-// AddLeaf adds the provided leaf node into set. TODO(rjl493456442) how can
-// we get rid of it?
-func (set *NodeSet) AddLeaf(parent common.Hash, blob []byte) {
- set.Leaves = append(set.Leaves, &leaf{Blob: blob, Parent: parent})
-}
-
-// Size returns the number of dirty nodes in set.
-func (set *NodeSet) Size() (int, int) {
- return set.updates, set.deletes
-}
-
-// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can
-// we get rid of it?
-func (set *NodeSet) Hashes() []common.Hash {
- var ret []common.Hash
- for _, node := range set.Nodes {
- ret = append(ret, node.Hash)
- }
- return ret
-}
-
-// Summary returns a string-representation of the NodeSet.
-func (set *NodeSet) Summary() string {
- var out = new(strings.Builder)
- fmt.Fprintf(out, "nodeset owner: %v\n", set.Owner)
- if set.Nodes != nil {
- for path, n := range set.Nodes {
- // Deletion
- if n.IsDeleted() {
- fmt.Fprintf(out, " [-]: %x\n", path)
- continue
- }
- // Insertion or update
- fmt.Fprintf(out, " [+/*]: %x -> %v \n", path, n.Hash)
- }
- }
- for _, n := range set.Leaves {
- fmt.Fprintf(out, "[leaf]: %v\n", n)
- }
- return out.String()
-}
-
-// MergedNodeSet represents a merged node set for a group of tries.
-type MergedNodeSet struct {
- Sets map[common.Hash]*NodeSet
-}
-
-// NewMergedNodeSet initializes an empty merged set.
-func NewMergedNodeSet() *MergedNodeSet {
- return &MergedNodeSet{Sets: make(map[common.Hash]*NodeSet)}
-}
-
-// NewWithNodeSet constructs a merged nodeset with the provided single set.
-func NewWithNodeSet(set *NodeSet) *MergedNodeSet {
- merged := NewMergedNodeSet()
- merged.Merge(set)
- return merged
-}
-
-// Merge merges the provided dirty nodes of a trie into the set. The assumption
-// is held that no duplicated set belonging to the same trie will be merged twice.
-func (set *MergedNodeSet) Merge(other *NodeSet) error {
- subset, present := set.Sets[other.Owner]
- if present {
- return subset.Merge(other.Owner, other.Nodes)
- }
- set.Sets[other.Owner] = other
- return nil
-}
-
-// Flatten returns a two-dimensional map for internal nodes.
-func (set *MergedNodeSet) Flatten() map[common.Hash]map[string]*Node {
- nodes := make(map[common.Hash]map[string]*Node)
- for owner, set := range set.Sets {
- nodes[owner] = set.Nodes
- }
- return nodes
-}
diff --git a/trie/trienode/proof.go b/trie/trienode/proof.go
deleted file mode 100644
index 012f0087dd..0000000000
--- a/trie/trienode/proof.go
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trienode
-
-import (
- "errors"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
-)
-
-// ProofSet stores a set of trie nodes. It implements trie.Database and can also
-// act as a cache for another trie.Database.
-type ProofSet struct {
- nodes map[string][]byte
- order []string
-
- dataSize int
- lock sync.RWMutex
-}
-
-// NewProofSet creates an empty node set
-func NewProofSet() *ProofSet {
- return &ProofSet{
- nodes: make(map[string][]byte),
- }
-}
-
-// Put stores a new node in the set
-func (db *ProofSet) Put(key []byte, value []byte) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- if _, ok := db.nodes[string(key)]; ok {
- return nil
- }
- keystr := string(key)
-
- db.nodes[keystr] = common.CopyBytes(value)
- db.order = append(db.order, keystr)
- db.dataSize += len(value)
-
- return nil
-}
-
-// Delete removes a node from the set
-func (db *ProofSet) Delete(key []byte) error {
- db.lock.Lock()
- defer db.lock.Unlock()
-
- delete(db.nodes, string(key))
- return nil
-}
-
-// Get returns a stored node
-func (db *ProofSet) Get(key []byte) ([]byte, error) {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- if entry, ok := db.nodes[string(key)]; ok {
- return entry, nil
- }
- return nil, errors.New("not found")
-}
-
-// Has returns true if the node set contains the given key
-func (db *ProofSet) Has(key []byte) (bool, error) {
- _, err := db.Get(key)
- return err == nil, nil
-}
-
-// KeyCount returns the number of nodes in the set
-func (db *ProofSet) KeyCount() int {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- return len(db.nodes)
-}
-
-// DataSize returns the aggregated data size of nodes in the set
-func (db *ProofSet) DataSize() int {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- return db.dataSize
-}
-
-// List converts the node set to a ProofList
-func (db *ProofSet) List() ProofList {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- var values ProofList
- for _, key := range db.order {
- values = append(values, db.nodes[key])
- }
- return values
-}
-
-// Store writes the contents of the set to the given database
-func (db *ProofSet) Store(target ethdb.KeyValueWriter) {
- db.lock.RLock()
- defer db.lock.RUnlock()
-
- for key, value := range db.nodes {
- target.Put([]byte(key), value)
- }
-}
-
-// ProofList stores an ordered list of trie nodes. It implements ethdb.KeyValueWriter.
-type ProofList []rlp.RawValue
-
-// Store writes the contents of the list to the given database
-func (n ProofList) Store(db ethdb.KeyValueWriter) {
- for _, node := range n {
- db.Put(crypto.Keccak256(node), node)
- }
-}
-
-// Set converts the node list to a ProofSet
-func (n ProofList) Set() *ProofSet {
- db := NewProofSet()
- n.Store(db)
- return db
-}
-
-// Put stores a new node at the end of the list
-func (n *ProofList) Put(key []byte, value []byte) error {
- *n = append(*n, value)
- return nil
-}
-
-// Delete panics as there's no reason to remove a node from the list.
-func (n *ProofList) Delete(key []byte) error {
- panic("not supported")
-}
-
-// DataSize returns the aggregated data size of nodes in the list
-func (n ProofList) DataSize() int {
- var size int
- for _, node := range n {
- size += len(node)
- }
- return size
-}
diff --git a/trie/triestate/state.go b/trie/triestate/state.go
deleted file mode 100644
index 4c47e9c397..0000000000
--- a/trie/triestate/state.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// 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
-
-package triestate
-
-import (
- "errors"
- "fmt"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "golang.org/x/crypto/sha3"
-)
-
-// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia
-// tree or Verkle tree.
-type Trie interface {
- // Get returns the value for key stored in the trie.
- Get(key []byte) ([]byte, error)
-
- // Update associates key with value in the trie.
- Update(key, value []byte) error
-
- // Delete removes any existing value for key from the trie.
- Delete(key []byte) error
-
- // Commit the trie and returns a set of dirty nodes generated along with
- // the new root hash.
- Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error)
-}
-
-// TrieLoader wraps functions to load tries.
-type TrieLoader interface {
- // OpenTrie opens the main account trie.
- OpenTrie(root common.Hash) (Trie, error)
-
- // OpenStorageTrie opens the storage trie of an account.
- OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error)
-}
-
-// Set represents a collection of mutated states during a state transition.
-// The value refers to the original content of state before the transition
-// is made. Nil means that the state was not present previously.
-type Set struct {
- Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present
- Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present
- Incomplete map[common.Address]struct{} // Indicator whether the storage is incomplete due to large deletion
- size common.StorageSize // Approximate size of set
-}
-
-// New constructs the state set with provided data.
-func New(accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, incomplete map[common.Address]struct{}) *Set {
- return &Set{
- Accounts: accounts,
- Storages: storages,
- Incomplete: incomplete,
- }
-}
-
-// Size returns the approximate memory size occupied by the set.
-func (s *Set) Size() common.StorageSize {
- if s.size != 0 {
- return s.size
- }
- for _, account := range s.Accounts {
- s.size += common.StorageSize(common.AddressLength + len(account))
- }
- for _, slots := range s.Storages {
- for _, val := range slots {
- s.size += common.StorageSize(common.HashLength + len(val))
- }
- s.size += common.StorageSize(common.AddressLength)
- }
- s.size += common.StorageSize(common.AddressLength * len(s.Incomplete))
- return s.size
-}
-
-// context wraps all fields for executing state diffs.
-type context struct {
- prevRoot common.Hash
- postRoot common.Hash
- accounts map[common.Address][]byte
- storages map[common.Address]map[common.Hash][]byte
- accountTrie Trie
- nodes *trienode.MergedNodeSet
-}
-
-// Apply traverses the provided state diffs, apply them in the associated
-// post-state and return the generated dirty trie nodes. The state can be
-// 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) {
- tr, err := loader.OpenTrie(postRoot)
- if err != nil {
- return nil, err
- }
- ctx := &context{
- prevRoot: prevRoot,
- postRoot: postRoot,
- accounts: accounts,
- storages: storages,
- accountTrie: tr,
- nodes: trienode.NewMergedNodeSet(),
- }
- for addr, account := range accounts {
- var err error
- if len(account) == 0 {
- err = deleteAccount(ctx, loader, addr)
- } else {
- err = updateAccount(ctx, loader, addr)
- }
- if err != nil {
- return nil, fmt.Errorf("failed to revert state, err: %w", err)
- }
- }
- root, result, err := tr.Commit(false)
- if err != nil {
- return nil, err
- }
- if root != prevRoot {
- return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
- }
- if err := ctx.nodes.Merge(result); err != nil {
- return nil, err
- }
- return ctx.nodes.Flatten(), nil
-}
-
-// updateAccount the account was present in prev-state, and may or may not
-// existent in post-state. Apply the reverse diff and verify if the storage
-// root matches the one in prev-state account.
-func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error {
- // The account was present in prev-state, decode it from the
- // 'slim-rlp' format bytes.
- h := newHasher()
- defer h.release()
-
- addrHash := h.hash(addr.Bytes())
- prev, err := types.FullAccount(ctx.accounts[addr])
- if err != nil {
- return err
- }
- // The account may or may not existent in post-state, try to
- // load it and decode if it's found.
- blob, err := ctx.accountTrie.Get(addrHash.Bytes())
- if err != nil {
- return err
- }
- post := types.NewEmptyStateAccount()
- if len(blob) != 0 {
- if err := rlp.DecodeBytes(blob, &post); err != nil {
- return err
- }
- }
- // Apply all storage changes into the post-state storage trie.
- st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
- if err != nil {
- return err
- }
- for key, val := range ctx.storages[addr] {
- var err error
- if len(val) == 0 {
- err = st.Delete(key.Bytes())
- } else {
- err = st.Update(key.Bytes(), val)
- }
- if err != nil {
- return err
- }
- }
- root, result, err := st.Commit(false)
- if err != nil {
- return err
- }
- if root != prev.Root {
- return errors.New("failed to reset storage trie")
- }
- // The returned set can be nil if storage trie is not changed
- // at all.
- if result != nil {
- if err := ctx.nodes.Merge(result); err != nil {
- return err
- }
- }
- // Write the prev-state account into the main trie
- full, err := rlp.EncodeToBytes(prev)
- if err != nil {
- return err
- }
- return ctx.accountTrie.Update(addrHash.Bytes(), full)
-}
-
-// deleteAccount the account was not present in prev-state, and is expected
-// to be existent in post-state. Apply the reverse diff and verify if the
-// account and storage is wiped out correctly.
-func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
- // The account must be existent in post-state, load the account.
- h := newHasher()
- defer h.release()
-
- addrHash := h.hash(addr.Bytes())
- blob, err := ctx.accountTrie.Get(addrHash.Bytes())
- if err != nil {
- return err
- }
- if len(blob) == 0 {
- return fmt.Errorf("account is non-existent %#x", addrHash)
- }
- var post types.StateAccount
- if err := rlp.DecodeBytes(blob, &post); err != nil {
- return err
- }
- st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
- if err != nil {
- return err
- }
- for key, val := range ctx.storages[addr] {
- if len(val) != 0 {
- return errors.New("expect storage deletion")
- }
- if err := st.Delete(key.Bytes()); err != nil {
- return err
- }
- }
- root, result, err := st.Commit(false)
- if err != nil {
- return err
- }
- if root != types.EmptyRootHash {
- return errors.New("failed to clear storage trie")
- }
- // The returned set can be nil if storage trie is not changed
- // at all.
- if result != nil {
- if err := ctx.nodes.Merge(result); err != nil {
- return err
- }
- }
- // Delete the post-state account from the main trie.
- return ctx.accountTrie.Delete(addrHash.Bytes())
-}
-
-// 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)
-}
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
deleted file mode 100644
index ce059edc64..0000000000
--- a/trie/utils/verkle.go
+++ /dev/null
@@ -1,342 +0,0 @@
-// Copyright 2023 go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package utils
-
-import (
- "encoding/binary"
- "sync"
-
- "github.com/crate-crypto/go-ipa/bandersnatch/fr"
- "github.com/ethereum/go-ethereum/common/lru"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/gballet/go-verkle"
- "github.com/holiman/uint256"
-)
-
-const (
- // The spec of verkle key encoding can be found here.
- // https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding
- VersionLeafKey = 0
- BalanceLeafKey = 1
- NonceLeafKey = 2
- CodeKeccakLeafKey = 3
- CodeSizeLeafKey = 4
-)
-
-var (
- zero = uint256.NewInt(0)
- verkleNodeWidthLog2 = 8
- headerStorageOffset = uint256.NewInt(64)
- mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(verkleNodeWidthLog2))
- codeOffset = uint256.NewInt(128)
- verkleNodeWidth = uint256.NewInt(256)
- codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
-
- index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
-
- // cacheHitGauge is the metric to track how many cache hit occurred.
- cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil)
-
- // cacheMissGauge is the metric to track how many cache miss occurred.
- cacheMissGauge = metrics.NewRegisteredGauge("trie/verkle/cache/miss", nil)
-)
-
-func init() {
- // The byte array is the Marshalled output of the point computed as such:
- //
- // var (
- // config = verkle.GetConfig()
- // fr verkle.Fr
- // )
- // verkle.FromLEBytes(&fr, []byte{2, 64})
- // point := config.CommitToPoly([]verkle.Fr{fr}, 1)
- index0Point = new(verkle.Point)
- err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191})
- if err != nil {
- panic(err)
- }
-}
-
-// PointCache is the LRU cache for storing evaluated address commitment.
-type PointCache struct {
- lru lru.BasicLRU[string, *verkle.Point]
- lock sync.RWMutex
-}
-
-// NewPointCache returns the cache with specified size.
-func NewPointCache(maxItems int) *PointCache {
- return &PointCache{
- lru: lru.NewBasicLRU[string, *verkle.Point](maxItems),
- }
-}
-
-// Get returns the cached commitment for the specified address, or computing
-// it on the flight.
-func (c *PointCache) Get(addr []byte) *verkle.Point {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- p, ok := c.lru.Get(string(addr))
- if ok {
- cacheHitGauge.Inc(1)
- return p
- }
- cacheMissGauge.Inc(1)
- p = evaluateAddressPoint(addr)
- c.lru.Add(string(addr), p)
- return p
-}
-
-// GetStem returns the first 31 bytes of the tree key as the tree stem. It only
-// works for the account metadata whose treeIndex is 0.
-func (c *PointCache) GetStem(addr []byte) []byte {
- p := c.Get(addr)
- return pointToHash(p, 0)[:31]
-}
-
-// GetTreeKey performs both the work of the spec's get_tree_key function, and that
-// of pedersen_hash: it builds the polynomial in pedersen_hash without having to
-// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte
-// array. Since at most the first 5 coefficients of the polynomial will be non-zero,
-// these 5 coefficients are created directly.
-func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
- if len(address) < 32 {
- var aligned [32]byte
- address = append(aligned[:32-len(address)], address...)
- }
- // poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high]
- var poly [5]fr.Element
-
- // 32-byte address, interpreted as two little endian
- // 16-byte numbers.
- verkle.FromLEBytes(&poly[1], address[:16])
- verkle.FromLEBytes(&poly[2], address[16:])
-
- // treeIndex must be interpreted as a 32-byte aligned little-endian integer.
- // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00.
- // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes).
- //
- // To avoid unnecessary endianness conversions for go-ipa, we do some trick:
- // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of
- // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})).
- // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of
- // the 32-byte aligned big-endian representation (BE({00,00,...}).
- trieIndexBytes := treeIndex.Bytes32()
- verkle.FromBytes(&poly[3], trieIndexBytes[16:])
- verkle.FromBytes(&poly[4], trieIndexBytes[:16])
-
- cfg := verkle.GetConfig()
- ret := cfg.CommitToPoly(poly[:], 0)
-
- // add a constant point corresponding to poly[0]=[2+256*64].
- ret.Add(ret, index0Point)
-
- return pointToHash(ret, subIndex)
-}
-
-// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only
-// difference is a part of polynomial is already evaluated.
-//
-// Specifically, poly = [2+256*64, address_le_low, address_le_high] is already
-// evaluated.
-func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
- var poly [5]fr.Element
-
- poly[0].SetZero()
- poly[1].SetZero()
- poly[2].SetZero()
-
- // little-endian, 32-byte aligned treeIndex
- var index [32]byte
- for i := 0; i < len(treeIndex); i++ {
- binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i])
- }
- verkle.FromLEBytes(&poly[3], index[:16])
- verkle.FromLEBytes(&poly[4], index[16:])
-
- cfg := verkle.GetConfig()
- ret := cfg.CommitToPoly(poly[:], 0)
-
- // add the pre-evaluated address
- ret.Add(ret, evaluated)
-
- return pointToHash(ret, subIndex)
-}
-
-// VersionKey returns the verkle tree key of the version field for the specified account.
-func VersionKey(address []byte) []byte {
- return GetTreeKey(address, zero, VersionLeafKey)
-}
-
-// BalanceKey returns the verkle tree key of the balance field for the specified account.
-func BalanceKey(address []byte) []byte {
- return GetTreeKey(address, zero, BalanceLeafKey)
-}
-
-// NonceKey returns the verkle tree key of the nonce field for the specified account.
-func NonceKey(address []byte) []byte {
- return GetTreeKey(address, zero, NonceLeafKey)
-}
-
-// CodeKeccakKey returns the verkle tree key of the code keccak field for
-// the specified account.
-func CodeKeccakKey(address []byte) []byte {
- return GetTreeKey(address, zero, CodeKeccakLeafKey)
-}
-
-// CodeSizeKey returns the verkle tree key of the code size field for the
-// specified account.
-func CodeSizeKey(address []byte) []byte {
- return GetTreeKey(address, zero, CodeSizeLeafKey)
-}
-
-func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
- var (
- chunkOffset = new(uint256.Int).Add(codeOffset, chunk)
- treeIndex = new(uint256.Int).Div(chunkOffset, verkleNodeWidth)
- subIndexMod = new(uint256.Int).Mod(chunkOffset, verkleNodeWidth)
- )
- var subIndex byte
- if len(subIndexMod) != 0 {
- subIndex = byte(subIndexMod[0])
- }
- return treeIndex, subIndex
-}
-
-// CodeChunkKey returns the verkle tree key of the code chunk for the
-// specified account.
-func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
- treeIndex, subIndex := codeChunkIndex(chunk)
- return GetTreeKey(address, treeIndex, subIndex)
-}
-
-func storageIndex(bytes []byte) (*uint256.Int, byte) {
- // If the storage slot is in the header, we need to add the header offset.
- var key uint256.Int
- key.SetBytes(bytes)
- if key.Cmp(codeStorageDelta) < 0 {
- // This addition is always safe; it can't ever overflow since pos
-
-package utils
-
-import (
- "bytes"
- "testing"
-
- "github.com/gballet/go-verkle"
- "github.com/holiman/uint256"
-)
-
-func TestTreeKey(t *testing.T) {
- var (
- address = []byte{0x01}
- addressEval = evaluateAddressPoint(address)
- smallIndex = uint256.NewInt(1)
- largeIndex = uint256.NewInt(10000)
- smallStorage = []byte{0x1}
- largeStorage = bytes.Repeat([]byte{0xff}, 16)
- )
- if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) {
- t.Fatal("Unmatched version key")
- }
- if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) {
- t.Fatal("Unmatched balance key")
- }
- if !bytes.Equal(NonceKey(address), NonceKeyWithEvaluatedAddress(addressEval)) {
- t.Fatal("Unmatched nonce key")
- }
- if !bytes.Equal(CodeKeccakKey(address), CodeKeccakKeyWithEvaluatedAddress(addressEval)) {
- t.Fatal("Unmatched code keccak key")
- }
- if !bytes.Equal(CodeSizeKey(address), CodeSizeKeyWithEvaluatedAddress(addressEval)) {
- t.Fatal("Unmatched code size key")
- }
- if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) {
- t.Fatal("Unmatched code chunk key")
- }
- if !bytes.Equal(CodeChunkKey(address, largeIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, largeIndex)) {
- t.Fatal("Unmatched code chunk key")
- }
- if !bytes.Equal(StorageSlotKey(address, smallStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, smallStorage)) {
- t.Fatal("Unmatched storage slot key")
- }
- if !bytes.Equal(StorageSlotKey(address, largeStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, largeStorage)) {
- t.Fatal("Unmatched storage slot key")
- }
-}
-
-// goos: darwin
-// goarch: amd64
-// pkg: github.com/ethereum/go-ethereum/trie/utils
-// cpu: VirtualApple @ 2.50GHz
-// BenchmarkTreeKey
-// BenchmarkTreeKey-8 398731 2961 ns/op 32 B/op 1 allocs/op
-func BenchmarkTreeKey(b *testing.B) {
- // Initialize the IPA settings which can be pretty expensive.
- verkle.GetConfig()
-
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- BalanceKey([]byte{0x01})
- }
-}
-
-// goos: darwin
-// goarch: amd64
-// pkg: github.com/ethereum/go-ethereum/trie/utils
-// cpu: VirtualApple @ 2.50GHz
-// BenchmarkTreeKeyWithEvaluation
-// BenchmarkTreeKeyWithEvaluation-8 513855 2324 ns/op 32 B/op 1 allocs/op
-func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
- // Initialize the IPA settings which can be pretty expensive.
- verkle.GetConfig()
-
- addr := []byte{0x01}
- eval := evaluateAddressPoint(addr)
-
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- BalanceKeyWithEvaluatedAddress(eval)
- }
-}
-
-// goos: darwin
-// goarch: amd64
-// pkg: github.com/ethereum/go-ethereum/trie/utils
-// cpu: VirtualApple @ 2.50GHz
-// BenchmarkStorageKey
-// BenchmarkStorageKey-8 230516 4584 ns/op 96 B/op 3 allocs/op
-func BenchmarkStorageKey(b *testing.B) {
- // Initialize the IPA settings which can be pretty expensive.
- verkle.GetConfig()
-
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- StorageSlotKey([]byte{0x01}, bytes.Repeat([]byte{0xff}, 32))
- }
-}
-
-// goos: darwin
-// goarch: amd64
-// pkg: github.com/ethereum/go-ethereum/trie/utils
-// cpu: VirtualApple @ 2.50GHz
-// BenchmarkStorageKeyWithEvaluation
-// BenchmarkStorageKeyWithEvaluation-8 320125 3753 ns/op 96 B/op 3 allocs/op
-func BenchmarkStorageKeyWithEvaluation(b *testing.B) {
- // Initialize the IPA settings which can be pretty expensive.
- verkle.GetConfig()
-
- addr := []byte{0x01}
- eval := evaluateAddressPoint(addr)
-
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- StorageSlotKeyWithEvaluatedAddress(eval, bytes.Repeat([]byte{0xff}, 32))
- }
-}
diff --git a/trie/verkle.go b/trie/verkle.go
deleted file mode 100644
index 89e2e53408..0000000000
--- a/trie/verkle.go
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright 2023 go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "encoding/binary"
- "errors"
- "fmt"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/utils"
- "github.com/gballet/go-verkle"
- "github.com/holiman/uint256"
-)
-
-var (
- zero [32]byte
- errInvalidRootType = errors.New("invalid node type for root")
-)
-
-// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie
-// interface so that Verkle trees can be reused verbatim.
-type VerkleTrie struct {
- root verkle.VerkleNode
- db *Database
- cache *utils.PointCache
- reader *trieReader
-}
-
-// NewVerkleTrie constructs a verkle tree based on the specified root hash.
-func NewVerkleTrie(root common.Hash, db *Database, cache *utils.PointCache) (*VerkleTrie, error) {
- reader, err := newTrieReader(root, common.Hash{}, db)
- if err != nil {
- return nil, err
- }
- // Parse the root verkle node if it's not empty.
- node := verkle.New()
- if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
- blob, err := reader.node(nil, common.Hash{})
- if err != nil {
- return nil, err
- }
- node, err = verkle.ParseNode(blob, 0)
- if err != nil {
- return nil, err
- }
- }
- return &VerkleTrie{
- root: node,
- db: db,
- cache: cache,
- reader: reader,
- }, nil
-}
-
-// GetKey returns the sha3 preimage of a hashed key that was previously used
-// to store a value.
-func (t *VerkleTrie) GetKey(key []byte) []byte {
- return key
-}
-
-// GetAccount implements state.Trie, retrieving the account with the specified
-// account address. If the specified account is not in the verkle tree, nil will
-// be returned. If the tree is corrupted, an error will be returned.
-func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
- var (
- acc = &types.StateAccount{}
- values [][]byte
- err error
- )
- switch n := t.root.(type) {
- case *verkle.InternalNode:
- values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver)
- if err != nil {
- return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
- }
- default:
- return nil, errInvalidRootType
- }
- if values == nil {
- return nil, nil
- }
- // Decode nonce in little-endian
- if len(values[utils.NonceLeafKey]) > 0 {
- acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey])
- }
- // Decode balance in little-endian
- var balance [32]byte
- copy(balance[:], values[utils.BalanceLeafKey])
- for i := 0; i < len(balance)/2; i++ {
- balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1]
- }
- acc.Balance = new(big.Int).SetBytes(balance[:])
-
- // Decode codehash
- acc.CodeHash = values[utils.CodeKeccakLeafKey]
-
- // TODO account.Root is leave as empty. How should we handle the legacy account?
- return acc, nil
-}
-
-// GetStorage implements state.Trie, retrieving the storage slot with the specified
-// account address and storage key. If the specified slot is not in the verkle tree,
-// nil will be returned. If the tree is corrupted, an error will be returned.
-func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
- k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
- val, err := t.root.Get(k, t.nodeResolver)
- if err != nil {
- return nil, err
- }
- return common.TrimLeftZeroes(val), nil
-}
-
-// UpdateAccount implements state.Trie, writing the provided account into the tree.
-// If the tree is corrupted, an error will be returned.
-func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error {
- var (
- err error
- nonce, balance [32]byte
- values = make([][]byte, verkle.NodeWidth)
- )
- values[utils.VersionLeafKey] = zero[:]
- values[utils.CodeKeccakLeafKey] = acc.CodeHash[:]
-
- // Encode nonce in little-endian
- binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
- values[utils.NonceLeafKey] = nonce[:]
-
- // Encode balance in little-endian
- bytes := acc.Balance.Bytes()
- if len(bytes) > 0 {
- for i, b := range bytes {
- balance[len(bytes)-i-1] = b
- }
- }
- values[utils.BalanceLeafKey] = balance[:]
-
- switch n := t.root.(type) {
- case *verkle.InternalNode:
- err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver)
- if err != nil {
- return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
- }
- default:
- return errInvalidRootType
- }
- // TODO figure out if the code size needs to be updated, too
- return nil
-}
-
-// UpdateStorage implements state.Trie, writing the provided storage slot into
-// the tree. If the tree is corrupted, an error will be returned.
-func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
- // Left padding the slot value to 32 bytes.
- var v [32]byte
- if len(value) >= 32 {
- copy(v[:], value[:32])
- } else {
- copy(v[32-len(value):], value[:])
- }
- k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key)
- return t.root.Insert(k, v[:], t.nodeResolver)
-}
-
-// DeleteAccount implements state.Trie, deleting the specified account from the
-// trie. If the account was not existent in the trie, no error will be returned.
-// If the trie is corrupted, an error will be returned.
-func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
- var (
- err error
- values = make([][]byte, verkle.NodeWidth)
- )
- for i := 0; i < verkle.NodeWidth; i++ {
- values[i] = zero[:]
- }
- switch n := t.root.(type) {
- case *verkle.InternalNode:
- err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver)
- if err != nil {
- return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err)
- }
- default:
- return errInvalidRootType
- }
- return nil
-}
-
-// DeleteStorage implements state.Trie, deleting the specified storage slot from
-// the trie. If the storage slot was not existent in the trie, no error will be
-// returned. If the trie is corrupted, an error will be returned.
-func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error {
- var zero [32]byte
- k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
- return t.root.Insert(k, zero[:], t.nodeResolver)
-}
-
-// Hash returns the root hash of the tree. It does not write to the database and
-// can be used even if the tree doesn't have one.
-func (t *VerkleTrie) Hash() common.Hash {
- return t.root.Commit().Bytes()
-}
-
-// Commit writes all nodes to the tree's memory database.
-func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
- root, ok := t.root.(*verkle.InternalNode)
- if !ok {
- return common.Hash{}, nil, errors.New("unexpected root node type")
- }
- nodes, err := root.BatchSerialize()
- if err != nil {
- return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err)
- }
- nodeset := trienode.NewNodeSet(common.Hash{})
- for _, node := range nodes {
- // hash parameter is not used in pathdb
- nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes))
- }
- // Serialize root commitment form
- return t.Hash(), nodeset, nil
-}
-
-// NodeIterator implements state.Trie, returning an iterator that returns
-// nodes of the trie. Iteration starts at the key after the given start key.
-//
-// TODO(gballet, rjl493456442) implement it.
-func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
- panic("not implemented")
-}
-
-// Prove implements state.Trie, constructing a Merkle proof for key. The result
-// contains all encoded nodes on the path to the value at key. The value itself
-// is also included in the last node and can be retrieved by verifying the proof.
-//
-// If the trie does not contain a value for key, the returned proof contains all
-// nodes of the longest existing prefix of the key (at least the root), ending
-// with the node that proves the absence of the key.
-//
-// TODO(gballet, rjl493456442) implement it.
-func (t *VerkleTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
- panic("not implemented")
-}
-
-// Copy returns a deep-copied verkle tree.
-func (t *VerkleTrie) Copy() *VerkleTrie {
- return &VerkleTrie{
- root: t.root.Copy(),
- db: t.db,
- cache: t.cache,
- reader: t.reader,
- }
-}
-
-// IsVerkle indicates if the trie is a Verkle trie.
-func (t *VerkleTrie) IsVerkle() bool {
- return true
-}
-
-// ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which
-// are actual code, and 1 byte is the pushdata offset).
-type ChunkedCode []byte
-
-// Copy the values here so as to avoid an import cycle
-const (
- PUSH1 = byte(0x60)
- PUSH32 = byte(0x7f)
-)
-
-// ChunkifyCode generates the chunked version of an array representing EVM bytecode
-func ChunkifyCode(code []byte) ChunkedCode {
- var (
- chunkOffset = 0 // offset in the chunk
- chunkCount = len(code) / 31
- codeOffset = 0 // offset in the code
- )
- if len(code)%31 != 0 {
- chunkCount++
- }
- chunks := make([]byte, chunkCount*32)
- for i := 0; i < chunkCount; i++ {
- // number of bytes to copy, 31 unless the end of the code has been reached.
- end := 31 * (i + 1)
- if len(code) < end {
- end = len(code)
- }
- copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself
-
- // chunk offset = taken from the last chunk.
- if chunkOffset > 31 {
- // skip offset calculation if push data covers the whole chunk
- chunks[i*32] = 31
- chunkOffset = 1
- continue
- }
- chunks[32*i] = byte(chunkOffset)
- chunkOffset = 0
-
- // Check each instruction and update the offset it should be 0 unless
- // a PUSH-N overflows.
- for ; codeOffset < end; codeOffset++ {
- if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 {
- codeOffset += int(code[codeOffset] - PUSH1 + 1)
- if codeOffset+1 >= 31*(i+1) {
- codeOffset++
- chunkOffset = codeOffset - 31*(i+1)
- break
- }
- }
- }
- }
- return chunks
-}
-
-// UpdateContractCode implements state.Trie, writing the provided contract code
-// into the trie.
-func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
- var (
- chunks = ChunkifyCode(code)
- values [][]byte
- key []byte
- err error
- )
- for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
- groupOffset := (chunknr + 128) % 256
- if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
- values = make([][]byte, verkle.NodeWidth)
- key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr))
- }
- values[groupOffset] = chunks[i : i+32]
-
- // Reuse the calculated key to also update the code size.
- if i == 0 {
- cs := make([]byte, 32)
- binary.LittleEndian.PutUint64(cs, uint64(len(code)))
- values[utils.CodeSizeLeafKey] = cs
- }
- if groupOffset == 255 || len(chunks)-i <= 32 {
- switch root := t.root.(type) {
- case *verkle.InternalNode:
- err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver)
- if err != nil {
- return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
- }
- default:
- return errInvalidRootType
- }
- }
- }
- return nil
-}
-
-func (t *VerkleTrie) ToDot() string {
- return verkle.ToDot(t.root)
-}
-
-func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
- return t.reader.node(path, common.Hash{})
-}
diff --git a/trie/verkle_test.go b/trie/verkle_test.go
deleted file mode 100644
index bd31ea3879..0000000000
--- a/trie/verkle_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2023 go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package trie
-
-import (
- "bytes"
- "math/big"
- "reflect"
- "testing"
-
- "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/trie/triedb/pathdb"
- "github.com/ethereum/go-ethereum/trie/utils"
-)
-
-var (
- accounts = map[common.Address]*types.StateAccount{
- {1}: {
- Nonce: 100,
- Balance: big.NewInt(100),
- CodeHash: common.Hash{0x1}.Bytes(),
- },
- {2}: {
- Nonce: 200,
- Balance: big.NewInt(200),
- CodeHash: common.Hash{0x2}.Bytes(),
- },
- }
- storages = map[common.Address]map[common.Hash][]byte{
- {1}: {
- common.Hash{10}: []byte{10},
- common.Hash{11}: []byte{11},
- common.MaxHash: []byte{0xff},
- },
- {2}: {
- common.Hash{20}: []byte{20},
- common.Hash{21}: []byte{21},
- common.MaxHash: []byte{0xff},
- },
- }
-)
-
-func TestVerkleTreeReadWrite(t *testing.T) {
- db := NewDatabase(rawdb.NewMemoryDatabase(), &Config{
- IsVerkle: true,
- PathDB: pathdb.Defaults,
- })
- defer db.Close()
-
- tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
-
- for addr, acct := range accounts {
- if err := tr.UpdateAccount(addr, acct); err != nil {
- t.Fatalf("Failed to update account, %v", err)
- }
- for key, val := range storages[addr] {
- if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
- t.Fatalf("Failed to update account, %v", err)
- }
- }
- }
-
- for addr, acct := range accounts {
- stored, err := tr.GetAccount(addr)
- if err != nil {
- t.Fatalf("Failed to get account, %v", err)
- }
- if !reflect.DeepEqual(stored, acct) {
- t.Fatal("account is not matched")
- }
- for key, val := range storages[addr] {
- stored, err := tr.GetStorage(addr, key.Bytes())
- if err != nil {
- t.Fatalf("Failed to get storage, %v", err)
- }
- if !bytes.Equal(stored, val) {
- t.Fatal("storage is not matched")
- }
- }
- }
-}