core/state: polish

This commit is contained in:
Gary Rong 2026-01-05 14:10:25 +08:00
parent d80a124a59
commit 3cf530356a
5 changed files with 103 additions and 104 deletions

View file

@ -1649,36 +1649,35 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
log.Debug("Committed block data", "size", common.StorageSize(batch.ValueSize()), "elapsed", common.PrettyDuration(time.Since(start)))
var (
err error
root common.Hash
isEIP158 = bc.chainConfig.IsEIP158(block.Number())
isCancun = bc.chainConfig.IsCancun(block.Number(), block.Time())
hasStateHook = bc.logger != nil && bc.logger.OnStateUpdate != nil
hasStateSizer = bc.stateSizer != nil
needStateUpdate = hasStateHook || hasStateSizer
err error
root common.Hash
isEIP158 = bc.chainConfig.IsEIP158(block.Number())
isCancun = bc.chainConfig.IsCancun(block.Number(), block.Time())
hasStateHook = bc.logger != nil && bc.logger.OnStateUpdate != nil
hasStateSizer = bc.stateSizer != nil
)
if needStateUpdate {
if hasStateHook || hasStateSizer {
r, update, err := statedb.CommitWithUpdate(block.NumberU64(), isEIP158, isCancun)
if err == nil && update != nil {
if hasStateHook {
trUpdate, err := update.ToTracingUpdate()
if err != nil {
return err
}
bc.logger.OnStateUpdate(trUpdate)
}
if hasStateSizer {
bc.stateSizer.Notify(update)
if err != nil {
return err
}
if hasStateHook {
trUpdate, err := update.ToTracingUpdate()
if err != nil {
return err
}
bc.logger.OnStateUpdate(trUpdate)
}
if hasStateSizer {
bc.stateSizer.Notify(update)
}
root = r
} else {
root, err = statedb.Commit(block.NumberU64(), isEIP158, isCancun)
if err != nil {
return err
}
}
if err != nil {
return err
}
// If node is running in path mode, skip explicit gc operation
// which is unnecessary in this mode.
if bc.triedb.Scheme() == rawdb.PathScheme {

View file

@ -423,7 +423,7 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit(resolveCode bool) (*accountUpdate, *trienode.NodeSet, error) {
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
// commit the account metadata changes
op := &accountUpdate{
address: s.address,
@ -440,17 +440,10 @@ func (s *stateObject) commit(resolveCode bool) (*accountUpdate, *trienode.NodeSe
}
s.dirtyCode = false // reset the dirty flag
// retrieve the original code if requested and the code was not empty
if resolveCode && s.origin != nil && !bytes.Equal(s.origin.CodeHash, types.EmptyCodeHash.Bytes()) {
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.origin.CodeHash))
if err != nil {
return nil, nil, err
}
op.codeOrigin = &contractCode{
hash: common.BytesToHash(s.origin.CodeHash),
blob: code,
exists: true, // the code exists before the update
}
if s.origin == nil {
op.code.originHash = types.EmptyCodeHash
} else {
op.code.originHash = common.BytesToHash(s.origin.CodeHash)
}
}
// Commit storage changes and the associated storage trie

View file

@ -245,7 +245,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
codeExists := make(map[common.Hash]struct{})
for _, code := range update.codes {
if _, ok := codeExists[code.hash]; ok || code.exists {
if _, ok := codeExists[code.hash]; ok || code.duplicate {
continue
}
stats.ContractCodes += 1

View file

@ -1162,7 +1162,7 @@ func (s *StateDB) GetTrie() Trie {
// commit gathers the state mutations accumulated along with the associated
// trie changes, resetting all internal flags with the new state as the base.
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64, resolveCode bool) (*stateUpdate, error) {
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
// Short circuit in case any database failure occurred earlier.
if s.dbErr != nil {
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@ -1272,7 +1272,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie
update, set, err := obj.commit(resolveCode)
update, set, err := obj.commit()
if err != nil {
return err
}
@ -1319,16 +1319,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
// commitAndFlush is a wrapper of commit which also commits the state mutations
// to the configured data stores.
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, resolveCode bool) (*stateUpdate, error) {
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block, resolveCode)
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*stateUpdate, error) {
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
if err != nil {
return nil, err
}
if resolveCode {
ret.markCodeExistence(s.reader)
if deriveCodeFields {
if err := ret.deriveCodeFields(s.reader); err != nil {
return nil, err
}
}
// Commit dirty contract code if any exists
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
batch := db.NewBatch()

View file

@ -29,11 +29,15 @@ import (
"github.com/ethereum/go-ethereum/triedb"
)
// contractCode represents a contract code with associated metadata.
// contractCode represents contract bytecode along with its associated metadata.
type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the contract code.
blob []byte // blob is the binary representation of the contract code.
exists bool // flag whether the code has been existent
hash common.Hash // hash is the cryptographic hash of the current contract code.
blob []byte // blob is the binary representation of the current contract code.
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
// Derived fields, populated only when state tracking is enabled.
duplicate bool // duplicate indicates whether the updated code already exists.
originBlob []byte // originBlob is the original binary representation of the contract code.
}
// accountDelete represents an operation for deleting an Ethereum account.
@ -52,12 +56,11 @@ type accountDelete struct {
// accountUpdate represents an operation for updating an Ethereum account.
type accountUpdate struct {
address common.Address // address is the unique account identifier
data []byte // data is the slim-RLP encoded account data.
origin []byte // origin is the original value of account data in slim-RLP encoding.
code *contractCode // code represents mutated contract code; nil means it's not modified.
codeOrigin *contractCode // codeOrigin is the original value of contract code; nil means it does not exist before the update.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
address common.Address // address is the unique account identifier
data []byte // data is the slim-RLP encoded account data.
origin []byte // origin is the original value of account data in slim-RLP encoding.
code *contractCode // code represents mutated contract code; nil means it's not modified.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
// storagesOriginByKey and storagesOriginByHash both store the original values
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
@ -89,9 +92,8 @@ type stateUpdate struct {
storagesOrigin map[common.Address]map[common.Hash][]byte
rawStorageKey bool
codes map[common.Address]*contractCode // codes contains the set of dirty codes
codesOrigin map[common.Address]*contractCode // codesOrigin contains the original code data of the mutated accounts
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
codes map[common.Address]*contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
}
// empty returns a flag indicating the state transition is empty or not.
@ -112,7 +114,6 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
storages = make(map[common.Hash]map[common.Hash][]byte)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
codes = make(map[common.Address]*contractCode)
codesOrigin = make(map[common.Address]*contractCode)
)
// Since some accounts might be destroyed and recreated within the same
// block, deletions must be aggregated first.
@ -136,9 +137,6 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
if op.code != nil {
codes[addr] = op.code
}
if op.codeOrigin != nil {
codesOrigin[addr] = op.codeOrigin
}
accounts[addrHash] = op.data
// Aggregate the account original value. If the account is already
@ -185,7 +183,6 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
storagesOrigin: storagesOrigin,
rawStorageKey: rawStorageKey,
codes: codes,
codesOrigin: codesOrigin,
nodes: nodes,
}
}
@ -204,23 +201,31 @@ func (sc *stateUpdate) stateSet() *triedb.StateSet {
}
}
// markCodeExistence determines whether each piece of contract code referenced
// in this state update actually exists.
// deriveCodeFields derives the missing fields of contract code changes
// such as original code value.
//
// Note: This operation is expensive and not needed during normal state transitions.
// It is only required when SizeTracker is enabled to produce accurate state
// statistics.
func (sc *stateUpdate) markCodeExistence(reader ContractCodeReader) {
// Note: This operation is expensive and not needed during normal state
// transitions. It is only required when SizeTracker or StateUpdate hook
// is enabled to produce accurate state statistics.
func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
cache := make(map[common.Hash]bool)
for addr, code := range sc.codes {
if code.originHash != types.EmptyCodeHash {
blob, err := reader.Code(addr, code.originHash)
if err != nil {
return err
}
code.originBlob = blob
}
if exists, ok := cache[code.hash]; ok {
code.exists = exists
code.duplicate = exists
continue
}
res := reader.Has(addr, code.hash)
cache[code.hash] = res
code.exists = res
code.duplicate = res
}
return nil
}
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
@ -234,49 +239,51 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
}
for addr, prev := range sc.accountsOrigin {
// Gather all account changes
for addr, oldData := range sc.accountsOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
new, exists := sc.accounts[addrHash]
newData, exists := sc.accounts[addrHash]
if !exists {
return nil, fmt.Errorf("account %x not found", addr)
}
change := &tracing.AccountChange{}
if len(prev) > 0 {
if acct, err := types.FullAccount(prev); err == nil {
change.Prev = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
if len(oldData) > 0 {
acct, err := types.FullAccount(oldData)
if err != nil {
return nil, err
}
change.Prev = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
}
if len(new) > 0 {
if acct, err := types.FullAccount(new); err == nil {
change.New = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
if len(newData) > 0 {
acct, err := types.FullAccount(newData)
if err != nil {
return nil, err
}
change.New = &types.StateAccount{
Nonce: acct.Nonce,
Balance: acct.Balance,
Root: acct.Root,
CodeHash: acct.CodeHash,
}
}
update.AccountChanges[addr] = change
}
// Gather all storage slot changes
for addr, slots := range sc.storagesOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := sc.storages[addrHash]
if !exists {
return nil, fmt.Errorf("storage %x not found", addr)
}
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
storageMap := make(map[common.Hash]*tracing.StorageChange, len(slots))
for key, encPrev := range slots {
// Get new value - handle both raw and hashed key formats
var (
@ -308,42 +315,43 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
return nil, fmt.Errorf("failed to decode newValue: %v", err)
}
}
storageMap[key] = &tracing.StorageChange{
storageChanges[key] = &tracing.StorageChange{
Prev: common.BytesToHash(decPrev),
New: common.BytesToHash(decNew),
}
}
update.StorageChanges[addr] = storageMap
update.StorageChanges[addr] = storageChanges
}
// Gather all contract code changes
for addr, code := range sc.codes {
change := &tracing.CodeChange{
New: &tracing.ContractCode{
Hash: code.hash,
Code: code.blob,
Exists: code.exists,
Exists: code.duplicate,
},
}
if origin, ok := sc.codesOrigin[addr]; ok {
if code.originHash != types.EmptyCodeHash {
change.Prev = &tracing.ContractCode{
Hash: origin.hash,
Code: origin.blob,
Exists: origin.exists,
Hash: code.originHash,
Code: code.originBlob,
Exists: true,
}
}
update.CodeChanges[addr] = change
}
// Gather all trie node changes
if sc.nodes != nil {
for owner, subset := range sc.nodes.Sets {
nodeMap := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
for path, oldNode := range subset.Origins {
newNode, exists := subset.Nodes[path]
if !exists {
return nil, fmt.Errorf("node %x-%v not found", owner, path)
}
nodeMap[path] = &tracing.TrieNodeChange{
nodeChanges[path] = &tracing.TrieNodeChange{
Prev: &trienode.Node{
Hash: crypto.Keccak256Hash(oldNode),
Blob: oldNode,
@ -354,9 +362,8 @@ func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
},
}
}
update.TrieChanges[owner] = nodeMap
update.TrieChanges[owner] = nodeChanges
}
}
return update, nil
}