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
32 changes: 26 additions & 6 deletions cmd/utils/app/import_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const (
importBatchSize = 2500
)

var errInterrupted = errors.New("interrupted")

var importCommand = cli.Command{
Action: MigrateFlags(importChain),
Name: "import",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Comment thread
yperbasis marked this conversation as resolved.
}
}
return importErr
}

func ImportChain(ethereum *eth.Ethereum, chainDB kv.RwDB, fn string, logger log.Logger) error {
Expand Down Expand Up @@ -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++ {
Expand All @@ -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()
Expand Down
74 changes: 74 additions & 0 deletions cmd/utils/app/import_cmd_test.go
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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")
}
Loading