From cb888541c5c3aae0a0fa24aeff04e5573248c5ac Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Mon, 4 Aug 2025 11:19:42 +0800 Subject: [PATCH] core, trie: add state metrics #23433 (#1071) --- XDCx/tradingstate/XDCx_trie.go | 5 ++- XDCxlending/lendingstate/XDCx_trie.go | 5 ++- core/state/database.go | 2 +- core/state/metrics.go | 28 ++++++++++++ core/state/state_object.go | 17 ++++---- core/state/statedb.go | 32 ++++++++++++-- trie/committer.go | 63 +++++++++++++++------------ trie/iterator_test.go | 2 +- trie/proof.go | 2 +- trie/secure_trie.go | 2 +- trie/trie.go | 13 +++--- trie/trie_test.go | 14 +++--- 12 files changed, 123 insertions(+), 62 deletions(-) create mode 100644 core/state/metrics.go diff --git a/XDCx/tradingstate/XDCx_trie.go b/XDCx/tradingstate/XDCx_trie.go index d9ed255b96..5d7da2b615 100644 --- a/XDCx/tradingstate/XDCx_trie.go +++ b/XDCx/tradingstate/XDCx_trie.go @@ -155,7 +155,7 @@ func (t *XDCXTrie) GetKey(shaKey []byte) []byte { // // Committing flushes nodes from memory. Subsequent Get calls will load nodes // from the database. -func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) { +func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { t.trie.Db.Lock.Lock() @@ -167,7 +167,8 @@ func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate node database - return t.trie.Commit(onleaf) + root, _, err := t.trie.Commit(onleaf) + return root, err } func (t *XDCXTrie) Hash() common.Hash { diff --git a/XDCxlending/lendingstate/XDCx_trie.go b/XDCxlending/lendingstate/XDCx_trie.go index 600c4c0d6e..82cbc6ddca 100644 --- a/XDCxlending/lendingstate/XDCx_trie.go +++ b/XDCxlending/lendingstate/XDCx_trie.go @@ -151,7 +151,7 @@ func (t *XDCXTrie) GetKey(shaKey []byte) []byte { // // Committing flushes nodes from memory. Subsequent Get calls will load nodes // from the database. -func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) { +func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (common.Hash, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { t.trie.Db.Lock.Lock() @@ -163,7 +163,8 @@ func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate node database - return t.trie.Commit(onleaf) + root, _, err := t.trie.Commit(onleaf) + return root, err } func (t *XDCXTrie) Hash() common.Hash { diff --git a/core/state/database.go b/core/state/database.go index cd3f8665cf..b429f9dbe0 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -85,7 +85,7 @@ type Trie interface { // Commit writes all nodes to the trie's memory database, tracking the internal // and external (for account tries) references. - Commit(onleaf trie.LeafCallback) (common.Hash, error) + Commit(onleaf trie.LeafCallback) (common.Hash, int, error) // NodeIterator returns an iterator that returns nodes of the trie. Iteration // starts at the key after the given start key. diff --git a/core/state/metrics.go b/core/state/metrics.go new file mode 100644 index 0000000000..71640575ba --- /dev/null +++ b/core/state/metrics.go @@ -0,0 +1,28 @@ +// Copyright 2021 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 state + +import "github.com/XinFinOrg/XDPoSChain/metrics" + +var ( + accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) + storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) + accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) + storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) + accountCommittedMeter = metrics.NewRegisteredMeter("state/commit/account", nil) + storageCommittedMeter = metrics.NewRegisteredMeter("state/commit/storage", nil) +) diff --git a/core/state/state_object.go b/core/state/state_object.go index e7ef84879c..bcbef324ae 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -296,11 +296,13 @@ func (s *stateObject) updateTrie(db Database) Trie { if (value == common.Hash{}) { s.setError(tr.TryDelete(key[:])) + s.db.StorageDeleted += 1 continue } // Encoding []byte cannot fail, ok to ignore the error. v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) s.setError(tr.TryUpdate(key[:], v)) + s.db.StorageUpdated += 1 } if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) @@ -320,24 +322,23 @@ func (s *stateObject) updateRoot(db Database) { s.data.Root = s.trie.Hash() } -// commitTrie submits the storage changes into the storage trie and re-computes -// the root. Besides, all trie changes will be collected in a nodeset and returned. -func (s *stateObject) commitTrie(db Database) error { +// CommitTrie the storage trie of the object to dwb. +// This updates the trie root. +func (s *stateObject) commitTrie(db Database) (int, error) { // If nothing changed, don't bother with hashing anything if s.updateTrie(db) == nil { - return nil + return 0, nil } if s.dbErr != nil { - return s.dbErr + return 0, s.dbErr } // Track the amount of time wasted on committing the storage trie defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) - - root, err := s.trie.Commit(nil) + root, committed, err := s.trie.Commit(nil) if err == nil { s.data.Root = root } - return err + return committed, err } // AddBalance removes amount from c's balance. diff --git a/core/state/statedb.go b/core/state/statedb.go index a06e016a22..3a1f1ee12d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -92,6 +92,11 @@ type StateDB struct { StorageHashes time.Duration StorageUpdates time.Duration StorageCommits time.Duration + + AccountUpdated int + StorageUpdated int + AccountDeleted int + StorageDeleted int } type AccountInfo struct { @@ -772,9 +777,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { obj := s.stateObjects[addr] if obj.deleted { s.deleteStateObject(obj) + s.AccountDeleted += 1 } else { obj.updateRoot(s.db) s.updateStateObject(obj) + s.AccountUpdated += 1 } } if len(s.stateObjectsPending) > 0 { @@ -809,6 +816,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { s.stateObjectsDirty[addr] = struct{}{} } // Commit objects to the trie, measuring the elapsed time + var storageCommitted int codeWriter := s.db.TrieDB().DiskDB().NewBatch() for addr := range s.stateObjectsDirty { if obj := s.stateObjects[addr]; !obj.deleted { @@ -818,9 +826,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { obj.dirtyCode = false } // Write any storage changes in the state object to its storage trie. - if err := obj.commitTrie(s.db); err != nil { + committed, err := obj.commitTrie(s.db) + if err != nil { return common.Hash{}, err } + storageCommitted += committed } // If the contract is destructed, the storage is still left in the // database as dangling data. Theoretically it's should be wiped from @@ -838,9 +848,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } } // Write the account trie changes, measuing the amount of wasted time - defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now()) + start := time.Now() - return s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { + root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { var account Account if err := rlp.DecodeBytes(leaf, &account); err != nil { return nil @@ -850,6 +860,22 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } return nil }) + if err != nil { + return common.Hash{}, err + } + // Report the commit metrics + s.AccountCommits += time.Since(start) + + accountUpdatedMeter.Mark(int64(s.AccountUpdated)) + storageUpdatedMeter.Mark(int64(s.StorageUpdated)) + accountDeletedMeter.Mark(int64(s.AccountDeleted)) + storageDeletedMeter.Mark(int64(s.StorageDeleted)) + accountCommittedMeter.Mark(int64(accountCommitted)) + storageCommittedMeter.Mark(int64(storageCommitted)) + s.AccountUpdated, s.AccountDeleted = 0, 0 + s.StorageUpdated, s.StorageDeleted = 0, 0 + + return root, err } // Prepare handles the preparatory steps for executing a state transition with. diff --git a/trie/committer.go b/trie/committer.go index f5980cf00e..64fa32b13e 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -64,24 +64,24 @@ func returnCommitterToPool(h *committer) { committerPool.Put(h) } -// commit collapses a Node down into a hash Node and inserts it into the database -func (c *committer) Commit(n node, db *Database) (hashNode, error) { +// Commit collapses a node down into a hash node and inserts it into the database +func (c *committer) Commit(n node, db *Database) (hashNode, int, error) { if db == nil { - return nil, errors.New("no Db provided") + return nil, 0, errors.New("no Db provided") } - h, err := c.commit(n, db) + h, committed, err := c.commit(n, db) if err != nil { - return nil, err + return nil, 0, err } - return h.(hashNode), nil + return h.(hashNode), committed, nil } // commit collapses a Node down into a hash Node and inserts it into the database -func (c *committer) commit(n node, db *Database) (node, error) { +func (c *committer) commit(n node, db *Database) (node, int, error) { // if this path is clean, use available cached data hash, dirty := n.cache() if hash != nil && !dirty { - return hash, nil + return hash, 0, nil } // Commit children, then parent, and remove remove the dirty flag. switch cn := n.(type) { @@ -89,37 +89,38 @@ func (c *committer) commit(n node, db *Database) (node, error) { // Commit child collapsed := cn.copy() - // If the child is fullnode, recursively commit. - // Otherwise it can only be hashNode or valueNode. + // If the child is fullNode, recursively commit, + // otherwise it can only be hashNode or valueNode. + var childCommitted int if _, ok := cn.Val.(*fullNode); ok { - childV, err := c.commit(cn.Val, db) + childV, committed, err := c.commit(cn.Val, db) if err != nil { - return nil, err + return nil, 0, err } - collapsed.Val = childV + collapsed.Val, childCommitted = childV, committed } // The key needs to be copied, since we're delivering it to database collapsed.Key = hexToCompact(cn.Key) hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { - return hn, nil + return hn, childCommitted + 1, nil } - return collapsed, nil + return collapsed, childCommitted, nil case *fullNode: - hashedKids, err := c.commitChildren(cn, db) + hashedKids, childCommitted, err := c.commitChildren(cn, db) if err != nil { - return nil, err + return nil, 0, err } collapsed := cn.copy() collapsed.Children = hashedKids hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { - return hn, nil + return hn, childCommitted + 1, nil } - return collapsed, nil + return collapsed, childCommitted, nil case hashNode: - return cn, nil + return cn, 0, nil default: // nil, valuenode shouldn't be committed panic(fmt.Sprintf("%T: invalid node: %v", n, n)) @@ -127,8 +128,11 @@ func (c *committer) commit(n node, db *Database) (node, error) { } // commitChildren commits the children of the given fullnode -func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, error) { - var children [17]node +func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, int, error) { + var ( + committed int + children [17]node + ) for i := 0; i < 16; i++ { child := n.Children[i] if child == nil { @@ -136,25 +140,26 @@ func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, error) } // 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. + // 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. - hashed, err := c.commit(child, db) + // possible the type is not hashNode. + hashed, childCommitted, err := c.commit(child, db) if err != nil { - return children, err + return children, 0, err } children[i] = hashed + committed += childCommitted } // For the 17th child, it's possible the type is valuenode. if n.Children[16] != nil { children[16] = n.Children[16] } - return children, nil + return children, committed, nil } // store hashes the Node n and if we have a storage layer specified, it writes @@ -168,7 +173,7 @@ func (c *committer) store(n node, db *Database) node { ) if hash == nil { // This was not generated - must be a small node stored in the parent. - // In theory we should apply the leafCall here if it's not nil(embedded + // In theory, we should apply the leafCall here if it's not nil(embedded // node usually contains value). But small value(less than 32bytes) is // not our target. return n @@ -216,7 +221,7 @@ func (c *committer) commitLoop(db *Database) { } case *fullNode: // For children in range [0, 15], it's impossible - // to contain valuenode. Only check the 17th child. + // to contain valueNode. Only check the 17th child. if n.Children[16] != nil { c.onleaf(nil, nil, n.Children[16].(valueNode), hash) } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index b2b42349a5..a9cb26aede 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -407,7 +407,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { for _, val := range testdata1 { ctr.Update([]byte(val.k), []byte(val.v)) } - root, _ := ctr.Commit(nil) + root, _, _ := ctr.Commit(nil) if !memonly { triedb.Commit(root, true) } diff --git a/trie/proof.go b/trie/proof.go index 1ef549f0ef..8a8132c266 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -575,7 +575,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) } // Proof seems valid, serialize all the nodes into the database - if _, err := tr.Commit(nil); err != nil { + if _, _, err := tr.Commit(nil); err != nil { return nil, false, err } if err := triedb.Commit(rootHash, false); err != nil { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 602f25b5fa..46bcf99b51 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -144,7 +144,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) (root common.Hash, err error) { +func (t *SecureTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { if t.trie.Db.preimages != nil { // Ugly direct check but avoids the below write lock diff --git a/trie/trie.go b/trie/trie.go index e2f7e67e76..cffcfb6c4b 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -670,12 +670,12 @@ func (t *Trie) Hash() common.Hash { // Commit writes all nodes to the trie's memory database, tracking the internal // and external (for account tries) references. -func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { +func (t *Trie) Commit(onleaf LeafCallback) (common.Hash, int, error) { if t.Db == nil { panic("commit called on trie with nil database") } if t.root == nil { - return types.EmptyRootHash, nil + return types.EmptyRootHash, 0, nil } // Derive the hash for all dirty nodes first. We hold the assumption // in the following procedure that all nodes are hashed. @@ -687,7 +687,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { // up goroutines. This can happen e.g. if we load a trie for reading storage // values, but don't write to it. if _, dirty := t.root.cache(); !dirty { - return rootHash, nil + return rootHash, 0, nil } var wg sync.WaitGroup if onleaf != nil { @@ -699,8 +699,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { h.commitLoop(t.Db) }() } - var newRoot hashNode - newRoot, err = h.Commit(t.root, t.Db) + newRoot, committed, err := h.Commit(t.root, t.Db) if onleaf != nil { // The leafch is created in newCommitter if there was an onleaf callback // provided. The commitLoop only _reads_ from it, and the commit @@ -710,10 +709,10 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { wg.Wait() } if err != nil { - return common.Hash{}, err + return common.Hash{}, 0, err } t.root = newRoot - return rootHash, nil + return rootHash, committed, nil } // hashRoot calculates the root hash of the given trie diff --git a/trie/trie_test.go b/trie/trie_test.go index e6d539911d..babb327621 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -90,7 +90,7 @@ func testMissingNode(t *testing.T, memonly bool) { trie, _ := New(common.Hash{}, triedb) updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") - root, _ := trie.Commit(nil) + root, _, _ := trie.Commit(nil) if !memonly { triedb.Commit(root, true) } @@ -172,7 +172,7 @@ func TestInsert(t *testing.T) { updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") - root, err := trie.Commit(nil) + root, _, err := trie.Commit(nil) if err != nil { t.Fatalf("commit error: %v", err) } @@ -390,11 +390,11 @@ func runRandTest(rt randTest) error { rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) } case opCommit: - _, rt[i].err = tr.Commit(nil) + _, _, rt[i].err = tr.Commit(nil) case opHash: tr.Hash() case opReset: - hash, err := tr.Commit(nil) + hash, _, err := tr.Commit(nil) if err != nil { rt[i].err = err return err @@ -521,7 +521,7 @@ func TestCommitAfterHash(t *testing.T) { if exp != root { t.Errorf("got %x, exp %x", root, exp) } - root, _ = trie.Commit(nil) + root, _, _ = trie.Commit(nil) if exp != root { t.Errorf("got %x, exp %x", root, exp) } @@ -667,7 +667,7 @@ func TestCommitSequenceStackTrie(t *testing.T) { stTrie.TryUpdate(key, val) } // Flush trie -> database - root, _ := trie.Commit(nil) + root, _, _ := trie.Commit(nil) // Flush memdb -> disk (sponge) db.Commit(root, false) // And flush stacktrie -> disk @@ -712,7 +712,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) { trie.TryUpdate(key, []byte{0x1}) stTrie.TryUpdate(key, []byte{0x1}) // Flush trie -> database - root, _ := trie.Commit(nil) + root, _, _ := trie.Commit(nil) // Flush memdb -> disk (sponge) db.Commit(root, false) // And flush stacktrie -> disk