Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd, core, miner: rework genesis setup #30907

Merged
merged 1 commit into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cmd/geth/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,10 @@ func initGenesis(ctx *cli.Context) error {
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close()

_, hash, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
_, hash, _, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}

log.Info("Successfully wrote genesis state", "database", "chaindata", "hash", hash)

return nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/utils/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func TestHistoryImportAndExport(t *testing.T) {
db2.Close()
})

genesis.MustCommit(db2, triedb.NewDatabase(db, triedb.HashDefaults))
genesis.MustCommit(db2, triedb.NewDatabase(db2, triedb.HashDefaults))
rjl493456442 marked this conversation as resolved.
Show resolved Hide resolved
imported, err := core.NewBlockChain(db2, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil)
if err != nil {
t.Fatalf("unable to initialize chain: %v", err)
Expand Down
31 changes: 17 additions & 14 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,14 +269,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
cacheConfig = defaultCacheConfig
}
// Open trie database with provided config
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle()))
enableVerkle, err := EnableVerkleAtGenesis(db, genesis)
if err != nil {
return nil, err
}
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(enableVerkle))

// 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)
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
return nil, genesisErr
// Write the supplied genesis to the database if it has not been initialized
// yet. The corresponding chain config will be returned, either from the
// provided genesis or from the locally stored configuration if the genesis
// has already been initialized.
chainConfig, genesisHash, compatErr, err := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides)
if err != nil {
return nil, err
}
log.Info("")
log.Info(strings.Repeat("-", 153))
Expand All @@ -303,7 +308,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
vmConfig: vmConfig,
logger: vmConfig.Tracer,
}
var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
if err != nil {
return nil, err
Expand Down Expand Up @@ -453,16 +457,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
}

// Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
if compat.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compat.RewindToTime)
if compatErr != nil {
log.Warn("Rewinding chain to upgrade configuration", "err", compatErr)
if compatErr.RewindToTime > 0 {
bc.SetHeadWithTimestamp(compatErr.RewindToTime)
} else {
bc.SetHead(compat.RewindToBlock)
bc.SetHead(compatErr.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
}

// Start tx indexer if it's enabled.
if txLookupLimit != nil {
bc.txIndexer = newTxIndexer(*txLookupLimit, bc)
Expand Down
204 changes: 121 additions & 83 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,24 @@ type ChainOverrides struct {
OverrideVerkle *uint64
}

// apply applies the chain overrides on the supplied chain config.
func (o *ChainOverrides) apply(cfg *params.ChainConfig) (*params.ChainConfig, error) {
if o == nil || cfg == nil {
return cfg, nil
}
cpy := *cfg
if o.OverrideCancun != nil {
cpy.CancunTime = o.OverrideCancun
}
if o.OverrideVerkle != nil {
cpy.VerkleTime = o.OverrideVerkle
}
if err := cpy.CheckConfigForkOrder(); err != nil {
return nil, err
}
return &cpy, nil
}

// SetupGenesisBlock writes or updates the genesis block in db.
// The block that will be used is:
//
Expand All @@ -250,109 +268,102 @@ type ChainOverrides struct {
// The stored chain configuration will be updated if it is compatible (i.e. does not
// 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.
//
// The returned chain configuration is never nil.
func SetupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
func SetupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
return SetupGenesisBlockWithOverride(db, triedb, genesis, nil)
}

func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, error) {
func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
// Sanitize the supplied genesis, ensuring it has the associated chain
// config attached.
if genesis != nil && genesis.Config == nil {
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
}
applyOverrides := func(config *params.ChainConfig) {
if config != nil {
if overrides != nil && overrides.OverrideCancun != nil {
config.CancunTime = overrides.OverrideCancun
}
if overrides != nil && overrides.OverrideVerkle != nil {
config.VerkleTime = overrides.OverrideVerkle
}
}
return nil, common.Hash{}, nil, errGenesisNoConfig
}
// Just commit the new block if there is no stored genesis block.
stored := rawdb.ReadCanonicalHash(db, 0)
if (stored == common.Hash{}) {
// Commit the genesis if the database is empty
ghash := rawdb.ReadCanonicalHash(db, 0)
if (ghash == common.Hash{}) {
if genesis == nil {
log.Info("Writing default main-net genesis block")
genesis = DefaultGenesisBlock()
} else {
log.Info("Writing custom genesis block")
}
chainCfg, err := overrides.apply(genesis.Config)
if err != nil {
return nil, common.Hash{}, nil, err
}
genesis.Config = chainCfg

applyOverrides(genesis.Config)
block, err := genesis.Commit(db, triedb)
if err != nil {
return genesis.Config, common.Hash{}, err
return nil, common.Hash{}, nil, err
}
return genesis.Config, block.Hash(), nil
}
// The genesis block is present(perhaps in ancient database) while the
// state database is not initialized yet. It can happen that the node
// is initialized with an external ancient store. Commit genesis state
// in this case.
header := rawdb.ReadHeader(db, stored, 0)
if header.Root != types.EmptyRootHash && !triedb.Initialized(header.Root) {
return chainCfg, block.Hash(), nil, nil
}
// Commit the genesis if the genesis block exists in the ancient database
// but the key-value database is empty without initializing the genesis
// fields. This scenario can occur when the node is created from scratch
// with an existing ancient store.
storedCfg := rawdb.ReadChainConfig(db, ghash)
if storedCfg == nil {
// Ensure the stored genesis block matches with the given genesis. Private
// networks must explicitly specify the genesis in the config file, mainnet
// genesis will be used as default and the initialization will always fail.
if genesis == nil {
log.Info("Writing default main-net genesis block")
genesis = DefaultGenesisBlock()
} else {
log.Info("Writing custom genesis block")
}
applyOverrides(genesis.Config)
// Ensure the stored genesis matches with the given one.
hash := genesis.ToBlock().Hash()
if hash != stored {
return genesis.Config, hash, &GenesisMismatchError{stored, hash}
chainCfg, err := overrides.apply(genesis.Config)
if err != nil {
return nil, common.Hash{}, nil, err
}
genesis.Config = chainCfg

if hash := genesis.ToBlock().Hash(); hash != ghash {
return nil, common.Hash{}, nil, &GenesisMismatchError{ghash, hash}
}
block, err := genesis.Commit(db, triedb)
if err != nil {
return genesis.Config, hash, err
return nil, common.Hash{}, nil, err
}
return genesis.Config, block.Hash(), nil
return chainCfg, block.Hash(), nil, nil
}
// Check whether the genesis block is already written.
// The genesis block has already been committed previously. Verify that the
// provided genesis with chain overrides matches the existing one, and update
// the stored chain config if necessary.
if genesis != nil {
applyOverrides(genesis.Config)
hash := genesis.ToBlock().Hash()
if hash != stored {
return genesis.Config, hash, &GenesisMismatchError{stored, hash}
chainCfg, err := overrides.apply(genesis.Config)
if err != nil {
return nil, common.Hash{}, nil, err
}
genesis.Config = chainCfg

if hash := genesis.ToBlock().Hash(); hash != ghash {
return nil, common.Hash{}, nil, &GenesisMismatchError{ghash, hash}
}
}
// Get the existing chain configuration.
newcfg := genesis.configOrDefault(stored)
applyOverrides(newcfg)
if err := newcfg.CheckConfigForkOrder(); err != nil {
return newcfg, common.Hash{}, err
}
storedcfg := rawdb.ReadChainConfig(db, stored)
if storedcfg == nil {
log.Warn("Found genesis block without chain config")
rawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil
}
storedData, _ := json.Marshal(storedcfg)
// Special case: if a private network is being used (no genesis and also no
// mainnet hash in the database), we must not apply the `configOrDefault`
// chain config as that would be AllProtocolChanges (applying any new fork
// on top of an existing private network genesis block). In that case, only
// apply the overrides.
if genesis == nil && stored != params.MainnetGenesisHash {
newcfg = storedcfg
applyOverrides(newcfg)
}
// Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero.
head := rawdb.ReadHeadHeader(db)
if head == nil {
return newcfg, stored, errors.New("missing head header")
return nil, common.Hash{}, nil, errors.New("missing head header")
}
compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time)
newCfg := genesis.chainConfigOrDefault(ghash, storedCfg)

// TODO(rjl493456442) better to define the comparator of chain config
// and short circuit if the chain config is not changed.
compatErr := storedCfg.CheckCompatible(newCfg, head.Number.Uint64(), head.Time)
if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) {
return newcfg, stored, compatErr
return newCfg, ghash, compatErr, nil
}
// 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)
// Don't overwrite if the old is identical to the new. It's useful
// for the scenarios that database is opened in the read-only mode.
storedData, _ := json.Marshal(storedCfg)
if newData, _ := json.Marshal(newCfg); !bytes.Equal(storedData, newData) {
rawdb.WriteChainConfig(db, ghash, newCfg)
}
return newcfg, stored, nil
return newCfg, ghash, nil, nil
}

// LoadChainConfig loads the stored chain config if it is already present in
Expand Down Expand Up @@ -388,7 +399,10 @@ func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig,
return params.MainnetChainConfig, nil
}

func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
// chainConfigOrDefault retrieves the attached chain configuration. If the genesis
// object is null, it returns the default chain configuration based on the given
// genesis hash, or the locally stored config if it's not a pre-defined network.
func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainConfig) *params.ChainConfig {
switch {
case g != nil:
return g.Config
Expand All @@ -399,14 +413,14 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
case ghash == params.SepoliaGenesisHash:
return params.SepoliaChainConfig
default:
return params.AllEthashProtocolChanges
return stored
}
}

// IsVerkle indicates whether the state is already stored in a verkle
// tree at genesis time.
func (g *Genesis) IsVerkle() bool {
return g.Config.IsVerkle(new(big.Int).SetUint64(g.Number), g.Timestamp)
return g.Config.IsVerkleGenesis()
}

// ToBlock returns the genesis block according to genesis specification.
Expand Down Expand Up @@ -486,7 +500,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
}
config := g.Config
if config == nil {
config = params.AllEthashProtocolChanges
return nil, errors.New("invalid genesis without chain config")
}
if err := config.CheckConfigForkOrder(); err != nil {
return nil, err
Expand All @@ -506,16 +520,17 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
if err != nil {
return nil, err
}
rawdb.WriteGenesisStateSpec(db, block.Hash(), blob)
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
rawdb.WriteBlock(db, block)
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
rawdb.WriteHeadBlockHash(db, block.Hash())
rawdb.WriteHeadFastBlockHash(db, block.Hash())
rawdb.WriteHeadHeaderHash(db, block.Hash())
rawdb.WriteChainConfig(db, block.Hash(), config)
return block, nil
batch := db.NewBatch()
rawdb.WriteGenesisStateSpec(batch, block.Hash(), blob)
rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), block.Difficulty())
rawdb.WriteBlock(batch, block)
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), nil)
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
rawdb.WriteHeadBlockHash(batch, block.Hash())
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
rawdb.WriteHeadHeaderHash(batch, block.Hash())
rawdb.WriteChainConfig(batch, block.Hash(), config)
return block, batch.Write()
}

// MustCommit writes the genesis block and state to db, panicking on error.
Expand All @@ -528,6 +543,29 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.
return block
}

// EnableVerkleAtGenesis indicates whether the verkle fork should be activated
// at genesis. This is a temporary solution only for verkle devnet testing, where
// verkle fork is activated at genesis, and the configured activation date has
// already passed.
//
// In production networks (mainnet and public testnets), verkle activation always
// occurs after the genesis block, making this function irrelevant in those cases.
func EnableVerkleAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) {
if genesis != nil {
if genesis.Config == nil {
return false, errGenesisNoConfig
}
return genesis.Config.EnableVerkleAtGenesis, nil
}
if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) {
chainCfg := rawdb.ReadChainConfig(db, ghash)
if chainCfg != nil {
return chainCfg.EnableVerkleAtGenesis, nil
}
}
return false, nil
}

// DefaultGenesisBlock returns the Ethereum main net genesis block.
func DefaultGenesisBlock() *Genesis {
return &Genesis{
Expand Down
Loading