From 8846f648c38a9eeeb8c41164697688dc1f1d0c2c Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Thu, 5 Sep 2024 08:19:45 +0400 Subject: [PATCH] cli: add dump-bin command Dump blocks (starting with the genesis or specified block) to the directory in binary format. Signed-off-by: Ekaterina Pavlova --- cli/server/dump_bin.go | 87 +++++++++++++++++++++++++++++++++++ cli/server/dump_bin_test.go | 91 +++++++++++++++++++++++++++++++++++++ cli/server/server.go | 7 +++ 3 files changed, 185 insertions(+) create mode 100644 cli/server/dump_bin.go create mode 100644 cli/server/dump_bin_test.go diff --git a/cli/server/dump_bin.go b/cli/server/dump_bin.go new file mode 100644 index 000000000..00aeb932e --- /dev/null +++ b/cli/server/dump_bin.go @@ -0,0 +1,87 @@ +package server + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/nspcc-dev/neo-go/cli/cmdargs" + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/urfave/cli/v2" +) + +func dumpBin(ctx *cli.Context) error { + if err := cmdargs.EnsureNone(ctx); err != nil { + return err + } + cfg, err := options.GetConfigFromContext(ctx) + if err != nil { + return cli.Exit(err, 1) + } + log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) + if err != nil { + return cli.Exit(err, 1) + } + if logCloser != nil { + defer func() { _ = logCloser() }() + } + count := uint32(ctx.Uint("count")) + start := uint32(ctx.Uint("start")) + + chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) + if err != nil { + return err + } + defer func() { + pprof.ShutDown() + prometheus.ShutDown() + chain.Close() + }() + + blocksCount := chain.BlockHeight() + 1 + if start+count > blocksCount { + return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", blocksCount-1, count, start), 1) + } + if count == 0 { + count = blocksCount - start + } + + out := ctx.String("out") + if out == "" { + return cli.Exit("output directory is not specified", 1) + } + if _, err = os.Stat(out); os.IsNotExist(err) { + if err = os.MkdirAll(out, os.ModePerm); err != nil { + return cli.Exit(fmt.Sprintf("failed to create directory %s: %s", out, err), 1) + } + } + if err != nil { + return cli.Exit(fmt.Sprintf("failed to check directory %s: %s", out, err), 1) + } + + for i := start; i < start+count; i++ { + blk, err := chain.GetBlock(chain.GetHeaderHash(i)) + if err != nil { + return cli.Exit(fmt.Sprintf("failed to get block %d: %s", i, err), 1) + } + filePath := filepath.Join(out, fmt.Sprintf("block-%d.bin", i)) + if err = saveBlockToFile(blk, filePath); err != nil { + return cli.Exit(fmt.Sprintf("failed to save block %d to file %s: %s", i, filePath, err), 1) + } + } + return nil +} + +func saveBlockToFile(blk *block.Block, filePath string) error { + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + writer := io.NewBinWriterFromIO(file) + blk.EncodeBinary(writer) + return writer.Err +} diff --git a/cli/server/dump_bin_test.go b/cli/server/dump_bin_test.go new file mode 100644 index 000000000..0524f2c1d --- /dev/null +++ b/cli/server/dump_bin_test.go @@ -0,0 +1,91 @@ +package server_test + +import ( + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testcli" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestDumpBin(t *testing.T) { + tmpDir := t.TempDir() + + loadConfig := func(t *testing.T) config.Config { + chainPath := filepath.Join(tmpDir, "neogotestchain") + cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + return cfg + } + + cfg := loadConfig(t) + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) + + e := testcli.NewExecutor(t, false) + + restoreArgs := []string{"neo-go", "db", "restore", + "--config-file", cfgPath, "--in", inDump} + e.Run(t, restoreArgs...) + + t.Run("missing output directory", func(t *testing.T) { + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", ""} + e.RunWithErrorCheck(t, "output directory is not specified", args...) + }) + + t.Run("successful dump", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir, "--count", "5", "--start", "0"} + + e.Run(t, args...) + + require.DirExists(t, outDir) + + for i := range 5 { + blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") + require.FileExists(t, blockFile) + } + }) + + t.Run("invalid block range", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "invalid-blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir, "--count", "1000", "--start", "0"} + + e.RunWithError(t, args...) + }) + + t.Run("zero blocks (full chain dump)", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "full-dump") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", cfgPath, "--out", outDir} + + e.Run(t, args...) + + require.DirExists(t, outDir) + for i := range 5 { + blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") + require.FileExists(t, blockFile) + } + }) + + t.Run("invalid config file", func(t *testing.T) { + outDir := filepath.Join(tmpDir, "blocks") + args := []string{"neo-go", "db", "dump-bin", + "--config-file", "invalid-config-path", "--out", outDir} + + e.RunWithError(t, args...) + }) +} diff --git a/cli/server/server.go b/cli/server/server.go index ceab19586..b3f9ec22b 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -102,6 +102,13 @@ func NewCommands() []*cli.Command { Action: dumpDB, Flags: cfgCountOutFlags, }, + { + Name: "dump-bin", + Usage: "Dump blocks (starting with the genesis or specified block) to the directory in binary format", + UsageText: "neo-go db dump-bin -o directory [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]", + Action: dumpBin, + Flags: cfgCountOutFlags, + }, { Name: "restore", Usage: "Restore blocks from the file",