diff --git a/core/genesis.go b/core/genesis.go index f2ec9865ea..d01f86312c 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "encoding/json" "errors" "fmt" @@ -149,9 +150,35 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig log.Info("Writing custom genesis block") } block, err := genesis.Commit(db) + if err != nil { + return genesis.Config, common.Hash{}, err + } return genesis.Config, block.Hash(), err } + // We have the genesis block in database (perhaps in ancient database) + // but the corresponding state is missing. + header := rawdb.ReadHeader(db, stored, 0) + if header == nil { + cfg := genesis.configOrDefault(stored) + return cfg, stored, fmt.Errorf("missing genesis header for hash: %s", stored.Hex()) + } + if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil)); err != nil { + if genesis == nil { + genesis = DefaultGenesisBlock() + } + // Ensure the stored genesis matches with the given one. + hash := genesis.ToBlock(nil).Hash() + if hash != stored { + return genesis.Config, hash, &GenesisMismatchError{stored, hash} + } + block, err := genesis.Commit(db) + if err != nil { + return genesis.Config, hash, err + } + return genesis.Config, block.Hash(), nil + } + // Check whether the genesis block is already written. if genesis != nil { hash := genesis.ToBlock(nil).Hash() @@ -186,7 +213,18 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 { return newcfg, stored, compatErr } - rawdb.WriteChainConfig(db, stored, newcfg) + // Don't overwrite if the old is identical to the new + storedData, err := json.Marshal(storedcfg) + if err != nil { + return newcfg, stored, fmt.Errorf("failed to marshal stored chain config: %w", err) + } + newData, err := json.Marshal(newcfg) + if err != nil { + return newcfg, stored, fmt.Errorf("failed to marshal new chain config: %w", err) + } + if !bytes.Equal(storedData, newData) { + rawdb.WriteChainConfig(db, stored, newcfg) + } return newcfg, stored, nil } diff --git a/core/genesis_test.go b/core/genesis_test.go index 483fd50629..83d4da1626 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -17,6 +17,8 @@ package core import ( + "errors" + "fmt" "math/big" "reflect" "testing" @@ -105,6 +107,53 @@ func TestSetupGenesis(t *testing.T) { wantHash: params.TestnetGenesisHash, wantConfig: params.TestnetChainConfig, }, + { + name: "stored canonical hash without header", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + missingHash := common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + rawdb.WriteCanonicalHash(db, missingHash, 0) + return SetupGenesisBlock(db, nil) + }, + wantErr: fmt.Errorf("missing genesis header for hash: %s", common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Hex()), + wantHash: common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + wantConfig: params.AllEthashProtocolChanges, + }, + { + name: "genesis header present but state missing", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + block := DefaultGenesisBlock().ToBlock(nil) + rawdb.WriteCanonicalHash(db, block.Hash(), 0) + rawdb.WriteHeader(db, block.Header()) + return SetupGenesisBlock(db, nil) + }, + wantHash: params.MainnetGenesisHash, + wantConfig: params.XDCMainnetChainConfig, + }, + { + name: "genesis block without chain config", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + block := DefaultGenesisBlock().ToBlock(db) + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), 0) + return SetupGenesisBlock(db, nil) + }, + wantHash: params.MainnetGenesisHash, + wantConfig: params.XDCMainnetChainConfig, + }, + { + name: "missing block number for head header hash", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + block := DefaultGenesisBlock().ToBlock(db) + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), 0) + rawdb.WriteChainConfig(db, block.Hash(), params.XDCMainnetChainConfig) + rawdb.WriteHeadHeaderHash(db, common.HexToHash("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) + return SetupGenesisBlock(db, nil) + }, + wantErr: errors.New("missing block number for head header hash"), + wantHash: params.MainnetGenesisHash, + wantConfig: params.XDCMainnetChainConfig, + }, { name: "compatible config in DB", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { @@ -145,7 +194,7 @@ func TestSetupGenesis(t *testing.T) { db := rawdb.NewMemoryDatabase() config, hash, err := test.fn(db) // Check the return values. - if !reflect.DeepEqual(err, test.wantErr) { + if (err == nil) != (test.wantErr == nil) || (err != nil && test.wantErr != nil && !errors.Is(err, test.wantErr) && err.Error() != test.wantErr.Error()) { spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true} t.Errorf("%s: returned error %#v, want %#v", test.name, spew.NewFormatter(err), spew.NewFormatter(test.wantErr)) }