mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
core/state, core/tracing: new state update hook (#33490)
### Description Add a new `OnStateUpdate` hook which gets invoked after state is committed. ### Rationale For our particular use case, we need to obtain the state size metrics at every single block when fuly syncing from genesis. With the current state sizer, whenever the node is stopped, the background process must be freshly initialized. During this re-initialization, it can skip some blocks while the node continues executing blocks, causing gaps in the recorded metrics. Using this state update hook allows us to customize our own data persistence logic, and we would never skip blocks upon node restart. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
parent
957a3602d9
commit
01b39c96bf
14 changed files with 309 additions and 58 deletions
|
|
@ -296,7 +296,7 @@ func initGenesis(ctx *cli.Context) error {
|
|||
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
|
||||
defer triedb.Close()
|
||||
|
||||
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
|
||||
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to write genesis block: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
// yet. The corresponding chain config will be returned, either from the
|
||||
// provided genesis or from the locally stored configuration if the genesis
|
||||
// has already been initialized.
|
||||
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, cfg.Overrides)
|
||||
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, cfg.Overrides, cfg.VmConfig.Tracer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1651,20 +1651,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())
|
||||
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 bc.stateSizer == nil {
|
||||
root, err = statedb.Commit(block.NumberU64(), isEIP158, isCancun)
|
||||
if hasStateHook || hasStateSizer {
|
||||
r, update, err := statedb.CommitWithUpdate(block.NumberU64(), isEIP158, isCancun)
|
||||
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.CommitAndTrack(block.NumberU64(), isEIP158, isCancun, bc.stateSizer)
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
|
|||
}
|
||||
triedb := triedb.NewDatabase(db, triedbConfig)
|
||||
defer triedb.Close()
|
||||
_, err := genesis.Commit(db, triedb)
|
||||
_, err := genesis.Commit(db, triedb, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
|
|||
|
||||
// flushAlloc is very similar with hash, but the main difference is all the
|
||||
// generated states will be persisted into the given database.
|
||||
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
|
||||
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database, tracer *tracing.Hooks) (common.Hash, error) {
|
||||
emptyRoot := types.EmptyRootHash
|
||||
if triedb.IsVerkle() {
|
||||
emptyRoot = types.EmptyVerkleHash
|
||||
|
|
@ -185,10 +185,26 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e
|
|||
statedb.SetState(addr, key, value)
|
||||
}
|
||||
}
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
|
||||
var root common.Hash
|
||||
if tracer != nil && tracer.OnStateUpdate != nil {
|
||||
r, update, err := statedb.CommitWithUpdate(0, false, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
trUpdate, err := update.ToTracingUpdate()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
tracer.OnStateUpdate(trUpdate)
|
||||
root = r
|
||||
} else {
|
||||
root, err = statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Commit newly generated states into disk if it's not empty.
|
||||
if root != emptyRoot {
|
||||
if err := triedb.Commit(root, true); err != nil {
|
||||
|
|
@ -296,10 +312,10 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
|||
// specify a fork block below the local head block). In case of a conflict, the
|
||||
// error is a *params.ConfigCompatError and the new, unwritten config is returned.
|
||||
func SetupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
return SetupGenesisBlockWithOverride(db, triedb, genesis, nil)
|
||||
return SetupGenesisBlockWithOverride(db, triedb, genesis, nil, nil)
|
||||
}
|
||||
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, overrides *ChainOverrides, tracer *tracing.Hooks) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
// Copy the genesis, so we can operate on a copy.
|
||||
genesis = genesis.copy()
|
||||
// Sanitize the supplied genesis, ensuring it has the associated chain
|
||||
|
|
@ -320,7 +336,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
|
|||
return nil, common.Hash{}, nil, err
|
||||
}
|
||||
|
||||
block, err := genesis.Commit(db, triedb)
|
||||
block, err := genesis.Commit(db, triedb, tracer)
|
||||
if err != nil {
|
||||
return nil, common.Hash{}, nil, err
|
||||
}
|
||||
|
|
@ -348,7 +364,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
|
|||
if hash := genesis.ToBlock().Hash(); hash != ghash {
|
||||
return nil, common.Hash{}, nil, &GenesisMismatchError{ghash, hash}
|
||||
}
|
||||
block, err := genesis.Commit(db, triedb)
|
||||
block, err := genesis.Commit(db, triedb, tracer)
|
||||
if err != nil {
|
||||
return nil, common.Hash{}, nil, err
|
||||
}
|
||||
|
|
@ -537,7 +553,7 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
|||
|
||||
// Commit writes the block and state of a genesis specification to the database.
|
||||
// The block is committed as the canonical head block.
|
||||
func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Block, error) {
|
||||
func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database, tracer *tracing.Hooks) (*types.Block, error) {
|
||||
if g.Number != 0 {
|
||||
return nil, errors.New("can't commit genesis block with number > 0")
|
||||
}
|
||||
|
|
@ -552,7 +568,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
|
|||
return nil, errors.New("can't start clique chain without signers")
|
||||
}
|
||||
// flush the data to disk and compute the state root
|
||||
root, err := flushAlloc(&g.Alloc, triedb)
|
||||
root, err := flushAlloc(&g.Alloc, triedb, tracer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -578,7 +594,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
|
|||
// MustCommit writes the genesis block and state to db, panicking on error.
|
||||
// The block is committed as the canonical head block.
|
||||
func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.Block {
|
||||
block, err := g.Commit(db, triedb)
|
||||
block, err := g.Commit(db, triedb, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
|||
name: "custom block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||
customg.Commit(db, tdb)
|
||||
customg.Commit(db, tdb, nil)
|
||||
return SetupGenesisBlock(db, tdb, nil)
|
||||
},
|
||||
wantHash: customghash,
|
||||
|
|
@ -98,7 +98,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
|||
name: "custom block in DB, genesis == sepolia",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||
customg.Commit(db, tdb)
|
||||
customg.Commit(db, tdb, nil)
|
||||
return SetupGenesisBlock(db, tdb, DefaultSepoliaGenesisBlock())
|
||||
},
|
||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.SepoliaGenesisHash},
|
||||
|
|
@ -107,7 +107,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
|||
name: "custom block in DB, genesis == hoodi",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||
customg.Commit(db, tdb)
|
||||
customg.Commit(db, tdb, nil)
|
||||
return SetupGenesisBlock(db, tdb, DefaultHoodiGenesisBlock())
|
||||
},
|
||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.HoodiGenesisHash},
|
||||
|
|
@ -116,7 +116,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
|||
name: "compatible config in DB",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||
oldcustomg.Commit(db, tdb)
|
||||
oldcustomg.Commit(db, tdb, nil)
|
||||
return SetupGenesisBlock(db, tdb, &customg)
|
||||
},
|
||||
wantHash: customghash,
|
||||
|
|
@ -128,7 +128,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
|||
// Commit the 'old' genesis block with Homestead transition at #2.
|
||||
// Advance to block #4, past the homestead transition block of customg.
|
||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||
oldcustomg.Commit(db, tdb)
|
||||
oldcustomg.Commit(db, tdb, nil)
|
||||
|
||||
bc, _ := NewBlockChain(db, &oldcustomg, ethash.NewFullFaker(), DefaultConfig().WithStateScheme(scheme))
|
||||
defer bc.Stop()
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func TestHeaderInsertion(t *testing.T) {
|
|||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges}
|
||||
)
|
||||
gspec.Commit(db, triedb.NewDatabase(db, nil))
|
||||
gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
|
||||
hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -440,6 +440,12 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
|||
blob: s.code,
|
||||
}
|
||||
s.dirtyCode = false // reset the dirty flag
|
||||
|
||||
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
|
||||
s.commitStorage(op)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1318,16 +1318,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, dedupCode bool) (*stateUpdate, error) {
|
||||
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 dedupCode {
|
||||
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()
|
||||
|
|
@ -1389,14 +1389,14 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
|
|||
return ret.root, nil
|
||||
}
|
||||
|
||||
// CommitAndTrack writes the state mutations and notifies the size tracker of the state changes.
|
||||
func (s *StateDB) CommitAndTrack(block uint64, deleteEmptyObjects bool, noStorageWiping bool, sizer *SizeTracker) (common.Hash, error) {
|
||||
// CommitWithUpdate writes the state mutations and returns the state update for
|
||||
// external processing (e.g., live tracing hooks or size tracker).
|
||||
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
|
||||
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
return common.Hash{}, nil, err
|
||||
}
|
||||
sizer.Notify(ret)
|
||||
return ret.root, nil
|
||||
return ret.root, ret, nil
|
||||
}
|
||||
|
||||
// Prepare handles the preparatory steps for executing a state transition with.
|
||||
|
|
|
|||
|
|
@ -17,18 +17,27 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"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.
|
||||
|
|
@ -192,21 +201,169 @@ 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.
|
||||
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||
update := &tracing.StateUpdate{
|
||||
OriginRoot: sc.originRoot,
|
||||
Root: sc.root,
|
||||
BlockNumber: sc.blockNumber,
|
||||
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
||||
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
||||
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
|
||||
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
||||
}
|
||||
// Gather all account changes
|
||||
for addr, oldData := range sc.accountsOrigin {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
newData, exists := sc.accounts[addrHash]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("account %x not found", addr)
|
||||
}
|
||||
change := &tracing.AccountChange{}
|
||||
|
||||
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(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))
|
||||
|
||||
for key, encPrev := range slots {
|
||||
// Get new value - handle both raw and hashed key formats
|
||||
var (
|
||||
exists bool
|
||||
encNew []byte
|
||||
decPrev []byte
|
||||
decNew []byte
|
||||
err error
|
||||
)
|
||||
if sc.rawStorageKey {
|
||||
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||
} else {
|
||||
encNew, exists = subset[key]
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
||||
}
|
||||
|
||||
// Decode the prev and new values
|
||||
if len(encPrev) > 0 {
|
||||
_, decPrev, _, err = rlp.Split(encPrev)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode prevValue: %v", err)
|
||||
}
|
||||
}
|
||||
if len(encNew) > 0 {
|
||||
_, decNew, _, err = rlp.Split(encNew)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode newValue: %v", err)
|
||||
}
|
||||
}
|
||||
storageChanges[key] = &tracing.StorageChange{
|
||||
Prev: common.BytesToHash(decPrev),
|
||||
New: common.BytesToHash(decNew),
|
||||
}
|
||||
}
|
||||
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.duplicate,
|
||||
},
|
||||
}
|
||||
if code.originHash != types.EmptyCodeHash {
|
||||
change.Prev = &tracing.ContractCode{
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
nodeChanges[path] = &tracing.TrieNodeChange{
|
||||
Prev: &trienode.Node{
|
||||
Hash: crypto.Keccak256Hash(oldNode),
|
||||
Blob: oldNode,
|
||||
},
|
||||
New: &trienode.Node{
|
||||
Hash: newNode.Hash,
|
||||
Blob: newNode.Blob,
|
||||
},
|
||||
}
|
||||
}
|
||||
update.TrieChanges[owner] = nodeChanges
|
||||
}
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -75,6 +76,56 @@ type BlockEvent struct {
|
|||
Safe *types.Header
|
||||
}
|
||||
|
||||
// StateUpdate represents the state mutations resulting from block execution.
|
||||
// It provides access to account changes, storage changes, and contract code
|
||||
// deployments with both previous and new values.
|
||||
type StateUpdate struct {
|
||||
OriginRoot common.Hash // State root before the update
|
||||
Root common.Hash // State root after the update
|
||||
BlockNumber uint64
|
||||
|
||||
// AccountChanges contains all account state changes keyed by address.
|
||||
AccountChanges map[common.Address]*AccountChange
|
||||
|
||||
// StorageChanges contains all storage slot changes keyed by address and storage slot key.
|
||||
StorageChanges map[common.Address]map[common.Hash]*StorageChange
|
||||
|
||||
// CodeChanges contains all contract code changes keyed by address.
|
||||
CodeChanges map[common.Address]*CodeChange
|
||||
|
||||
// TrieChanges contains trie node mutations keyed by address hash and trie node path.
|
||||
TrieChanges map[common.Hash]map[string]*TrieNodeChange
|
||||
}
|
||||
|
||||
// AccountChange represents a change to an account's state.
|
||||
type AccountChange struct {
|
||||
Prev *types.StateAccount // nil if account was created
|
||||
New *types.StateAccount // nil if account was deleted
|
||||
}
|
||||
|
||||
// StorageChange represents a change to a storage slot.
|
||||
type StorageChange struct {
|
||||
Prev common.Hash // previous value (zero if slot was created)
|
||||
New common.Hash // new value (zero if slot was deleted)
|
||||
}
|
||||
|
||||
type ContractCode struct {
|
||||
Hash common.Hash
|
||||
Code []byte
|
||||
Exists bool // true if the code was existent
|
||||
}
|
||||
|
||||
// CodeChange represents a change in contract code of an account.
|
||||
type CodeChange struct {
|
||||
Prev *ContractCode // nil if no code existed before
|
||||
New *ContractCode
|
||||
}
|
||||
|
||||
type TrieNodeChange struct {
|
||||
Prev *trienode.Node
|
||||
New *trienode.Node
|
||||
}
|
||||
|
||||
type (
|
||||
/*
|
||||
- VM events -
|
||||
|
|
@ -161,6 +212,11 @@ type (
|
|||
// beacon block root.
|
||||
OnSystemCallEndHook = func()
|
||||
|
||||
// StateUpdateHook is called after state is committed for a block.
|
||||
// It provides access to the complete state mutations including account changes,
|
||||
// storage changes, trie node mutations, and contract code deployments.
|
||||
StateUpdateHook = func(update *StateUpdate)
|
||||
|
||||
/*
|
||||
- State events -
|
||||
*/
|
||||
|
|
@ -209,6 +265,7 @@ type Hooks struct {
|
|||
OnSystemCallStart OnSystemCallStartHook
|
||||
OnSystemCallStartV2 OnSystemCallStartHookV2
|
||||
OnSystemCallEnd OnSystemCallEndHook
|
||||
OnStateUpdate StateUpdateHook
|
||||
// State events
|
||||
OnBalanceChange BalanceChangeHook
|
||||
OnNonceChange NonceChangeHook
|
||||
|
|
|
|||
|
|
@ -546,7 +546,7 @@ func TestExceedLogQueryLimit(t *testing.T) {
|
|||
}
|
||||
)
|
||||
|
||||
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil))
|
||||
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
|||
|
||||
// Hack: GenerateChainWithGenesis creates a new db.
|
||||
// Commit the genesis manually and use GenerateChain.
|
||||
_, err = gspec.Commit(db, triedb.NewDatabase(db, nil))
|
||||
_, err = gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -426,7 +426,7 @@ func TestRangeLogs(t *testing.T) {
|
|||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
)
|
||||
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil))
|
||||
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
|
|||
gspec.Config.TerminalTotalDifficulty = big.NewInt(stdmath.MaxInt64)
|
||||
}
|
||||
triedb := triedb.NewDatabase(db, tconf)
|
||||
gblock, err := gspec.Commit(db, triedb)
|
||||
gblock, err := gspec.Commit(db, triedb, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue