Skip to content
Open
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
6 changes: 3 additions & 3 deletions cmd/XDC/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func importChain(ctx *cli.Context) error {
// Start metrics export if enabled
utils.SetupMetrics(&cfg.Metrics)

chain, db := utils.MakeChain(ctx, stack, false)
chain, db := utils.MakeChain(ctx, stack, false, cfg.Eth.ChainConfigMismatchPolicy)
defer db.Close()

// Start periodically gathering memory profiles
Expand Down Expand Up @@ -291,10 +291,10 @@ func exportChain(ctx *cli.Context) error {
utils.Fatalf("This command requires an argument.")
}

stack, _ := makeConfigNode(ctx)
stack, cfg := makeConfigNode(ctx)
defer stack.Close()

chain, db := utils.MakeChain(ctx, stack, true)
chain, db := utils.MakeChain(ctx, stack, true, cfg.Eth.ChainConfigMismatchPolicy)
defer db.Close()
start := time.Now()

Expand Down
33 changes: 24 additions & 9 deletions cmd/XDC/genesis_startup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"
"time"

cmdutils "github.com/XinFinOrg/XDPoSChain/cmd/utils"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
Expand Down Expand Up @@ -117,13 +118,17 @@ func assertCommandSucceeds(t *testing.T, cmd *testXDC) {
}
}

