From f67ac095a60c58507136c4012de703ce563e92b5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 20 Jun 2023 13:43:45 +0200 Subject: [PATCH] add hooks to state object & genesis --- core/blockchain.go | 6 +++++- core/chain_makers.go | 2 +- core/genesis.go | 22 +++++++++++++--------- core/state/state_object.go | 12 ++++++++++++ core/state/statedb.go | 13 +++++++++++++ eth/tracers/printer.go | 20 ++++++++++++++++++++ les/client.go | 2 +- 7 files changed, 65 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7acee54144..cfc6cc7c51 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -155,8 +155,10 @@ var defaultCacheConfig = &CacheConfig{ type BlockchainLogger interface { vm.EVMLogger + state.StateLogger CaptureBlockStart(*types.Block) CaptureBlockEnd() + OnGenesisBlock(*types.Block) } // BlockChain represents the canonical chain given a database with a genesis @@ -251,7 +253,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis // Setup the genesis block, commit the provided genesis specification // to database if the genesis block is not present yet, or load the // stored one from database. - chainConfig, genesisHash, genesisErr := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides) + // TODO: pass in blockchainLogger here to catch genesis block and allocs + chainConfig, genesisHash, genesisErr := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides, nil) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } @@ -1739,6 +1742,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if err != nil { return it.index, err } + statedb.SetLogger(bc.logger) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") diff --git a/core/chain_makers.go b/core/chain_makers.go index 61d0098af3..fe168f8b9c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -350,7 +350,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse // then generate chain on top. func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { db := rawdb.NewMemoryDatabase() - _, err := genesis.Commit(db, trie.NewDatabase(db)) + _, err := genesis.Commit(db, trie.NewDatabase(db), nil) if err != nil { panic(err) } diff --git a/core/genesis.go b/core/genesis.go index 1e56845d8a..2d84efcfde 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -138,8 +138,9 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { // flush is very similar with deriveHash, but the main difference is // all the generated states will be persisted into the given database. // Also, the genesis state specification will be flushed as well. -func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { +func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash, bcLogger BlockchainLogger) error { statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) + statedb.SetLogger(bcLogger) if err != nil { return err } @@ -200,7 +201,7 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm return errors.New("not found") } } - return alloc.flush(db, triedb, blockhash) + return alloc.flush(db, triedb, blockhash, nil) } // GenesisAccount is an account in the state of the genesis block. @@ -282,10 +283,10 @@ type ChainOverrides struct { // // The returned chain configuration is never nil. func SetupGenesisBlock(db ethdb.Database, triedb *trie.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { - return SetupGenesisBlockWithOverride(db, triedb, genesis, nil) + return SetupGenesisBlockWithOverride(db, triedb, genesis, nil, nil) } -func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, error) { +func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, genesis *Genesis, overrides *ChainOverrides, bcLogger BlockchainLogger) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } @@ -305,7 +306,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen } else { log.Info("Writing custom genesis block") } - block, err := genesis.Commit(db, triedb) + block, err := genesis.Commit(db, triedb, bcLogger) if err != nil { return genesis.Config, common.Hash{}, err } @@ -324,7 +325,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if hash != stored { return genesis.Config, hash, &GenesisMismatchError{stored, hash} } - block, err := genesis.Commit(db, triedb) + block, err := genesis.Commit(db, triedb, bcLogger) if err != nil { return genesis.Config, hash, err } @@ -468,7 +469,7 @@ func (g *Genesis) ToBlock() *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 *trie.Database) (*types.Block, error) { +func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database, bcLogger BlockchainLogger) (*types.Block, error) { block := g.ToBlock() if block.Number().Sign() != 0 { return nil, errors.New("can't commit genesis block with number > 0") @@ -483,10 +484,13 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block if config.Clique != nil && len(block.Extra()) < 32+crypto.SignatureLength { return nil, errors.New("can't start clique chain without signers") } + if bcLogger != nil { + bcLogger.OnGenesisBlock(block) + } // All the checks has passed, flush the states derived from the genesis // specification as well as the specification itself into the provided // database. - if err := g.Alloc.flush(db, triedb, block.Hash()); err != nil { + if err := g.Alloc.flush(db, triedb, block.Hash(), bcLogger); err != nil { return nil, err } rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) @@ -505,7 +509,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block // Note the state changes will be committed in hash-based scheme, use Commit // if path-scheme is preferred. func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { - block, err := g.Commit(db, trie.NewDatabase(db)) + block, err := g.Commit(db, trie.NewDatabase(db), nil) if err != nil { panic(err) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 606441d2a6..5ffae5df02 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -236,6 +236,9 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) { key: key, prevalue: prev, }) + if s.db.logger != nil { + s.db.logger.OnStorageChange(s.address, key, prev, value) + } s.setState(key, value) } @@ -399,6 +402,9 @@ func (s *stateObject) SetBalance(amount *big.Int) { account: &s.address, prev: new(big.Int).Set(s.data.Balance), }) + if s.db.logger != nil { + s.db.logger.OnBalanceChange(s.address, s.Balance(), amount) + } s.setBalance(amount) } @@ -470,6 +476,9 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { prevhash: s.CodeHash(), prevcode: prevcode, }) + if s.db.logger != nil { + s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) + } s.setCode(codeHash, code) } @@ -484,6 +493,9 @@ func (s *stateObject) SetNonce(nonce uint64) { account: &s.address, prev: s.data.Nonce, }) + if s.db.logger != nil { + s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) + } s.setNonce(nonce) } diff --git a/core/state/statedb.go b/core/state/statedb.go index b1b52cd7f1..0ef6e6e344 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -53,6 +53,13 @@ func (n *proofList) Delete(key []byte) error { panic("not supported") } +type StateLogger interface { + OnBalanceChange(addr common.Address, prev, new *big.Int) + OnNonceChange(addr common.Address, prev, new uint64) + OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) + OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) +} + // StateDB structs within the ethereum protocol are used to store anything // within the merkle trie. StateDBs take care of caching and storing // nested states. It's the general query interface to retrieve: @@ -63,6 +70,7 @@ type StateDB struct { prefetcher *triePrefetcher trie Trie hasher crypto.KeccakState + logger StateLogger // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -161,6 +169,11 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } +// TODO: move to New +func (s *StateDB) SetLogger(l StateLogger) { + s.logger = l +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. diff --git a/eth/tracers/printer.go b/eth/tracers/printer.go index 78f2c8b63d..94e7b10b45 100644 --- a/eth/tracers/printer.go +++ b/eth/tracers/printer.go @@ -65,3 +65,23 @@ func (p *Printer) CaptureBlockStart(b *types.Block) { func (p *Printer) CaptureBlockEnd() { fmt.Printf("CaptureBlockEnd\n") } + +func (p *Printer) OnGenesisBlock(b *types.Block) { + fmt.Printf("OnGenesisBlock: b=%v\n", b.NumberU64()) +} + +func (p *Printer) OnBalanceChange(a common.Address, prev, new *big.Int) { + fmt.Printf("OnBalanceChange: a=%v, prev=%v, new=%v\n", a, prev, new) +} + +func (p *Printer) OnNonceChange(a common.Address, prev, new uint64) { + fmt.Printf("OnNonceChange: a=%v, prev=%v, new=%v\n", a, prev, new) +} + +func (p *Printer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { + fmt.Printf("OnCodeChange: a=%v, prevCodeHash=%v, prev=%v, codeHash=%v, code=%v\n", a, prevCodeHash, prev, codeHash, code) +} + +func (p *Printer) OnStorageChange(a common.Address, k, prev, new common.Hash) { + fmt.Printf("OnStorageChange: a=%v, k=%v, prev=%v, new=%v\n", a, k, prev, new) +} diff --git a/les/client.go b/les/client.go index 0072eb8a1a..8cda91fc9d 100644 --- a/les/client.go +++ b/les/client.go @@ -97,7 +97,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if config.OverrideCancun != nil { overrides.OverrideCancun = config.OverrideCancun } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, trie.NewDatabase(chainDb), config.Genesis, &overrides) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, trie.NewDatabase(chainDb), config.Genesis, &overrides, nil) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr }