diff --git a/cmd/utils/app/import_cmd.go b/cmd/utils/app/import_cmd.go index d8c749327bb..f4fa78751e4 100644 --- a/cmd/utils/app/import_cmd.go +++ b/cmd/utils/app/import_cmd.go @@ -54,6 +54,8 @@ const ( importBatchSize = 2500 ) +var errInterrupted = errors.New("interrupted") + var importCommand = cli.Command{ Action: MigrateFlags(importChain), Name: "import", @@ -87,6 +89,7 @@ func importChain(cliCtx *cli.Context) error { utils.NATFlag.Name: "none", utils.NoDownloaderFlag.Name: "true", utils.ExternalConsensusFlag.Name: "true", + utils.MCPDisableFlag.Name: "true", } { if err := cliCtx.Set(flag, value); err != nil { return fmt.Errorf("importChain: set %s=%s: %w", flag, value, err) @@ -116,11 +119,28 @@ func importChain(cliCtx *cli.Context) error { return err } - if err := ImportChain(ethereum, ethereum.ChainDB(), cliCtx.Args().First(), logger); err != nil { - return err - } + return importFiles(cliCtx.Args().Slice(), logger, func(fn string) error { + return ImportChain(ethereum, ethereum.ChainDB(), fn, logger) + }) +} - return nil +// importFiles imports each file in order; with more than one file, per-file +// failures are logged and skipped (matching go-ethereum), except a user +// interrupt aborts the whole command. +func importFiles(files []string, logger log.Logger, importOne func(fn string) error) error { + var importErr error + for _, fn := range files { + if err := importOne(fn); err != nil { + importErr = err + if errors.Is(err, errInterrupted) { + return err + } + if len(files) > 1 { + logger.Error("Import error", "file", fn, "err", err) + } + } + } + return importErr } func ImportChain(ethereum *eth.Ethereum, chainDB kv.RwDB, fn string, logger log.Logger) error { @@ -169,7 +189,7 @@ func ImportChain(ethereum *eth.Ethereum, chainDB kv.RwDB, fn string, logger log. for batch := 0; ; batch++ { // Load a batch of RLP blocks. if checkInterrupt() { - return errors.New("interrupted") + return errInterrupted } i := 0 for ; i < importBatchSize; i++ { @@ -192,7 +212,7 @@ func ImportChain(ethereum *eth.Ethereum, chainDB kv.RwDB, fn string, logger log. } // Import the batch. if checkInterrupt() { - return errors.New("interrupted") + return errInterrupted } br, _ := ethereum.BlockIO() diff --git a/cmd/utils/app/import_cmd_test.go b/cmd/utils/app/import_cmd_test.go new file mode 100644 index 00000000000..786cd6c447d --- /dev/null +++ b/cmd/utils/app/import_cmd_test.go @@ -0,0 +1,74 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package app + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon/common/log/v3" +) + +func TestImportFilesProcessesEveryFile(t *testing.T) { + files := []string{"0001.rlp", "0002.rlp", "0003.rlp"} + var imported []string + err := importFiles(files, log.Root(), func(fn string) error { + imported = append(imported, fn) + return nil + }) + require.NoError(t, err) + require.Equal(t, files, imported) +} + +func TestImportFilesContinuesPastPerFileFailure(t *testing.T) { + files := []string{"0001.rlp", "0002.rlp", "0003.rlp"} + badBlock := errors.New("invalid block") + var imported []string + err := importFiles(files, log.Root(), func(fn string) error { + imported = append(imported, fn) + if fn == "0002.rlp" { + return badBlock + } + return nil + }) + require.ErrorIs(t, err, badBlock) + require.Equal(t, files, imported, "a failing block file must not stop import of later files") +} + +func TestImportFilesSingleFileSurfacesError(t *testing.T) { + badBlock := errors.New("invalid block") + err := importFiles([]string{"0001.rlp"}, log.Root(), func(fn string) error { + return badBlock + }) + require.ErrorIs(t, err, badBlock) +} + +func TestImportFilesStopsOnInterrupt(t *testing.T) { + files := []string{"0001.rlp", "0002.rlp", "0003.rlp"} + var imported []string + err := importFiles(files, log.Root(), func(fn string) error { + imported = append(imported, fn) + if fn == "0002.rlp" { + return errInterrupted + } + return nil + }) + require.ErrorIs(t, err, errInterrupted) + require.Equal(t, []string{"0001.rlp", "0002.rlp"}, imported, "user interrupt must abort the whole import, not just skip the current file") +}