From 09d66773bdf5be61e68de11736d022108733aa35 Mon Sep 17 00:00:00 2001 From: Matthieu Vachon Date: Wed, 26 Jul 2023 14:37:32 -0400 Subject: [PATCH] Added writing of `genesis.Alloc` on bootstrap, `geth init` will also fill the missing value --- core/blockchain.go | 28 ++++++++++++++++++ core/genesis.go | 53 ++++++++++++++++++++++++++++++++++ core/rawdb/accessors_chain.go | 54 +++++++++++++++++++++++++++++++++++ core/rawdb/schema.go | 2 ++ 4 files changed, 137 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index a738c73a2e..31ad574744 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -408,6 +408,34 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } + if bc.logger != nil && bc.CurrentBlock().Number.Uint64() == 0 { + rawAlloc, found := rawdb.ReadGenesisAlloc(bc.db) + if !found { + return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set, use 'geth init' to set it") + } + + alloc := make(GenesisAlloc, len(rawAlloc)) + for _, account := range rawAlloc { + var storage map[common.Hash]common.Hash + if len(account.Storage) > 0 { + storage = make(map[common.Hash]common.Hash, len(account.Storage)) + for _, entry := range account.Storage { + storage[entry.Key] = entry.Value + } + } + + alloc[account.Address] = GenesisAccount{ + Balance: account.Balance, + Code: account.Code, + Storage: storage, + Nonce: account.Nonce, + PrivateKey: account.PrivateKey, + } + } + + bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) + } + // Load any existing snapshot, regenerating it if loading failed if bc.cacheConfig.SnapshotLimit > 0 { // If the chain was rewound past the snapshot persistent layer (causing diff --git a/core/genesis.go b/core/genesis.go index 691ae1c04c..31a3ea9c08 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -371,10 +371,21 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) { return newcfg, stored, compatErr } + // Don't overwrite if the old is identical to the new if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) { rawdb.WriteChainConfig(db, stored, newcfg) } + + // PR Review Question: Should we actually use same behavior as above and overwrite if the old is not identical to the new? + // A new genesis alloc would lead to a new genesis block hash, so we should probably NOT overwrite unless block hash + // is also affected in the flow above. + // + // We only write genesis alloc if the `genesis` is non-nil, which happen on blockchain boot and custom network. + if genesis != nil && !rawdb.HasGenesisAlloc(db) { + rawdb.WriteGenesisAlloc(db, toRawDBAccounts(genesis.Alloc)) + } + return newcfg, stored, nil } @@ -493,6 +504,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database, bcLogger Bloc if err := g.Alloc.flush(db, triedb, block.Hash(), bcLogger); err != nil { return nil, err } + + rawdb.WriteGenesisAlloc(db, toRawDBAccounts(g.Alloc)) rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) rawdb.WriteBlock(db, block) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) @@ -504,6 +517,46 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database, bcLogger Bloc return block, nil } +func toRawDBAccounts(alloc GenesisAlloc) (out []rawdb.Account) { + if len(alloc) == 0 { + return + } + + i := 0 + out = make([]rawdb.Account, len(alloc)) + for addr, account := range alloc { + out[i] = rawdb.Account{ + Address: addr, + Code: account.Code, + Storage: toRawDBAccountStorages(account.Storage), + Balance: account.Balance, + Nonce: account.Nonce, + PrivateKey: account.PrivateKey, + } + i++ + } + + return +} + +func toRawDBAccountStorages(in map[common.Hash]common.Hash) (out []rawdb.AccountStorage) { + if len(in) == 0 { + return + } + + i := 0 + out = make([]rawdb.AccountStorage, len(in)) + for key, value := range in { + out[i] = rawdb.AccountStorage{ + Key: key, + Value: value, + } + i++ + } + + return +} + // MustCommit writes the genesis block and state to db, panicking on error. // The block is committed as the canonical head block. // Note the state changes will be committed in hash-based scheme, use Commit diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index b479655b0b..239cd14161 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -761,6 +761,60 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals) } +// Account is an account in the state of the genesis block +// +// FIXME: This is a duplicate for now of core.GenesisAccount (and actually core.GenesisAlloc). +type Account struct { + Address common.Address `json:"address" gencodec:"required"` + Code []byte `json:"code,omitempty"` + Storage []AccountStorage `json:"storage,omitempty"` + Balance *big.Int `json:"balance" gencodec:"required"` + Nonce uint64 `json:"nonce,omitempty"` + PrivateKey []byte `json:"secretKey,omitempty"` // for tests +} + +type AccountStorage struct { + Key common.Hash `json:"key,omitempty"` + Value common.Hash `json:"value,omitempty"` +} + +// HasGenesisAlloc checks if a genesis allocation exists currently in the database +func HasGenesisAlloc(db ethdb.Reader) (found bool) { + found, _ = db.Has(genesisAllocKey) + return +} + +// ReadGenesisalloc serializes the full initial genesis allocation into the database +func ReadGenesisAlloc(db ethdb.Reader) (alloc []Account, found bool) { + // We handle case where genesis alloc is not present, downstream code might decide differently + // PR Review question: Should I check for specific Not Found Error? I did not see a common `error` in `ethdb` + data, _ := db.Get(genesisAllocKey) + + if len(data) == 0 { + return nil, false + } + + alloc = []Account{} + if err := rlp.Decode(bytes.NewReader(data), &alloc); err != nil { + log.Crit("Failed to decode genesis alloc", "err", err) + return nil, true + } + + return alloc, true +} + +// WriteGenesisAlloc serializes the full initial genesis allocation into the database +func WriteGenesisAlloc(db ethdb.KeyValueWriter, alloc []Account) { + data, err := rlp.EncodeToBytes(alloc) + if err != nil { + log.Crit("Failed to RLP encode alloc", "err", err) + } + + if err := db.Put(genesisAllocKey, data); err != nil { + log.Crit("Failed to store genesis alloc", "err", err) + } +} + // WriteBlock serializes a block into the database, header and body separately. func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) { WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 18722ed5d4..9511acf9b7 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -86,6 +86,8 @@ var ( transitionStatusKey = []byte("eth2-transition") // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). + genesisAllocKey = []byte("g") // genesisAllocKey (static key) + headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash