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") - } - } - } -}