add hooks to state object & genesis

This commit is contained in:
Sina Mahmoodi 2023-06-20 13:43:45 +02:00
parent e25065bfc3
commit f67ac095a6
7 changed files with 65 additions and 12 deletions

View file

@ -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")

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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.

View file

@ -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)
}

View file

@ -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
}