From e914a9f8a473b8536c01f862eabcae68f1cf0510 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 28 Jul 2022 19:38:54 +0200 Subject: [PATCH 1/7] core: use TryGetAccount to read where TryUpdateAccount has been used to write --- core/state/database.go | 4 +++- core/state/statedb.go | 12 ++++-------- core/state/trie_prefetcher.go | 6 +++++- eth/protocols/snap/handler.go | 6 +++--- light/trie.go | 10 ++++++++++ trie/secure_trie.go | 6 +++++- trie/trie.go | 27 ++++++++++++++++++++++++++- 7 files changed, 56 insertions(+), 15 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index ce5d8d731715..d069dee4282a 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -71,7 +71,9 @@ type Trie interface { // trie.MissingNodeError is returned. TryGet(key []byte) ([]byte, error) - // TryUpdateAccount abstract an account write in the trie. + TryGetAccount(key []byte) (*types.StateAccount, error) + + // TryUpdateAccount abstract an account write to the trie. TryUpdateAccount(key []byte, account *types.StateAccount) error // TryUpdate associates key with value in the trie. If value has length zero, any diff --git a/core/state/statedb.go b/core/state/statedb.go index e945ab595013..d2bbe4375df8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -537,20 +537,16 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { // If snapshot unavailable or reading from it failed, load from the database if data == nil { start := time.Now() - enc, err := s.trie.TryGet(addr.Bytes()) + var err error + data, err = s.trie.TryGetAccount(addr.Bytes()) if metrics.EnabledExpensive { s.AccountReads += time.Since(start) } if err != nil { - s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err)) + s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) return nil } - if len(enc) == 0 { - return nil - } - data = new(types.StateAccount) - if err := rlp.DecodeBytes(enc, data); err != nil { - log.Error("Failed to decode state object", "addr", addr, "err", err) + if data == nil { return nil } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 4c817b1bc6fb..0f6bce3b8171 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -331,7 +331,11 @@ func (sf *subfetcher) loop() { if _, ok := sf.seen[string(task)]; ok { sf.dups++ } else { - sf.trie.TryGet(task) + if len(task) == len(common.Address{}) { + sf.trie.TryGetAccount(task) + } else { + sf.trie.TryGet(task) + } sf.seen[string(task)] = struct{}{} } } diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 7ecf041e9a54..6cee7e2e85af 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -419,8 +418,9 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if err != nil { return nil, nil } - var acc types.StateAccount - if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil { + var acc *types.StateAccount + acc, err = accTrie.TryGetAccount(account[:]) + if err != nil { return nil, nil } stTrie, err := trie.New(account, acc.Root, chain.StateCache().TrieDB()) diff --git a/light/trie.go b/light/trie.go index 931ba30cb40a..f3045a02b7f3 100644 --- a/light/trie.go +++ b/light/trie.go @@ -112,6 +112,16 @@ func (t *odrTrie) TryGet(key []byte) ([]byte, error) { return res, err } +func (t *odrTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { + key = crypto.Keccak256(key) + var res *types.StateAccount + err := t.do(key, func() (err error) { + res, err = t.trie.TryGetAccount(key) + return err + }) + return res, err +} + func (t *odrTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { key = crypto.Keccak256(key) value, err := rlp.EncodeToBytes(acc) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 967194df9628..8a27d2186874 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -82,6 +82,10 @@ func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { return t.trie.TryGet(t.hashKey(key)) } +func (t *SecureTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { + return t.trie.TryGetAccount(t.hashKey(key)) +} + // TryGetNode 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. func (t *SecureTrie) TryGetNode(path []byte) ([]byte, int, error) { @@ -96,7 +100,7 @@ func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error if err != nil { return err } - if err := t.trie.TryUpdate(hk, data); err != nil { + if err := t.trie.tryUpdate(hk, data); err != nil { return err } t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) diff --git a/trie/trie.go b/trie/trie.go index 1e168402ad95..20e49f109afb 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -154,6 +154,23 @@ func (t *Trie) Get(key []byte) []byte { return res } +func (t *Trie) TryGetAccount(key []byte) (*types.StateAccount, error) { + res, newroot, didResolve, err := t.tryGet(t.root, keybytesToHex(key), 0) + if err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + return &types.StateAccount{}, err + } + if didResolve { + t.root = newroot + } + if res == nil { + return nil, nil + } + var ret types.StateAccount + err = rlp.DecodeBytes(res, &ret) + return &ret, err +} + // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. @@ -295,7 +312,9 @@ func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { if err != nil { return fmt.Errorf("can't encode object at %x: %w", key[:], err) } - return t.TryUpdate(key, data) + // Encoding []byte cannot fail, ok to ignore the error. + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(data[:])) + return t.tryUpdate(key, v) } // TryUpdate associates key with value in the trie. Subsequent calls to @@ -307,6 +326,12 @@ func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { // // If a node was not found in the database, a MissingNodeError is returned. func (t *Trie) TryUpdate(key, value []byte) error { + return t.tryUpdate(key, value) +} + +// tryUpdate expects an RLP-encoded value and performs the core function +// for TryUpdate and TryUpdateAccount. +func (t *Trie) tryUpdate(key, value []byte) error { t.unhashed++ k := keybytesToHex(key) if len(value) != 0 { From 7b4c18596b8b8dc95e59b8c9fd2740466b5f54f7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 2 Aug 2022 11:47:56 +0200 Subject: [PATCH 2/7] Gary's review feedback --- core/state/database.go | 1 + eth/protocols/snap/handler.go | 4 +--- trie/secure_trie.go | 2 +- trie/trie.go | 9 ++------- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index d069dee4282a..d04830787234 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -71,6 +71,7 @@ type Trie interface { // trie.MissingNodeError is returned. TryGet(key []byte) ([]byte, error) + // TryUpdateAccount abstract an account read from the trie. TryGetAccount(key []byte) (*types.StateAccount, error) // TryUpdateAccount abstract an account write to the trie. diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 6cee7e2e85af..c2d05e19ed31 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -418,8 +417,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if err != nil { return nil, nil } - var acc *types.StateAccount - acc, err = accTrie.TryGetAccount(account[:]) + acc, err := accTrie.TryGetAccount(account[:]) if err != nil { return nil, nil } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 8a27d2186874..f50d199c9709 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -100,7 +100,7 @@ func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error if err != nil { return err } - if err := t.trie.tryUpdate(hk, data); err != nil { + if err := t.trie.TryUpdate(hk, data); err != nil { return err } t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) diff --git a/trie/trie.go b/trie/trie.go index 20e49f109afb..bdf5f2d97e08 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -155,14 +155,11 @@ func (t *Trie) Get(key []byte) []byte { } func (t *Trie) TryGetAccount(key []byte) (*types.StateAccount, error) { - res, newroot, didResolve, err := t.tryGet(t.root, keybytesToHex(key), 0) + res, err := t.TryGet(key) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) return &types.StateAccount{}, err } - if didResolve { - t.root = newroot - } if res == nil { return nil, nil } @@ -312,9 +309,7 @@ func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { if err != nil { return fmt.Errorf("can't encode object at %x: %w", key[:], err) } - // Encoding []byte cannot fail, ok to ignore the error. - v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(data[:])) - return t.tryUpdate(key, v) + return t.tryUpdate(key, data) } // TryUpdate associates key with value in the trie. Subsequent calls to From ff32da7a6bdfb863b909039a2bf89d575ab87924 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 3 Aug 2022 19:56:12 +0200 Subject: [PATCH 3/7] implement Gary's suggestion --- core/state/database.go | 4 +- core/state/snapshot/generate_test.go | 2 +- eth/protocols/snap/handler.go | 6 +-- light/trie.go | 14 ++++-- trie/iterator_test.go | 2 +- trie/proof.go | 2 +- trie/secure_trie.go | 64 ++++++++++++++++------------ trie/secure_trie_test.go | 12 +++--- trie/sync_test.go | 2 +- trie/trie.go | 24 ----------- 10 files changed, 62 insertions(+), 70 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index d04830787234..3a81034c5cea 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -63,7 +63,7 @@ type Trie interface { // GetKey returns the sha3 preimage of a hashed key that was previously used // to store a value. // - // TODO(fjl): remove this when SecureTrie is removed + // TODO(fjl): remove this when StateTrie is removed GetKey([]byte) []byte // TryGet returns the value for key stored in the trie. The value bytes must @@ -155,7 +155,7 @@ func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { // CopyTrie returns an independent copy of the given trie. func (db *cachingDB) CopyTrie(t Trie) Trie { switch t := t.(type) { - case *trie.SecureTrie: + case *trie.StateTrie: return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index fe81993e9d2f..44c0545b322c 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -142,7 +142,7 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) { type testHelper struct { diskdb ethdb.Database triedb *trie.Database - accTrie *trie.SecureTrie + accTrie *trie.StateTrie } func newHelper() *testHelper { diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index c2d05e19ed31..2f5c59486249 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -413,15 +413,15 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if origin != (common.Hash{}) || (abort && len(storage) > 0) { // Request started at a non-zero hash or was capped prematurely, add // the endpoint Merkle proofs - accTrie, err := trie.New(common.Hash{}, req.Root, chain.StateCache().TrieDB()) + accTrie, err := trie.NewSecure(common.Hash{}, req.Root, chain.StateCache().TrieDB()) if err != nil { return nil, nil } acc, err := accTrie.TryGetAccount(account[:]) - if err != nil { + if err != nil || acc == nil { return nil, nil } - stTrie, err := trie.New(account, acc.Root, chain.StateCache().TrieDB()) + stTrie, err := trie.NewSecure(account, acc.Root, chain.StateCache().TrieDB()) if err != nil { return nil, nil } diff --git a/light/trie.go b/light/trie.go index f3045a02b7f3..c6348842687e 100644 --- a/light/trie.go +++ b/light/trie.go @@ -114,12 +114,18 @@ func (t *odrTrie) TryGet(key []byte) ([]byte, error) { func (t *odrTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { key = crypto.Keccak256(key) - var res *types.StateAccount + var res types.StateAccount err := t.do(key, func() (err error) { - res, err = t.trie.TryGetAccount(key) - return err + value, err := t.trie.TryGet(key) + if err != nil { + return err + } + if value == nil { + return nil + } + return rlp.DecodeBytes(value, &res) }) - return res, err + return &res, err } func (t *odrTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { diff --git a/trie/iterator_test.go b/trie/iterator_test.go index e3e6d0e3a8fa..57c8a8ad035f 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -509,7 +509,7 @@ func (l *loggingDb) Close() error { } // makeLargeTestTrie create a sample test trie -func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) { +func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} triedb := NewDatabase(logDb) diff --git a/trie/proof.go b/trie/proof.go index 9bf9107562fa..46de53de0b83 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -94,7 +94,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e // 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 *SecureTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { +func (t *StateTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { return t.trie.Prove(key, fromLevel, proofDb) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index f50d199c9709..bd60ca06aaf7 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -25,22 +25,22 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// SecureTrie wraps a trie with key hashing. In a secure trie, all +// StateTrie wraps a trie with key hashing. In a secure 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 SecureTrie can only be created with +// 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. // -// SecureTrie is not safe for concurrent use. -type SecureTrie struct { +// StateTrie is not safe for concurrent use. +type StateTrie struct { trie Trie preimages *preimageStore hashKeyBuf [common.HashLength]byte secKeyCache map[string][]byte - secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch + secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch } // NewSecure creates a trie with an existing root node from a backing database @@ -54,7 +54,7 @@ type SecureTrie struct { // Loaded nodes are kept around until their 'cache generation' expires. // A new cache generation is created by each call to Commit. // cachelimit sets the number of past cache generations to keep. -func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { +func NewSecure(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) { if db == nil { panic("trie.NewSecure called without a database") } @@ -62,12 +62,12 @@ func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, if err != nil { return nil, err } - return &SecureTrie{trie: *trie, preimages: db.preimages}, nil + return &StateTrie{trie: *trie, preimages: db.preimages}, nil } // Get returns the value for key stored in the trie. // The value bytes must not be modified by the caller. -func (t *SecureTrie) Get(key []byte) []byte { +func (t *StateTrie) Get(key []byte) []byte { res, err := t.TryGet(key) if err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) @@ -78,23 +78,33 @@ func (t *SecureTrie) Get(key []byte) []byte { // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { +func (t *StateTrie) TryGet(key []byte) ([]byte, error) { return t.trie.TryGet(t.hashKey(key)) } -func (t *SecureTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { - return t.trie.TryGetAccount(t.hashKey(key)) +func (t *StateTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { + var ret types.StateAccount + res, err := t.trie.TryGet(t.hashKey(key)) + if err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + return &ret, err + } + if res == nil { + return nil, nil + } + err = rlp.DecodeBytes(res, &ret) + return &ret, err } // TryGetNode 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. -func (t *SecureTrie) TryGetNode(path []byte) ([]byte, int, error) { +func (t *StateTrie) TryGetNode(path []byte) ([]byte, int, error) { return t.trie.TryGetNode(path) } // TryUpdateAccount account will abstract the write of an account to the // secure trie. -func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { +func (t *StateTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { hk := t.hashKey(key) data, err := rlp.EncodeToBytes(acc) if err != nil { @@ -113,7 +123,7 @@ func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error // // The value bytes must not be modified by the caller while they are // stored in the trie. -func (t *SecureTrie) Update(key, value []byte) { +func (t *StateTrie) Update(key, value []byte) { if err := t.TryUpdate(key, value); err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) } @@ -127,7 +137,7 @@ func (t *SecureTrie) Update(key, value []byte) { // stored in the trie. // // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryUpdate(key, value []byte) error { +func (t *StateTrie) TryUpdate(key, value []byte) error { hk := t.hashKey(key) err := t.trie.TryUpdate(hk, value) if err != nil { @@ -138,7 +148,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { } // Delete removes any existing value for key from the trie. -func (t *SecureTrie) Delete(key []byte) { +func (t *StateTrie) Delete(key []byte) { if err := t.TryDelete(key); err != nil { log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) } @@ -146,7 +156,7 @@ func (t *SecureTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. -func (t *SecureTrie) TryDelete(key []byte) error { +func (t *StateTrie) TryDelete(key []byte) error { hk := t.hashKey(key) delete(t.getSecKeyCache(), string(hk)) return t.trie.TryDelete(hk) @@ -154,7 +164,7 @@ func (t *SecureTrie) TryDelete(key []byte) error { // GetKey returns the sha3 preimage of a hashed key that was // previously used to store a value. -func (t *SecureTrie) GetKey(shaKey []byte) []byte { +func (t *StateTrie) GetKey(shaKey []byte) []byte { if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { return key } @@ -169,7 +179,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { // // Committing flushes nodes from memory. Subsequent Get calls will load nodes // from the database. -func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) { +func (t *StateTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { if t.preimages != nil { @@ -185,15 +195,15 @@ func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) { return t.trie.Commit(onleaf) } -// Hash returns the root hash of SecureTrie. It does not write to the +// 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 *SecureTrie) Hash() common.Hash { +func (t *StateTrie) Hash() common.Hash { return t.trie.Hash() } -// Copy returns a copy of SecureTrie. -func (t *SecureTrie) Copy() *SecureTrie { - return &SecureTrie{ +// Copy returns a copy of StateTrie. +func (t *StateTrie) Copy() *StateTrie { + return &StateTrie{ trie: *t.trie.Copy(), preimages: t.preimages, secKeyCache: t.secKeyCache, @@ -202,14 +212,14 @@ func (t *SecureTrie) Copy() *SecureTrie { // NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration // starts at the key after the given start key. -func (t *SecureTrie) NodeIterator(start []byte) NodeIterator { +func (t *StateTrie) NodeIterator(start []byte) NodeIterator { return t.trie.NodeIterator(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 *SecureTrie) hashKey(key []byte) []byte { +func (t *StateTrie) hashKey(key []byte) []byte { h := newHasher(false) h.sha.Reset() h.sha.Write(key) @@ -221,7 +231,7 @@ func (t *SecureTrie) hashKey(key []byte) []byte { // 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 *SecureTrie) getSecKeyCache() map[string][]byte { +func (t *StateTrie) getSecKeyCache() map[string][]byte { if t != t.secKeyCacheOwner { t.secKeyCacheOwner = t t.secKeyCache = make(map[string][]byte) diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index beea5845ad0d..a70e9dac9c60 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -27,13 +27,13 @@ import ( "github.com/ethereum/go-ethereum/ethdb/memorydb" ) -func newEmptySecure() *SecureTrie { +func newEmptySecure() *StateTrie { trie, _ := NewSecure(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New())) return trie } -// makeTestSecureTrie creates a large enough secure trie for testing. -func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { +// makeTestStateTrie creates a large enough secure trie for testing. +func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) @@ -105,12 +105,12 @@ func TestSecureGetKey(t *testing.T) { } } -func TestSecureTrieConcurrency(t *testing.T) { +func TestStateTrieConcurrency(t *testing.T) { // Create an initial trie and copy if for concurrent access - _, trie, _ := makeTestSecureTrie() + _, trie, _ := makeTestStateTrie() threads := runtime.NumCPU() - tries := make([]*SecureTrie, threads) + tries := make([]*StateTrie, threads) for i := 0; i < threads; i++ { tries[i] = trie.Copy() } diff --git a/trie/sync_test.go b/trie/sync_test.go index 472c31a63b9b..95b77fbf6b55 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -26,7 +26,7 @@ import ( ) // makeTestTrie create a sample test trie to test node-wise reconstruction. -func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) { +func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) diff --git a/trie/trie.go b/trie/trie.go index bdf5f2d97e08..0715e65ae5e4 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -25,10 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -154,20 +152,6 @@ func (t *Trie) Get(key []byte) []byte { return res } -func (t *Trie) TryGetAccount(key []byte) (*types.StateAccount, error) { - res, err := t.TryGet(key) - if err != nil { - log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) - return &types.StateAccount{}, err - } - if res == nil { - return nil, nil - } - var ret types.StateAccount - err = rlp.DecodeBytes(res, &ret) - return &ret, err -} - // TryGet returns the value for key stored in the trie. // The value bytes must not be modified by the caller. // If a node was not found in the database, a MissingNodeError is returned. @@ -304,14 +288,6 @@ func (t *Trie) Update(key, value []byte) { } } -func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error { - data, err := rlp.EncodeToBytes(acc) - if err != nil { - return fmt.Errorf("can't encode object at %x: %w", key[:], err) - } - return t.tryUpdate(key, data) -} - // TryUpdate 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. From 6ce8351137d295ce24d40dd8e7baf1cfdbe7f67a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 4 Aug 2022 09:15:54 +0200 Subject: [PATCH 4/7] fix bug + rename NewSecure into NewStateTrie --- cmd/geth/snapshot.go | 8 ++++---- core/blockchain.go | 2 +- core/state/database.go | 4 ++-- core/state/pruner/pruner.go | 4 ++-- core/state/snapshot/generate_test.go | 4 ++-- eth/api.go | 4 ++-- eth/protocols/snap/handler.go | 10 +++++----- eth/protocols/snap/sync_test.go | 2 +- les/downloader/downloader_test.go | 2 +- trie/iterator_test.go | 2 +- trie/secure_trie.go | 21 +++++++++++++++++++-- trie/secure_trie_test.go | 4 ++-- trie/sync_test.go | 6 +++--- 13 files changed, 45 insertions(+), 28 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index a218ae9cd292..39bef1f2d352 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -271,7 +271,7 @@ func traverseState(ctx *cli.Context) error { log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) - t, err := trie.NewSecure(common.Hash{}, root, triedb) + t, err := trie.NewStateTrie(common.Hash{}, root, triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err @@ -292,7 +292,7 @@ func traverseState(ctx *cli.Context) error { return err } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.Key), acc.Root, triedb) + storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.Key), acc.Root, triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return err @@ -360,7 +360,7 @@ func traverseRawState(ctx *cli.Context) error { log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) - t, err := trie.NewSecure(common.Hash{}, root, triedb) + t, err := trie.NewStateTrie(common.Hash{}, root, triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) return err @@ -406,7 +406,7 @@ func traverseRawState(ctx *cli.Context) error { return errors.New("invalid account") } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, triedb) + storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.LeafKey()), acc.Root, triedb) if err != nil { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return errors.New("missing storage trie") diff --git a/core/blockchain.go b/core/blockchain.go index 506034b539a7..92f220c9eceb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -706,7 +706,7 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x..]", hash[:4]) } - if _, err := trie.NewSecure(common.Hash{}, block.Root(), bc.stateCache.TrieDB()); err != nil { + if _, err := trie.NewStateTrie(common.Hash{}, block.Root(), bc.stateCache.TrieDB()); err != nil { return err } diff --git a/core/state/database.go b/core/state/database.go index 3a81034c5cea..8212ae17be5b 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -136,7 +136,7 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(common.Hash{}, root, db.db) + tr, err := trie.NewStateTrie(common.Hash{}, root, db.db) if err != nil { return nil, err } @@ -145,7 +145,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - tr, err := trie.NewSecure(addrHash, root, db.db) + tr, err := trie.NewStateTrie(addrHash, root, db.db) if err != nil { return nil, err } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 2f4b068d88f3..2da2eda8b74d 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -410,7 +410,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if genesis == nil { return errors.New("missing genesis block") } - t, err := trie.NewSecure(common.Hash{}, genesis.Root(), trie.NewDatabase(db)) + t, err := trie.NewStateTrie(common.Hash{}, genesis.Root(), trie.NewDatabase(db)) if err != nil { return err } @@ -430,7 +430,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { return err } if acc.Root != emptyRoot { - storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db)) + storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db)) if err != nil { return err } diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 44c0545b322c..529b02d44d80 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -148,7 +148,7 @@ type testHelper struct { func newHelper() *testHelper { diskdb := rawdb.NewMemoryDatabase() triedb := trie.NewDatabase(diskdb) - accTrie, _ := trie.NewSecure(common.Hash{}, common.Hash{}, triedb) + accTrie, _ := trie.NewStateTrie(common.Hash{}, common.Hash{}, triedb) return &testHelper{ diskdb: diskdb, triedb: triedb, @@ -180,7 +180,7 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) } func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string, vals []string, commit bool) []byte { - stTrie, _ := trie.NewSecure(owner, common.Hash{}, t.triedb) + stTrie, _ := trie.NewStateTrie(owner, common.Hash{}, t.triedb) for i, k := range keys { stTrie.Update([]byte(k), []byte(vals[i])) } diff --git a/eth/api.go b/eth/api.go index ad8566dae26a..2c7b0908ab38 100644 --- a/eth/api.go +++ b/eth/api.go @@ -506,11 +506,11 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c } triedb := api.eth.BlockChain().StateCache().TrieDB() - oldTrie, err := trie.NewSecure(common.Hash{}, startBlock.Root(), triedb) + oldTrie, err := trie.NewStateTrie(common.Hash{}, startBlock.Root(), triedb) if err != nil { return nil, err } - newTrie, err := trie.NewSecure(common.Hash{}, endBlock.Root(), triedb) + newTrie, err := trie.NewStateTrie(common.Hash{}, endBlock.Root(), triedb) if err != nil { return nil, err } diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 2f5c59486249..77bd96f46e8a 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -413,15 +413,15 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if origin != (common.Hash{}) || (abort && len(storage) > 0) { // Request started at a non-zero hash or was capped prematurely, add // the endpoint Merkle proofs - accTrie, err := trie.NewSecure(common.Hash{}, req.Root, chain.StateCache().TrieDB()) + accTrie, err := trie.NewStateTrie(common.Hash{}, req.Root, chain.StateCache().TrieDB()) if err != nil { return nil, nil } - acc, err := accTrie.TryGetAccount(account[:]) + acc, err := accTrie.TryGetAccountWithPreHashedKey(account[:]) if err != nil || acc == nil { return nil, nil } - stTrie, err := trie.NewSecure(account, acc.Root, chain.StateCache().TrieDB()) + stTrie, err := trie.NewStateTrie(account, acc.Root, chain.StateCache().TrieDB()) if err != nil { return nil, nil } @@ -487,7 +487,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s // Make sure we have the state associated with the request triedb := chain.StateCache().TrieDB() - accTrie, err := trie.NewSecure(common.Hash{}, req.Root, triedb) + accTrie, err := trie.NewStateTrie(common.Hash{}, req.Root, triedb) if err != nil { // We don't have the requested state available, bail out return nil, nil @@ -529,7 +529,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s if err != nil || account == nil { break } - stTrie, err := trie.NewSecure(common.BytesToHash(pathset[0]), common.BytesToHash(account.Root), triedb) + stTrie, err := trie.NewStateTrie(common.BytesToHash(pathset[0]), common.BytesToHash(account.Root), triedb) loads++ // always account database reads, even for failures if err != nil { break diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 85e4dc5e4f83..65d12e777f93 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -1606,7 +1606,7 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { } accounts++ if acc.Root != emptyRoot { - storeTrie, err := trie.NewSecure(common.BytesToHash(accIt.Key), acc.Root, triedb) + storeTrie, err := trie.NewStateTrie(common.BytesToHash(accIt.Key), acc.Root, triedb) if err != nil { t.Fatal(err) } diff --git a/les/downloader/downloader_test.go b/les/downloader/downloader_test.go index 792fc284619d..5eea49877969 100644 --- a/les/downloader/downloader_test.go +++ b/les/downloader/downloader_test.go @@ -229,7 +229,7 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block { func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { // For now only check that the state trie is correct if block := dl.GetBlockByHash(hash); block != nil { - _, err := trie.NewSecure(common.Hash{}, block.Root(), trie.NewDatabase(dl.stateDb)) + _, err := trie.NewStateTrie(common.Hash{}, block.Root(), trie.NewDatabase(dl.stateDb)) return err } return fmt.Errorf("non existent block: %x", hash[:4]) diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 57c8a8ad035f..bad3636a6a64 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -513,7 +513,7 @@ func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { // Create an empty trie logDb := &loggingDb{0, memorydb.New()} triedb := NewDatabase(logDb) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data for i := 0; i < 10000; i++ { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index bd60ca06aaf7..b25d382bf930 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -43,7 +43,7 @@ type StateTrie struct { secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch } -// NewSecure creates a trie with an existing root node from a backing database +// NewStateTrie creates a trie with an existing root node from a backing database // and optional intermediate in-memory node pool. // // If root is the zero hash or the sha3 hash of an empty string, the @@ -54,7 +54,7 @@ type StateTrie struct { // Loaded nodes are kept around until their 'cache generation' expires. // A new cache generation is created by each call to Commit. // cachelimit sets the number of past cache generations to keep. -func NewSecure(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) { +func NewStateTrie(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) { if db == nil { panic("trie.NewSecure called without a database") } @@ -96,6 +96,23 @@ func (t *StateTrie) TryGetAccount(key []byte) (*types.StateAccount, error) { return &ret, err } +// TryGetAccountWithPreHashedKey does the same thing as TryGetAccount, however +// it expects a key that is already hashed. This constitutes an abstraction leak, +// since the client code needs to know the key format. +func (t *StateTrie) TryGetAccountWithPreHashedKey(key []byte) (*types.StateAccount, error) { + var ret types.StateAccount + res, err := t.trie.TryGet(key) + if err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + return &ret, err + } + if res == nil { + return nil, nil + } + err = rlp.DecodeBytes(res, &ret) + return &ret, err +} + // TryGetNode 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. func (t *StateTrie) TryGetNode(path []byte) ([]byte, int, error) { diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index a70e9dac9c60..beb6b817e357 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -28,7 +28,7 @@ import ( ) func newEmptySecure() *StateTrie { - trie, _ := NewSecure(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New())) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New())) return trie } @@ -36,7 +36,7 @@ func newEmptySecure() *StateTrie { func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data content := make(map[string][]byte) diff --git a/trie/sync_test.go b/trie/sync_test.go index 95b77fbf6b55..620724dc09af 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -29,7 +29,7 @@ import ( func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie triedb := NewDatabase(memorydb.New()) - trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb) + trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb) // Fill it with some arbitrary data content := make(map[string][]byte) @@ -60,7 +60,7 @@ func makeTestTrie() (*Database, *StateTrie, map[string][]byte) { // content map. func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) { // Check root availability and trie contents - trie, err := NewSecure(common.Hash{}, common.BytesToHash(root), db) + trie, err := NewStateTrie(common.Hash{}, common.BytesToHash(root), db) if err != nil { t.Fatalf("failed to create trie at %x: %v", root, err) } @@ -77,7 +77,7 @@ func checkTrieContents(t *testing.T, db *Database, root []byte, content map[stri // checkTrieConsistency checks that all nodes in a trie are indeed present. func checkTrieConsistency(db *Database, root common.Hash) error { // Create and iterate a trie rooted in a subnode - trie, err := NewSecure(common.Hash{}, root, db) + trie, err := NewStateTrie(common.Hash{}, root, db) if err != nil { return nil // Consider a non existent state consistent } From c3f4718b6f80553f78f06f82c9f871b13d19e228 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 4 Aug 2022 10:16:22 +0200 Subject: [PATCH 5/7] trie: add backwards-compatibility aliases for SecureTrie --- trie/secure_trie.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index b25d382bf930..ea3a445802b7 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -25,6 +25,16 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +// SecureTrie is the old name of StateTrie. +// Deprecated: use StateTrie. +type SecureTrie = StateTrie + +// NewSecure creates a new StateTrie. +// Deprecated: use NewStateTrie. +func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) { + return NewStateTrie(owner, root, db) +} + // StateTrie wraps a trie with key hashing. In a secure trie, all // access operations hash the key using keccak256. This prevents // calling code from creating long chains of nodes that From c431cb027f5767e44c63935bb6854f8e913931c4 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Thu, 4 Aug 2022 16:49:22 +0800 Subject: [PATCH 6/7] Update database.go --- core/state/database.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 8212ae17be5b..37a11bfa2935 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -71,17 +71,17 @@ type Trie interface { // trie.MissingNodeError is returned. TryGet(key []byte) ([]byte, error) - // TryUpdateAccount abstract an account read from the trie. + // TryGetAccount abstract an account read from the trie. TryGetAccount(key []byte) (*types.StateAccount, error) - // TryUpdateAccount abstract an account write to the trie. - TryUpdateAccount(key []byte, account *types.StateAccount) error - // TryUpdate associates key with value in the trie. If value has length zero, any // existing value is deleted from the trie. The value bytes must not be modified // by the caller while they are stored in the trie. If a node was not found in the // database, a trie.MissingNodeError is returned. TryUpdate(key, value []byte) error + + // TryUpdateAccount abstract an account write to the trie. + TryUpdateAccount(key []byte, account *types.StateAccount) error // TryDelete removes any existing value for key from the trie. If a node was not // found in the database, a trie.MissingNodeError is returned. From 70925c62e2193ea1cd56c4c685350620c3087ec8 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 4 Aug 2022 14:01:59 +0200 Subject: [PATCH 7/7] make the linter happy --- core/state/database.go | 2 +- trie/trie.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 4030dee958fc..edbf78ae311a 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -79,7 +79,7 @@ type Trie interface { // by the caller while they are stored in the trie. If a node was not found in the // database, a trie.MissingNodeError is returned. TryUpdate(key, value []byte) error - + // TryUpdateAccount abstract an account write to the trie. TryUpdateAccount(key []byte, account *types.StateAccount) error diff --git a/trie/trie.go b/trie/trie.go index 7aba29cca891..9274d88380cc 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -23,7 +23,6 @@ import ( "fmt" "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" )