diff --git a/cmd/bera-geth/chaincmd.go b/cmd/bera-geth/chaincmd.go index 9988d13e40..9f3d8bd6b3 100644 --- a/cmd/bera-geth/chaincmd.go +++ b/cmd/bera-geth/chaincmd.go @@ -47,6 +47,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" "github.com/urfave/cli/v2" ) @@ -281,8 +282,19 @@ func initGenesis(ctx *cli.Context) error { chaindb := utils.MakeChainDatabase(ctx, stack, false) defer chaindb.Close() - triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) - defer triedb.Close() + // Berachain: Check if genesis already exists for PBSS databases BEFORE opening trie database. + // This prevents the state history truncation that happens during trie database initialization. + var triedb *triedb.Database + if rawdb.ReadStateScheme(chaindb) == rawdb.PathScheme && + rawdb.ReadChainConfig(chaindb, rawdb.ReadCanonicalHash(chaindb, 0)) != nil { + log.Info("PBSS db already initialized with genesis, skipping trie db initialization") + } else { + // Only create triedb if we're not using PBSS or genesis is empty on disk. Refer to + // https://github.com/ethereum/go-ethereum/pull/25523 for why the triedb cannot be + // re-created when using PBSS. + triedb = utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) + defer triedb.Close() + } _, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides) if err != nil { diff --git a/cmd/bera-geth/genesis_test.go b/cmd/bera-geth/genesis_test.go index f5d21cc00b..cb54a5c647 100644 --- a/cmd/bera-geth/genesis_test.go +++ b/cmd/bera-geth/genesis_test.go @@ -21,6 +21,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "testing" ) @@ -97,6 +98,142 @@ func TestCustomGenesis(t *testing.T) { } } +// TestPBSSRepeatInitNoTrieOpen verifies that running init twice on a PBSS (path-scheme) +// database does not re-initialize the trie database and safely short-circuits. +func TestPBSSRepeatInitNoTrieOpen(t *testing.T) { + t.Parallel() + + // Minimal genesis with TTD=0 to keep execution simple + genesis := `{ + "alloc": {"0x0000000000000000000000000000000000000001": {"balance": "0x1"}}, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x1", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00", + "config": { "terminalTotalDifficulty": 0 } + }` + + datadir := t.TempDir() + jsonPath := filepath.Join(datadir, "genesis.json") + if err := os.WriteFile(jsonPath, []byte(genesis), 0600); err != nil { + t.Fatalf("failed to write genesis file: %v", err) + } + + // First init should perform normal initialization + gethInit1 := runGeth(t, "--datadir", datadir, "init", jsonPath) + gethInit1.WaitExit() + if gethInit1.Err != nil { + t.Fatalf("first init failed: %v", gethInit1.Err) + } + + // Second init should skip trie db initialization and not truncate freezer + gethInit2 := runGeth(t, "--datadir", datadir, "init", jsonPath) + gethInit2.WaitExit() + if gethInit2.Err != nil { + t.Fatalf("second init failed: %v", gethInit2.Err) + } + stderr := gethInit2.StderrText() + if !strings.Contains(stderr, "PBSS db already initialized with genesis, skipping trie db initialization") { + t.Fatalf("expected skip-triedb log not found in stderr. got:\n%s", stderr) + } +} + +// TestPBSSConfigUpdate verifies that a repeat init on PBSS writes updated chain +// configuration without touching the trie database when the genesis block hash +// is unchanged. +func TestPBSSConfigUpdate(t *testing.T) { + t.Parallel() + + // Base genesis (no Prague1 yet). Include all prior timestamp forks explicitly + // with timestamp 0 to satisfy fork ordering: Shanghai -> Cancun -> Prague -> Prague1. + baseGenesis := `{ + "alloc": {"0x0000000000000000000000000000000000000001": {"balance": "0x1"}}, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x1", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00", + "config": { + "terminalTotalDifficulty": 0, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "shanghaiTime": 0 + } + }` + + // Updated config: enable Berachain Prague1 while keeping genesis state identical. + // Include all prior timestamp forks to maintain correct ordering. + updatedGenesis := `{ + "alloc": {"0x0000000000000000000000000000000000000001": {"balance": "0x1"}}, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x1", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00", + "config": { + "terminalTotalDifficulty": 0, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "shanghaiTime": 0, + "berachain": { + "prague1": { + "time": 1000, + "baseFeeChangeDenominator": 48, + "poLDistributorAddress": "0x1111111111111111111111111111111111111111" + } + } + } + }` + + datadir := t.TempDir() + jsonPath := filepath.Join(datadir, "genesis.json") + if err := os.WriteFile(jsonPath, []byte(baseGenesis), 0600); err != nil { + t.Fatalf("failed to write base genesis file: %v", err) + } + + // First init with base config + runGeth(t, "--datadir", datadir, "init", jsonPath).WaitExit() + + // Overwrite with updated config (same genesis state, different chain config) + if err := os.WriteFile(jsonPath, []byte(updatedGenesis), 0600); err != nil { + t.Fatalf("failed to write updated genesis file: %v", err) + } + + // Second init should skip trie db init and write the new chain config + geth := runGeth(t, "--datadir", datadir, "init", jsonPath) + geth.WaitExit() + stderr := geth.StderrText() + if !strings.Contains(stderr, "PBSS db already initialized with genesis, skipping trie db initialization") { + t.Fatalf("expected PBSS skip log not found in stderr. got:\n%s", stderr) + } + if !strings.Contains(stderr, "Writing new chain config") { + t.Fatalf("expected config-update log not found in stderr. got:\n%s", stderr) + } +} + // TestCustomBackend that the backend selection and detection (leveldb vs pebble) works properly. func TestCustomBackend(t *testing.T) { t.Parallel() @@ -186,9 +323,9 @@ func TestCustomBackend(t *testing.T) { { // Reject invalid backend choice initArgs: []string{"--db.engine", "mssql"}, initExpect: `Fatal: Invalid choice for db.engine 'mssql', allowed 'leveldb' or 'pebble'`, - // Since the init fails, this will return the (default) mainnet genesis + // Since the init fails, this will return the (default) berachain mainnet genesis // block nonce - execExpect: `0x0000000000000042`, + execExpect: `0x0000000000001234`, }, } { if err := testfunc(t, tt); err != nil { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 40a779920e..d1825cd27c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1067,7 +1067,7 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { // 1. --bootnodes flag // 2. Config file // 3. Network preset flags (e.g. --holesky) -// 4. default to Berachainmainnet nodes +// 4. default to Berachain mainnet nodes func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls := params.BerachainBootnodes if ctx.IsSet(BootnodesFlag.Name) { diff --git a/core/genesis.go b/core/genesis.go index 1ba8bc9b23..098de3cf24 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -307,8 +307,8 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g ghash := rawdb.ReadCanonicalHash(db, 0) if (ghash == common.Hash{}) { if genesis == nil { - log.Info("Writing default main-net genesis block") - genesis = DefaultGenesisBlock() + log.Info("Writing default berachain mainnet genesis block") + genesis = DefaultBerachainGenesisBlock() } else { log.Info("Writing custom genesis block") } @@ -332,8 +332,8 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g // 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() + log.Info("Writing default berachain mainnet genesis block") + genesis = DefaultBerachainGenesisBlock() } else { log.Info("Writing custom genesis block") } @@ -385,9 +385,11 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g return newCfg, ghash, compatErr, nil } // 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. + // for the scenarios that database is opened in the read-only mode + // OR if the chain config is updated to include new Berachain fork info. storedData, _ := json.Marshal(storedCfg) if newData, _ := json.Marshal(newCfg); !bytes.Equal(storedData, newData) { + log.Info("Writing new chain config") rawdb.WriteChainConfig(db, ghash, newCfg) } return newCfg, ghash, nil, nil @@ -423,8 +425,8 @@ func LoadChainConfig(db ethdb.Database, genesis *Genesis) (cfg *params.ChainConf return genesis.Config, ghash, nil } // There is no stored chain config and no new config provided, - // In this case the default chain config(mainnet) will be used - return params.MainnetChainConfig, params.MainnetGenesisHash, nil + // In this case the default chain config(berachain mainnet) will be used + return params.BerachainChainConfig, params.BerachainGenesisHash, nil } // chainConfigOrDefault retrieves the attached chain configuration. If the genesis diff --git a/core/genesis_test.go b/core/genesis_test.go index 7e39e7a71c..1abf978cf8 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -72,8 +72,8 @@ func testSetupGenesis(t *testing.T, scheme string) { fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) { return SetupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), nil) }, - wantHash: params.MainnetGenesisHash, - wantConfig: params.MainnetChainConfig, + wantHash: params.BerachainGenesisHash, + wantConfig: params.BerachainChainConfig, }, { name: "mainnet block in DB, genesis == nil", diff --git a/params/bootnodes.go b/params/bootnodes.go index ba006ad681..7f8ed7092f 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -133,13 +133,11 @@ const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUD // KnownDNSNetwork returns the address of a public DNS-based node list for the given // genesis hash and protocol. See https://github.com/ethereum/discv4-dns-lists for more // information. +// +// TODO: add support for bepolia and berachain mainnet. func KnownDNSNetwork(genesis common.Hash, protocol string) string { var net string switch genesis { - case BerachainGenesisHash: - net = "berachain" - case BepoliaGenesisHash: - net = "bepolia" case MainnetGenesisHash: net = "mainnet" case SepoliaGenesisHash: