fix(pbss): idempotent init cmd if stored db (#54)

* fix(pbss): idempotent init cmd if stored db

* rename + nits

* fix

* handles all cases

* remove docs

* comments

* nit

* fix and add unit tests
This commit is contained in:
Cal Bera 2025-08-09 20:56:44 -07:00 committed by GitHub
parent c46c737d94
commit 186b95619a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 18 deletions

View file

@ -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())
// 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 {

View file

@ -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 {

View file

@ -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

View file

@ -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",

View file

@ -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: