From fabfd1c4855f026c004ae8d0db498cb28c755d81 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Tue, 24 Feb 2026 16:19:17 +0800 Subject: [PATCH] perf(core): track state change set with account address #27815 (#2076) --- core/state/journal.go | 8 +-- core/state/state_object.go | 4 +- core/state/statedb.go | 90 ++++++++++++++++++--------------- core/state/statedb_fuzz_test.go | 23 +++++---- trie/triestate/state.go | 6 +-- 5 files changed, 70 insertions(+), 61 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index 87edf237ea..ec7ea43d74 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -203,14 +203,14 @@ func (ch resetObjectChange) revert(s *StateDB) { delete(s.storages, ch.prev.addrHash) } if ch.prevAccountOriginExist { - s.accountsOrigin[ch.prev.addrHash] = ch.prevAccountOrigin + s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin } else { - delete(s.accountsOrigin, ch.prev.addrHash) + delete(s.accountsOrigin, ch.prev.address) } if ch.prevStorageOrigin != nil { - s.storagesOrigin[ch.prev.addrHash] = ch.prevStorageOrigin + s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin } else { - delete(s.storagesOrigin, ch.prev.addrHash) + delete(s.storagesOrigin, ch.prev.address) } } diff --git a/core/state/state_object.go b/core/state/state_object.go index 1bf8a01f6a..f2cdd71247 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -292,9 +292,9 @@ func (s *stateObject) updateTrie() (Trie, error) { // Cache the original value of mutated storage slots if origin == nil { - if origin = s.db.storagesOrigin[s.addrHash]; origin == nil { + if origin = s.db.storagesOrigin[s.address]; origin == nil { origin = make(map[common.Hash][]byte) - s.db.storagesOrigin[s.addrHash] = origin + s.db.storagesOrigin[s.address] = origin } } // Track the original value of slot only if it's mutated first time diff --git a/core/state/statedb.go b/core/state/statedb.go index 84bb04cda8..0e787ae252 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -65,10 +65,10 @@ type StateDB struct { // These maps hold the state changes (including the corresponding // original value) that occurred in this **block**. - accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding - storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format - accountsOrigin map[common.Hash][]byte // The original value of mutated accounts in 'slim RLP' encoding - storagesOrigin map[common.Hash]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format + accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding + storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format + accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding + storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format // This map holds 'live' objects, which will get modified while processing // a state transition. @@ -147,8 +147,8 @@ func New(root common.Hash, db Database) (*StateDB, error) { originalRoot: root, accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Hash][]byte), - storagesOrigin: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}), @@ -576,11 +576,11 @@ func (s *StateDB) updateStateObject(obj *stateObject) { // Track the original value of mutated account, nil means it was not present. // Skip if it has been tracked (because updateStateObject may be called // multiple times in a block). - if _, ok := s.accountsOrigin[obj.addrHash]; !ok { + if _, ok := s.accountsOrigin[obj.address]; !ok { if obj.origin == nil { - s.accountsOrigin[obj.addrHash] = nil + s.accountsOrigin[obj.address] = nil } else { - s.accountsOrigin[obj.addrHash] = types.SlimAccountRLP(*obj.origin) + s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin) } } } @@ -674,7 +674,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // There may be some cached account/storage data already since IntermediateRoot // will be called for each transaction before byzantium fork which will always // cache the latest account/storage data. - prevAccount, ok := s.accountsOrigin[prev.addrHash] + prevAccount, ok := s.accountsOrigin[prev.address] s.journal.append(resetObjectChange{ account: addr, prev: prev, @@ -683,12 +683,12 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) prevStorage: s.storages[prev.addrHash], prevAccountOriginExist: ok, prevAccountOrigin: prevAccount, - prevStorageOrigin: s.storagesOrigin[prev.addrHash], + prevStorageOrigin: s.storagesOrigin[prev.address], }) delete(s.accounts, prev.addrHash) delete(s.storages, prev.addrHash) - delete(s.accountsOrigin, prev.addrHash) - delete(s.storagesOrigin, prev.addrHash) + delete(s.accountsOrigin, prev.address) + delete(s.storagesOrigin, prev.address) } s.setStateObject(newobj) @@ -775,8 +775,8 @@ func (s *StateDB) Copy() *StateDB { originalRoot: s.originalRoot, accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Hash][]byte), - storagesOrigin: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), @@ -822,14 +822,20 @@ func (s *StateDB) Copy() *StateDB { } // Deep copy the destruction markers. for addr, value := range s.stateObjectsDestruct { - state.stateObjectsDestruct[addr] = value + if value == nil { + state.stateObjectsDestruct[addr] = nil + continue + } + cpy := new(types.StateAccount) + *cpy = *value + state.stateObjectsDestruct[addr] = cpy } // Deep copy the state changes made in the scope of block // along with their original values. - state.accounts = copyAccounts(s.accounts) - state.storages = copyStorages(s.storages) - state.accountsOrigin = copyAccounts(s.accountsOrigin) - state.storagesOrigin = copyStorages(s.storagesOrigin) + state.accounts = copySet(s.accounts) + state.storages = copy2DSet(s.storages) + state.accountsOrigin = copySet(s.accountsOrigin) + state.storagesOrigin = copy2DSet(s.storagesOrigin) // Deep copy the logs occurred in the scope of block for hash, logs := range s.logs { @@ -903,10 +909,10 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. - delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) - delete(s.accountsOrigin, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storagesOrigin, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) } else { obj.finalise() // Prefetch slots in the background } @@ -1041,8 +1047,8 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root // // In case (d), **original** account along with its storages should be deleted, // with their values be tracked as original value. -func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.Hash]struct{}, error) { - incomplete := make(map[common.Hash]struct{}) +func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.Address]struct{}, error) { + incomplete := make(map[common.Address]struct{}) for addr, prev := range s.stateObjectsDestruct { // The original account was non-existing, and it's marked as destructed // in the scope of block. It can be case (a) or (b). @@ -1052,12 +1058,12 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.H addrHash := crypto.Keccak256Hash(addr[:]) if prev == nil { if _, ok := s.accounts[addrHash]; ok { - s.accountsOrigin[addrHash] = nil // case (b) + s.accountsOrigin[addr] = nil // case (b) } continue } // It can overwrite the data in s.accountsOrigin set by 'updateStateObject'. - s.accountsOrigin[addrHash] = types.SlimAccountRLP(*prev) // case (c) or (d) + s.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d) // Short circuit if the storage was empty. if prev.Root == types.EmptyRootHash { @@ -1073,17 +1079,17 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.H // created. In this case, wipe the entire storage state diff because // of aborted deletion. if aborted { - incomplete[addrHash] = struct{}{} - delete(s.storagesOrigin, addrHash) + incomplete[addr] = struct{}{} + delete(s.storagesOrigin, addr) continue } - if s.storagesOrigin[addrHash] == nil { - s.storagesOrigin[addrHash] = slots + if s.storagesOrigin[addr] == nil { + s.storagesOrigin[addr] = slots } else { - // It can overwrite the data in s.storagesOrigin[addrHash] set by + // It can overwrite the data in s.storagesOrigin[addr] set by // 'object.updateTrie'. for key, val := range slots { - s.storagesOrigin[addrHash][key] = val + s.storagesOrigin[addr][key] = val } } if err := nodes.Merge(set); err != nil { @@ -1206,8 +1212,8 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // Clear all internal flags at the end of commit operation. s.accounts = make(map[common.Hash][]byte) s.storages = make(map[common.Hash]map[common.Hash][]byte) - s.accountsOrigin = make(map[common.Hash][]byte) - s.storagesOrigin = make(map[common.Hash]map[common.Hash][]byte) + s.accountsOrigin = make(map[common.Address][]byte) + s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) s.stateObjectsDirty = make(map[common.Address]struct{}) s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) return root, nil @@ -1297,18 +1303,18 @@ func (s *StateDB) GetOwner(candidate common.Address) common.Address { return common.HexToAddress(ret.Hex()) } -// 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)) +// copySet returns a deep-copied set. +func copySet[k comparable](set map[k][]byte) map[k][]byte { + copied := make(map[k][]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)) +// copy2DSet returns a two-dimensional deep-copied set. +func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.Hash][]byte { + copied := make(map[k]map[common.Hash][]byte, len(set)) for addr, subset := range set { copied[addr] = make(map[common.Hash][]byte, len(subset)) for key, val := range subset { diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index af2fb0f9b3..e3dea0cfc1 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -33,6 +33,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/trie" "github.com/XinFinOrg/XDPoSChain/trie/triestate" @@ -172,11 +173,11 @@ func (test *stateTest) String() string { func (test *stateTest) run() bool { var ( roots []common.Hash - accountList []map[common.Hash][]byte - storageList []map[common.Hash]map[common.Hash][]byte + accountList []map[common.Address][]byte + storageList []map[common.Address]map[common.Hash][]byte onCommit = func(states *triestate.Set) { - accountList = append(accountList, copyAccounts(states.Accounts)) - storageList = append(storageList, copyStorages(states.Storages)) + accountList = append(accountList, copySet(states.Accounts)) + storageList = append(storageList, copy2DSet(states.Storages)) } disk = rawdb.NewMemoryDatabase() tdb = trie.NewDatabaseWithConfig(disk, &trie.Config{OnCommit: onCommit}) @@ -236,8 +237,9 @@ func (test *stateTest) run() bool { // - the account was indeed not present in trie // - the account is present in new trie, nil->nil is regarded as invalid // - the slots transition is correct -func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addrHash common.Hash, slots map[common.Hash][]byte) error { +func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error { // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) oBlob, err := otr.Get(addrHash.Bytes()) if err != nil { return err @@ -286,8 +288,9 @@ func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database // - the account was indeed present in trie // - the account in old trie matches the provided value // - the slots transition is correct -func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addrHash common.Hash, origin []byte, slots map[common.Hash][]byte) error { +func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error { // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) oBlob, err := otr.Get(addrHash.Bytes()) if err != nil { return err @@ -339,7 +342,7 @@ func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, return nil } -func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Database, accountsOrigin map[common.Hash][]byte, storagesOrigin map[common.Hash]map[common.Hash][]byte) error { +func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error { otr, err := trie.New(trie.StateTrieID(root), db) if err != nil { return err @@ -348,12 +351,12 @@ func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Datab if err != nil { return err } - for addrHash, account := range accountsOrigin { + for addr, account := range accountsOrigin { var err error if len(account) == 0 { - err = test.verifyAccountCreation(next, db, otr, ntr, addrHash, storagesOrigin[addrHash]) + err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr]) } else { - err = test.verifyAccountUpdate(next, db, otr, ntr, addrHash, accountsOrigin[addrHash], storagesOrigin[addrHash]) + err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr]) } if err != nil { return err diff --git a/trie/triestate/state.go b/trie/triestate/state.go index 264bc43140..5a13138942 100644 --- a/trie/triestate/state.go +++ b/trie/triestate/state.go @@ -22,7 +22,7 @@ import "github.com/XinFinOrg/XDPoSChain/common" // 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.Hash][]byte // Mutated account set, nil means the account was not present - Storages map[common.Hash]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present - Incomplete map[common.Hash]struct{} // Indicator whether the storage slot is incomplete due to large deletion + 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 slot is incomplete due to large deletion }