func startTestnetConsole(t *testing.T, datadir string) {
func startTestnetConsole(t *testing.T, datadir string, extraArgs ...string) {
t.Helper()

startupCmd := runXDC(t,
args := []string{
"--testnet", "console", "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable",
"--datadir", datadir, "--exec", "2+2",
)
"--datadir", datadir,
}
args = append(args, extraArgs...)
args = append(args, "--exec", "2+2")

startupCmd := runXDC(t, args...)
assertCommandSucceeds(t, startupCmd)
}

Expand Down Expand Up @@ -855,7 +860,12 @@ func TestOfflineExportFailsReadonlyConfigRewindWithoutMutation(t *testing.T) {
injectedHeadHash := injectCanonicalHeadBlock(t, datadir, genesisHash, 101)

exportCmd := runXDC(t, "--datadir", datadir, "--testnet", "export", exportFile)
assertCommandFailsWithChainConfigError(t, exportCmd, "Can't open blockchain in readonly mode: the local chain configuration requires rewind. Use the correct --networkid/--datadir combination, or reopen the database in writable mode so the chain can rewind, then retry.")
assertCommandFailsWithChainConfigErrors(
t,
exportCmd,
"Can't open blockchain: mismatching",
cmdutils.ChainConfigMismatchPolicyExitHint,
)
if _, err := os.Stat(exportFile); err == nil {
t.Fatalf("expected export to fail without creating %s", exportFile)
} else if !os.IsNotExist(err) {
Expand Down Expand Up @@ -929,7 +939,12 @@ func TestOfflineExportFailsReadonlyConfigRewindToZeroWithoutMutation(t *testing.
injectedHeadHash := injectCanonicalHeadBlock(t, datadir, genesisHash, 1)

exportCmd := runXDC(t, "--datadir", datadir, "--testnet", "export", exportFile)
assertCommandFailsWithChainConfigError(t, exportCmd, "Can't open blockchain in readonly mode: the local chain configuration requires rewind. Use the correct --networkid/--datadir combination, or reopen the database in writable mode so the chain can rewind, then retry.")
assertCommandFailsWithChainConfigErrors(
t,
exportCmd,
"Can't open blockchain: mismatching",
cmdutils.ChainConfigMismatchPolicyExitHint,
)
if _, err := os.Stat(exportFile); err == nil {
t.Fatalf("expected export to fail without creating %s", exportFile)
} else if !os.IsNotExist(err) {
Expand Down Expand Up @@ -967,7 +982,7 @@ func TestStartupRewindsStoredBuiltInHistoricalForkDrift(t *testing.T) {
genesisHash := overwriteStoredBerlinBlock(t, datadir, big.NewInt(100))
injectedHeadHash := injectCanonicalHeadBlock(t, datadir, genesisHash, 101)

startTestnetConsole(t, datadir)
startTestnetConsole(t, datadir, "--chain-config-mismatch-policy", "rewind-and-update")

path := filepath.Join(datadir, "XDC", "chaindata")
db := openTestChainDB(t, path)
Expand All @@ -993,12 +1008,12 @@ func TestStartupRewindsStoredBuiltInHistoricalForkDrift(t *testing.T) {
func TestStartupRewindsStoredBuiltInConfigDriftToZero(t *testing.T) {
datadir := t.TempDir()

startTestnetConsole(t, datadir)
startTestnetConsole(t, datadir, "--chain-config-mismatch-policy", "rewind-and-update")

genesisHash := overwriteStoredEIP150Block(t, datadir, big.NewInt(1))
injectedHeadHash := injectCanonicalHeadBlock(t, datadir, genesisHash, 1)

startTestnetConsole(t, datadir)
startTestnetConsole(t, datadir, "--chain-config-mismatch-policy", "rewind-and-update")

path := filepath.Join(datadir, "XDC", "chaindata")
db := openTestChainDB(t, path)
Expand Down
1 change: 1 addition & 0 deletions cmd/XDC/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ var (
utils.VMTraceJsonConfigFlag,
utils.NetworkIdFlag,
utils.AllowBuiltInConfigOverrideFlag,
utils.ChainConfigMismatchPolicyFlag,
utils.HTTPCORSDomainFlag,
utils.AuthListenFlag,
utils.AuthPortFlag,
Expand Down
18 changes: 18 additions & 0 deletions cmd/utils/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import (

const (
importBatchSize = 2500

ChainConfigMismatchPolicyExitHint = "Hint: Use --chain-config-mismatch-policy to recover; Or restart with a matching XDC binary and the same network/genesis settings."
)

// Fatalf formats a message to standard error and exits the program.
Expand Down Expand Up @@ -72,6 +74,22 @@ func FormatChainConfigError(err error) string {
return ""
}
message := err.Error()
if errors.Is(err, core.ErrConfigMismatchPolicyExit) {
exitText := core.ErrConfigMismatchPolicyExit.Error()
guidance := ChainConfigMismatchPolicyExitHint
prefix := exitText + ": "
if after, ok := strings.CutPrefix(message, prefix); ok {
details := strings.TrimSpace(after)
if details == "" {
return guidance
}
return details + ".\n" + guidance
}
if message == exitText {
return guidance
}
return message + ". " + ChainConfigMismatchPolicyExitHint
}
if !errors.Is(err, params.ErrMissingForkSwitch) {
return message
}
Expand Down
55 changes: 45 additions & 10 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ var (
Usage: "Allow same-hash custom overrides on built-in IDs to use custom chain config",
Category: flags.EthCategory,
}
ChainConfigMismatchPolicyFlag = &cli.StringFlag{
Name: "chain-config-mismatch-policy",
Usage: "Startup policy when chain config mismatches stored config: exit|rewind-and-update|update-config-only|ignore-mismatch (warning: update-config-only/ignore-mismatch may cause state/consensus divergence; expert use only)",
Value: core.DefaultChainConfigMismatchPolicy.String(),
Category: flags.EthCategory,
}

// Dev mode
DeveloperFlag = &cli.BoolFlag{
Expand Down Expand Up @@ -1520,6 +1526,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setMiner(ctx, &cfg.Miner)
setLes(ctx, cfg)
cfg.AllowBuiltInCustomRecovery = ctx.Bool(AllowBuiltInConfigOverrideFlag.Name)
cfg.ChainConfigMismatchPolicy = resolveChainConfigMismatchPolicyOrFatal(ctx, cfg.ChainConfigMismatchPolicy)

// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()
Expand Down Expand Up @@ -1850,14 +1857,18 @@ func formatBlockChainOpenError(err error, readonly bool) string {
return fmt.Sprintf("Can't create BlockChain: %v", err)
}
switch {
case errors.Is(err, core.ErrConfigMismatchPolicyExit):
return "Can't open blockchain: " + FormatChainConfigError(err)
case errors.Is(err, core.ErrReadOnlyGenesisStateRecovery):
return "Can't open blockchain in readonly mode: genesis state is missing and requires recovery. Reopen the database in writable mode to recover the missing genesis state, then retry."
case errors.Is(err, core.ErrReadOnlyHeadStateRepair):
return "Can't open blockchain in readonly mode: head state is missing and requires repair. Reopen the database in writable mode to repair the missing head state, then retry."
case errors.Is(err, core.ErrReadOnlyBadHashRewind):
return "Can't open blockchain in readonly mode: the local chain contains a denylisted hash and requires rewind. Reopen the database in writable mode so the chain can rewind past the denylisted hash, then retry."
case errors.Is(err, core.ErrReadOnlyConfigRewind):
return "Can't open blockchain in readonly mode: the local chain configuration requires rewind. Use the correct --networkid/--datadir combination, or reopen the database in writable mode so the chain can rewind, then retry."
return "Can't open blockchain in readonly mode: the selected chain-config mismatch policy requires rewind. Reopen in writable mode, or use --chain-config-mismatch-policy=ignore-mismatch to avoid rewind in readonly mode."
case errors.Is(err, core.ErrReadOnlyConfigUpdate):
return "Can't open blockchain in readonly mode: the selected chain-config mismatch policy requires writing chain config. Reopen in writable mode, or use --chain-config-mismatch-policy=ignore-mismatch in readonly mode."
default:
return fmt.Sprintf("Can't create BlockChain: %v", err)
}
Expand All @@ -1866,14 +1877,15 @@ func formatBlockChainOpenError(err error, readonly bool) string {
var makeChainFatalf = Fatalf

// MakeChain creates a chain manager from set command line flags.
func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockChain, ethdb.Database) {
func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool, configuredCompatPolicy string) (*core.BlockChain, ethdb.Database) {
var (
gspec = MakeGenesis(ctx)
chainDb = MakeChainDatabase(ctx, stack, readonly)
config *params.ChainConfig
ghash common.Hash
compatErr *params.ConfigCompatError
err error
gspec = MakeGenesis(ctx)
chainDb = MakeChainDatabase(ctx, stack, readonly)
config *params.ChainConfig
ghash common.Hash
compatErr *params.ConfigCompatError
compatPolicy = core.ChainConfigMismatchPolicy(resolveChainConfigMismatchPolicyOrFatal(ctx, configuredCompatPolicy))
err error
)
if readonly {
// Readonly startup still needs compatibility metadata so chain open can
Expand Down Expand Up @@ -1933,9 +1945,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
// Disable transaction indexing/unindexing by default.
var chain *core.BlockChain
if readonly {
chain, err = core.NewBlockChainReadOnlyResolved(chainDb, cache, gspec, engine, vmcfg, config, ghash, compatErr)
chain, err = core.NewBlockChainReadOnlyResolved(chainDb, cache, gspec, engine, vmcfg, config, ghash, compatErr, compatPolicy)
} else {
chain, err = core.NewBlockChainResolved(chainDb, cache, gspec, engine, vmcfg, config, ghash, compatErr)
chain, err = core.NewBlockChainResolved(chainDb, cache, gspec, engine, vmcfg, config, ghash, compatErr, compatPolicy)
}
if err != nil {
makeChainFatalf("%s", formatBlockChainOpenError(err, readonly))
Expand All @@ -1944,6 +1956,29 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
return chain, chainDb
}

func resolveChainConfigMismatchPolicy(ctx *cli.Context, configured string) (string, error) {
raw := configured
errPrefix := "invalid ChainConfigMismatchPolicy in config"
if ctx.IsSet(ChainConfigMismatchPolicyFlag.Name) {
raw = ctx.String(ChainConfigMismatchPolicyFlag.Name)
errPrefix = fmt.Sprintf("invalid --%s flag", ChainConfigMismatchPolicyFlag.Name)
}
policy, err := core.ParseChainConfigMismatchPolicy(raw)
if err != nil {
return "", fmt.Errorf("%s: %w", errPrefix, err)
}
log.Info("Resolved chain config mismatch policy", "value", policy.String())
return policy.String(), nil
}

func resolveChainConfigMismatchPolicyOrFatal(ctx *cli.Context, configured string) string {
policy, err := resolveChainConfigMismatchPolicy(ctx, configured)
if err != nil {
makeChainFatalf("%v", err)
}
return policy
}

// MakeConsolePreloads retrieves the absolute paths for the console JavaScript
// scripts to preload before starting.
func MakeConsolePreloads(ctx *cli.Context) []string {
Expand Down
84 changes: 75 additions & 9 deletions cmd/utils/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
Expand Down Expand Up @@ -195,10 +196,11 @@ func TestMakeChainWriteModePassesCompatRewindToCore(t *testing.T) {
}

ctx := newMakeChainTestCLIContext(t, map[string]string{
TestnetFlag.Name: "true",
GCModeFlag.Name: "full",
TestnetFlag.Name: "true",
GCModeFlag.Name: "full",
ChainConfigMismatchPolicyFlag.Name: core.MismatchRewindAndUpdate.String(),
})
chain, reopenedDb := MakeChain(ctx, stack, false)
chain, reopenedDb := MakeChain(ctx, stack, false, "")
defer chain.Stop()
defer reopenedDb.Close()

Expand Down Expand Up @@ -269,7 +271,7 @@ func TestMakeChainReadOnlyModeSurfacesCompatRewind(t *testing.T) {
t.Fatal("expected compatibility error")
}

chain, err := core.NewBlockChainReadOnlyResolved(readonlyDb, nil, genesis, ethash.NewFaker(), vm.Config{}, config, ghash, compatErr)
chain, err := core.NewBlockChainReadOnlyResolved(readonlyDb, nil, genesis, ethash.NewFaker(), vm.Config{}, config, ghash, compatErr, core.MismatchRewindAndUpdate)
if chain != nil {
chain.Stop()
t.Fatal("expected readonly blockchain open to fail")
Expand Down Expand Up @@ -317,8 +319,9 @@ func TestMakeChainReadOnlyModeFormatsCompatRewindForOperators(t *testing.T) {
}

ctx := newMakeChainTestCLIContext(t, map[string]string{
TestnetFlag.Name: "true",
GCModeFlag.Name: "full",
TestnetFlag.Name: "true",
GCModeFlag.Name: "full",
ChainConfigMismatchPolicyFlag.Name: core.MismatchRewindAndUpdate.String(),
})

const sentinel = "fatal called"
Expand All @@ -334,13 +337,13 @@ func TestMakeChainReadOnlyModeFormatsCompatRewindForOperators(t *testing.T) {
if recovered := recover(); recovered != sentinel {
t.Fatalf("expected sentinel panic, have %v", recovered)
}
want := "Can't open blockchain in readonly mode: the local chain configuration requires rewind. Use the correct --networkid/--datadir combination, or reopen the database in writable mode so the chain can rewind, then retry."
want := "Can't open blockchain in readonly mode: the selected chain-config mismatch policy requires rewind. Reopen in writable mode, or use --chain-config-mismatch-policy=ignore-mismatch to avoid rewind in readonly mode."
if got != want {
t.Fatalf("unexpected fatal message: have %q want %q", got, want)
}
}()

MakeChain(ctx, stack, true)
MakeChain(ctx, stack, true, "")
t.Fatal("expected MakeChain to terminate via fatal hook")
}

Expand Down Expand Up @@ -371,7 +374,17 @@ func TestFormatBlockChainOpenErrorReadOnly(t *testing.T) {
{
name: "config rewind",
err: core.ErrReadOnlyConfigRewind,
want: "Can't open blockchain in readonly mode: the local chain configuration requires rewind. Use the correct --networkid/--datadir combination, or reopen the database in writable mode so the chain can rewind, then retry.",
want: "Can't open blockchain in readonly mode: the selected chain-config mismatch policy requires rewind. Reopen in writable mode, or use --chain-config-mismatch-policy=ignore-mismatch to avoid rewind in readonly mode.",
},
{
name: "config update",
err: core.ErrReadOnlyConfigUpdate,
want: "Can't open blockchain in readonly mode: the selected chain-config mismatch policy requires writing chain config. Reopen in writable mode, or use --chain-config-mismatch-policy=ignore-mismatch in readonly mode.",
},
{
name: "config exit",
err: core.ErrConfigMismatchPolicyExit,
want: "Can't open blockchain: " + ChainConfigMismatchPolicyExitHint,
},
}

Expand All @@ -394,12 +407,65 @@ func TestFormatBlockChainOpenErrorReadOnly(t *testing.T) {
}
}

func TestResolveChainConfigMismatchPolicyHonorsConfiguredWhenFlagNotSet(t *testing.T) {
t.Parallel()

set := flag.NewFlagSet("resolve-policy-test", flag.ContinueOnError)
set.String(ChainConfigMismatchPolicyFlag.Name, core.DefaultChainConfigMismatchPolicy.String(), "")
ctx := cli.NewContext(cli.NewApp(), set, nil)

got, err := resolveChainConfigMismatchPolicy(ctx, core.MismatchIgnoreMismatch.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != core.MismatchIgnoreMismatch.String() {
t.Fatalf("unexpected policy: have %q want %q", got, core.MismatchIgnoreMismatch.String())
}
}

func TestResolveChainConfigMismatchPolicyFlagOverridesConfigured(t *testing.T) {
t.Parallel()

set := flag.NewFlagSet("resolve-policy-override-test", flag.ContinueOnError)
set.String(ChainConfigMismatchPolicyFlag.Name, core.DefaultChainConfigMismatchPolicy.String(), "")
if err := set.Set(ChainConfigMismatchPolicyFlag.Name, core.MismatchUpdateConfigOnly.String()); err != nil {
t.Fatalf("failed to set policy flag: %v", err)
}
ctx := cli.NewContext(cli.NewApp(), set, nil)

got, err := resolveChainConfigMismatchPolicy(ctx, core.MismatchIgnoreMismatch.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != core.MismatchUpdateConfigOnly.String() {
t.Fatalf("unexpected policy: have %q want %q", got, core.MismatchUpdateConfigOnly.String())
}
}

func TestResolveChainConfigMismatchPolicyRejectsInvalidConfiguredValue(t *testing.T) {
t.Parallel()

set := flag.NewFlagSet("resolve-policy-invalid-test", flag.ContinueOnError)
set.String(ChainConfigMismatchPolicyFlag.Name, core.DefaultChainConfigMismatchPolicy.String(), "")
ctx := cli.NewContext(cli.NewApp(), set, nil)

_, err := resolveChainConfigMismatchPolicy(ctx, "not-a-policy")
if err == nil {
t.Fatal("expected error for invalid configured policy")
}
const want = "invalid ChainConfigMismatchPolicy in config"
if !strings.HasPrefix(err.Error(), want) {
t.Fatalf("unexpected error: %v", err)
}
}

// newMakeChainTestCLIContext builds a minimal CLI context for MakeChain tests.
func newMakeChainTestCLIContext(t *testing.T, values map[string]string) *cli.Context {
t.Helper()
set := flag.NewFlagSet("make-chain-test", flag.ContinueOnError)
set.Bool(TestnetFlag.Name, false, "")
set.Bool(AllowBuiltInConfigOverrideFlag.Name, false, "")
set.String(ChainConfigMismatchPolicyFlag.Name, core.DefaultChainConfigMismatchPolicy.String(), "")
set.String(GCModeFlag.Name, "full", "")
for name, value := range values {
if err := set.Set(name, value); err != nil {
Expand Down
Loading
Loading