diff --git a/core/blockchain.go b/core/blockchain.go index 57906738a5..aa263711ad 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -406,6 +406,21 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } + if bc.logger != nil { + if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { + alloc, found, err := getGenesisState(bc.db, block.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to get genesis state: %w", err) + } + + if !found { + return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set, use 'geth init' to set it") + } + + 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 e274f5a79b..49a86c625d 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -167,45 +167,68 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas return err } } - // Marshal the genesis state specification and persist. - blob, err := json.Marshal(ga) + + if err := writeGenesisStateSpec(db, blockhash, *ga); err != nil { + return err + } + + return nil +} + +// writeGenesisStateSpec marshals the genesis state specification and persist to database. +func writeGenesisStateSpec(db ethdb.Database, blockhash common.Hash, alloc GenesisAlloc) error { + blob, err := json.Marshal(alloc) if err != nil { return err } + rawdb.WriteGenesisStateSpec(db, blockhash, blob) return nil } +func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc GenesisAlloc, found bool, err error) { + blob := rawdb.ReadGenesisStateSpec(db, blockhash) + if len(blob) != 0 { + if err := alloc.UnmarshalJSON(blob); err != nil { + return nil, true, err + } + + return alloc, true, nil + } + + // Genesis allocation is missing and there are several possibilities: + // the node is legacy which doesn't persist the genesis allocation or + // the persisted allocation is just lost. + // - supported networks(mainnet, testnets), recover with defined allocations + // - private network, can't recover + var genesis *Genesis + switch blockhash { + case params.MainnetGenesisHash: + genesis = DefaultGenesisBlock() + case params.GoerliGenesisHash: + genesis = DefaultGoerliGenesisBlock() + case params.SepoliaGenesisHash: + genesis = DefaultSepoliaGenesisBlock() + } + if genesis != nil { + return genesis.Alloc, true, nil + } + + return nil, false, nil +} + // CommitGenesisState loads the stored genesis state with the given block // hash and commits it into the provided trie database. func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { - var alloc GenesisAlloc - blob := rawdb.ReadGenesisStateSpec(db, blockhash) - if len(blob) != 0 { - if err := alloc.UnmarshalJSON(blob); err != nil { - return err - } - } else { - // Genesis allocation is missing and there are several possibilities: - // the node is legacy which doesn't persist the genesis allocation or - // the persisted allocation is just lost. - // - supported networks(mainnet, testnets), recover with defined allocations - // - private network, can't recover - var genesis *Genesis - switch blockhash { - case params.MainnetGenesisHash: - genesis = DefaultGenesisBlock() - case params.GoerliGenesisHash: - genesis = DefaultGoerliGenesisBlock() - case params.SepoliaGenesisHash: - genesis = DefaultSepoliaGenesisBlock() - } - if genesis != nil { - alloc = genesis.Alloc - } else { - return errors.New("not found") - } + alloc, found, err := getGenesisState(db, blockhash) + if err != nil { + return err } + + if !found { + return nil + } + return alloc.flush(db, triedb, blockhash, nil) } @@ -382,10 +405,19 @@ 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) } + + // We only write genesis alloc if the `genesis` is non-nil, which happen on blockchain boot and custom network. + if genesis != nil && !rawdb.HasGenesisStateSpec(db, stored) { + if err := writeGenesisStateSpec(db, stored, genesis.Alloc); err != nil { + return newcfg, stored, fmt.Errorf("write genesis state spec: %w", err) + } + } + return newcfg, stored, nil } @@ -517,6 +549,7 @@ 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.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) rawdb.WriteBlock(db, block) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 2ff29d1add..bc88f75b14 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -81,6 +81,12 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha } } +// HasGenesisStateSpec determines if the genesis state specification based on the +// given genesis (block-)hash exists in the database +func HasGenesisStateSpec(db ethdb.KeyValueReader, blockhash common.Hash) bool { + return len(ReadGenesisStateSpec(db, blockhash)) > 0 +} + // ReadGenesisStateSpec retrieves the genesis state specification based on the // given genesis (block-)hash. func ReadGenesisStateSpec(db ethdb.KeyValueReader, blockhash common.Hash) []byte {