mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-10 01:56:37 +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())
|
triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
|
||||||
defer triedb.Close()
|
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 {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to write genesis block: %v", err)
|
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
|
// yet. The corresponding chain config will be returned, either from the
|
||||||
// provided genesis or from the locally stored configuration if the genesis
|
// provided genesis or from the locally stored configuration if the genesis
|
||||||
// has already been initialized.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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)))
|
log.Debug("Committed block data", "size", common.StorageSize(batch.ValueSize()), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
root common.Hash
|
root common.Hash
|
||||||
isEIP158 = bc.chainConfig.IsEIP158(block.Number())
|
isEIP158 = bc.chainConfig.IsEIP158(block.Number())
|
||||||
isCancun = bc.chainConfig.IsCancun(block.Number(), block.Time())
|
isCancun = bc.chainConfig.IsCancun(block.Number(), block.Time())
|
||||||
|
hasStateHook = bc.logger != nil && bc.logger.OnStateUpdate != nil
|
||||||
|
hasStateSizer = bc.stateSizer != nil
|
||||||
)
|
)
|
||||||
if bc.stateSizer == nil {
|
if hasStateHook || hasStateSizer {
|
||||||
root, err = statedb.Commit(block.NumberU64(), isEIP158, isCancun)
|
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 {
|
} 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
|
// If node is running in path mode, skip explicit gc operation
|
||||||
// which is unnecessary in this mode.
|
// which is unnecessary in this mode.
|
||||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||||
|
|
|
||||||
|
|
@ -481,7 +481,7 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
|
||||||
}
|
}
|
||||||
triedb := triedb.NewDatabase(db, triedbConfig)
|
triedb := triedb.NewDatabase(db, triedbConfig)
|
||||||
defer triedb.Close()
|
defer triedb.Close()
|
||||||
_, err := genesis.Commit(db, triedb)
|
_, err := genesis.Commit(db, triedb, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
// flushAlloc is very similar with hash, but the main difference is all the
|
||||||
// generated states will be persisted into the given database.
|
// 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
|
emptyRoot := types.EmptyRootHash
|
||||||
if triedb.IsVerkle() {
|
if triedb.IsVerkle() {
|
||||||
emptyRoot = types.EmptyVerkleHash
|
emptyRoot = types.EmptyVerkleHash
|
||||||
|
|
@ -185,10 +185,26 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e
|
||||||
statedb.SetState(addr, key, value)
|
statedb.SetState(addr, key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root, err := statedb.Commit(0, false, false)
|
|
||||||
if err != nil {
|
var root common.Hash
|
||||||
return common.Hash{}, err
|
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.
|
// Commit newly generated states into disk if it's not empty.
|
||||||
if root != emptyRoot {
|
if root != emptyRoot {
|
||||||
if err := triedb.Commit(root, true); err != nil {
|
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
|
// 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.
|
// 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) {
|
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.
|
// Copy the genesis, so we can operate on a copy.
|
||||||
genesis = genesis.copy()
|
genesis = genesis.copy()
|
||||||
// Sanitize the supplied genesis, ensuring it has the associated chain
|
// 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
|
return nil, common.Hash{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := genesis.Commit(db, triedb)
|
block, err := genesis.Commit(db, triedb, tracer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Hash{}, nil, err
|
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 {
|
if hash := genesis.ToBlock().Hash(); hash != ghash {
|
||||||
return nil, common.Hash{}, nil, &GenesisMismatchError{ghash, hash}
|
return nil, common.Hash{}, nil, &GenesisMismatchError{ghash, hash}
|
||||||
}
|
}
|
||||||
block, err := genesis.Commit(db, triedb)
|
block, err := genesis.Commit(db, triedb, tracer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Hash{}, nil, err
|
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.
|
// Commit writes the block and state of a genesis specification to the database.
|
||||||
// The block is committed as the canonical head block.
|
// 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 {
|
if g.Number != 0 {
|
||||||
return nil, errors.New("can't commit genesis block with 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")
|
return nil, errors.New("can't start clique chain without signers")
|
||||||
}
|
}
|
||||||
// flush the data to disk and compute the state root
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// MustCommit writes the genesis block and state to db, panicking on error.
|
||||||
// The block is committed as the canonical head block.
|
// The block is committed as the canonical head block.
|
||||||
func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
||||||
name: "custom block in DB, genesis == nil",
|
name: "custom block in DB, genesis == nil",
|
||||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||||
customg.Commit(db, tdb)
|
customg.Commit(db, tdb, nil)
|
||||||
return SetupGenesisBlock(db, tdb, nil)
|
return SetupGenesisBlock(db, tdb, nil)
|
||||||
},
|
},
|
||||||
wantHash: customghash,
|
wantHash: customghash,
|
||||||
|
|
@ -98,7 +98,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
||||||
name: "custom block in DB, genesis == sepolia",
|
name: "custom block in DB, genesis == sepolia",
|
||||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||||
customg.Commit(db, tdb)
|
customg.Commit(db, tdb, nil)
|
||||||
return SetupGenesisBlock(db, tdb, DefaultSepoliaGenesisBlock())
|
return SetupGenesisBlock(db, tdb, DefaultSepoliaGenesisBlock())
|
||||||
},
|
},
|
||||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.SepoliaGenesisHash},
|
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",
|
name: "custom block in DB, genesis == hoodi",
|
||||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||||
customg.Commit(db, tdb)
|
customg.Commit(db, tdb, nil)
|
||||||
return SetupGenesisBlock(db, tdb, DefaultHoodiGenesisBlock())
|
return SetupGenesisBlock(db, tdb, DefaultHoodiGenesisBlock())
|
||||||
},
|
},
|
||||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.HoodiGenesisHash},
|
wantErr: &GenesisMismatchError{Stored: customghash, New: params.HoodiGenesisHash},
|
||||||
|
|
@ -116,7 +116,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
||||||
name: "compatible config in DB",
|
name: "compatible config in DB",
|
||||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
|
||||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||||
oldcustomg.Commit(db, tdb)
|
oldcustomg.Commit(db, tdb, nil)
|
||||||
return SetupGenesisBlock(db, tdb, &customg)
|
return SetupGenesisBlock(db, tdb, &customg)
|
||||||
},
|
},
|
||||||
wantHash: customghash,
|
wantHash: customghash,
|
||||||
|
|
@ -128,7 +128,7 @@ func testSetupGenesis(t *testing.T, scheme string) {
|
||||||
// Commit the 'old' genesis block with Homestead transition at #2.
|
// Commit the 'old' genesis block with Homestead transition at #2.
|
||||||
// Advance to block #4, past the homestead transition block of customg.
|
// Advance to block #4, past the homestead transition block of customg.
|
||||||
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
|
||||||
oldcustomg.Commit(db, tdb)
|
oldcustomg.Commit(db, tdb, nil)
|
||||||
|
|
||||||
bc, _ := NewBlockChain(db, &oldcustomg, ethash.NewFullFaker(), DefaultConfig().WithStateScheme(scheme))
|
bc, _ := NewBlockChain(db, &oldcustomg, ethash.NewFullFaker(), DefaultConfig().WithStateScheme(scheme))
|
||||||
defer bc.Stop()
|
defer bc.Stop()
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func TestHeaderInsertion(t *testing.T) {
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges}
|
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 })
|
hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,12 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
||||||
blob: s.code,
|
blob: s.code,
|
||||||
}
|
}
|
||||||
s.dirtyCode = false // reset the dirty flag
|
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
|
// Commit storage changes and the associated storage trie
|
||||||
s.commitStorage(op)
|
s.commitStorage(op)
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
|
|
||||||
codeExists := make(map[common.Hash]struct{})
|
codeExists := make(map[common.Hash]struct{})
|
||||||
for _, code := range update.codes {
|
for _, code := range update.codes {
|
||||||
if _, ok := codeExists[code.hash]; ok || code.exists {
|
if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stats.ContractCodes += 1
|
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
|
// commitAndFlush is a wrapper of commit which also commits the state mutations
|
||||||
// to the configured data stores.
|
// 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)
|
ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if deriveCodeFields {
|
||||||
if dedupCode {
|
if err := ret.deriveCodeFields(s.reader); err != nil {
|
||||||
ret.markCodeExistence(s.reader)
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit dirty contract code if any exists
|
// Commit dirty contract code if any exists
|
||||||
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
|
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
|
||||||
batch := db.NewBatch()
|
batch := db.NewBatch()
|
||||||
|
|
@ -1389,14 +1389,14 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
|
||||||
return ret.root, nil
|
return ret.root, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitAndTrack writes the state mutations and notifies the size tracker of the state changes.
|
// CommitWithUpdate writes the state mutations and returns the state update for
|
||||||
func (s *StateDB) CommitAndTrack(block uint64, deleteEmptyObjects bool, noStorageWiping bool, sizer *SizeTracker) (common.Hash, error) {
|
// 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)
|
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, nil, err
|
||||||
}
|
}
|
||||||
sizer.Notify(ret)
|
return ret.root, ret, nil
|
||||||
return ret.root, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare handles the preparatory steps for executing a state transition with.
|
// Prepare handles the preparatory steps for executing a state transition with.
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,27 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"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 {
|
type contractCode struct {
|
||||||
hash common.Hash // hash is the cryptographic hash of the contract code.
|
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
||||||
blob []byte // blob is the binary representation of the contract code.
|
blob []byte // blob is the binary representation of the current contract code.
|
||||||
exists bool // flag whether the code has been existent
|
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.
|
// 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
|
// deriveCodeFields derives the missing fields of contract code changes
|
||||||
// in this state update actually exists.
|
// such as original code value.
|
||||||
//
|
//
|
||||||
// Note: This operation is expensive and not needed during normal state transitions.
|
// Note: This operation is expensive and not needed during normal state
|
||||||
// It is only required when SizeTracker is enabled to produce accurate state
|
// transitions. It is only required when SizeTracker or StateUpdate hook
|
||||||
// statistics.
|
// is enabled to produce accurate state statistics.
|
||||||
func (sc *stateUpdate) markCodeExistence(reader ContractCodeReader) {
|
func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
||||||
cache := make(map[common.Hash]bool)
|
cache := make(map[common.Hash]bool)
|
||||||
for addr, code := range sc.codes {
|
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 {
|
if exists, ok := cache[code.hash]; ok {
|
||||||
code.exists = exists
|
code.duplicate = exists
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res := reader.Has(addr, code.hash)
|
res := reader.Has(addr, code.hash)
|
||||||
cache[code.hash] = res
|
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/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -75,6 +76,56 @@ type BlockEvent struct {
|
||||||
Safe *types.Header
|
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 (
|
type (
|
||||||
/*
|
/*
|
||||||
- VM events -
|
- VM events -
|
||||||
|
|
@ -161,6 +212,11 @@ type (
|
||||||
// beacon block root.
|
// beacon block root.
|
||||||
OnSystemCallEndHook = func()
|
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 -
|
- State events -
|
||||||
*/
|
*/
|
||||||
|
|
@ -209,6 +265,7 @@ type Hooks struct {
|
||||||
OnSystemCallStart OnSystemCallStartHook
|
OnSystemCallStart OnSystemCallStartHook
|
||||||
OnSystemCallStartV2 OnSystemCallStartHookV2
|
OnSystemCallStartV2 OnSystemCallStartHookV2
|
||||||
OnSystemCallEnd OnSystemCallEndHook
|
OnSystemCallEnd OnSystemCallEndHook
|
||||||
|
OnStateUpdate StateUpdateHook
|
||||||
// State events
|
// State events
|
||||||
OnBalanceChange BalanceChangeHook
|
OnBalanceChange BalanceChangeHook
|
||||||
OnNonceChange NonceChangeHook
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
||||||
|
|
||||||
// Hack: GenerateChainWithGenesis creates a new db.
|
// Hack: GenerateChainWithGenesis creates a new db.
|
||||||
// Commit the genesis manually and use GenerateChain.
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +426,7 @@ func TestRangeLogs(t *testing.T) {
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
gspec.Config.TerminalTotalDifficulty = big.NewInt(stdmath.MaxInt64)
|
||||||
}
|
}
|
||||||
triedb := triedb.NewDatabase(db, tconf)
|
triedb := triedb.NewDatabase(db, tconf)
|
||||||
gblock, err := gspec.Commit(db, triedb)
|
gblock, err := gspec.Commit(db, triedb, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue