Skip to content

Commit 2e8b58f

Browse files
authored
cmd/geth: implement data import and export (#22931)
This PR offers two more database sub commands for exporting and importing data. Two exporters are implemented: preimage and snapshot data respectively. The import command is generic, it can take any data export and import into leveldb. The data format has a 'magic' for disambiguation, and a version field for future compatibility.
1 parent 551bd6e commit 2e8b58f

File tree

7 files changed

+590
-15
lines changed

7 files changed

+590
-15
lines changed

cmd/geth/chaincmd.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ be gzipped.`,
140140
},
141141
Category: "BLOCKCHAIN COMMANDS",
142142
Description: `
143-
The import-preimages command imports hash preimages from an RLP encoded stream.`,
143+
The import-preimages command imports hash preimages from an RLP encoded stream.
144+
It's deprecated, please use "geth db import" instead.
145+
`,
144146
}
145147
exportPreimagesCommand = cli.Command{
146148
Action: utils.MigrateFlags(exportPreimages),
@@ -154,7 +156,9 @@ be gzipped.`,
154156
},
155157
Category: "BLOCKCHAIN COMMANDS",
156158
Description: `
157-
The export-preimages command export hash preimages to an RLP encoded stream`,
159+
The export-preimages command exports hash preimages to an RLP encoded stream.
160+
It's deprecated, please use "geth db export" instead.
161+
`,
158162
}
159163
dumpCommand = cli.Command{
160164
Action: utils.MigrateFlags(dump),
@@ -368,7 +372,6 @@ func exportPreimages(ctx *cli.Context) error {
368372
if len(ctx.Args()) < 1 {
369373
utils.Fatalf("This command requires an argument.")
370374
}
371-
372375
stack, _ := makeConfigNode(ctx)
373376
defer stack.Close()
374377

cmd/geth/dbcmd.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package main
1818

1919
import (
20+
"bytes"
2021
"errors"
2122
"fmt"
2223
"os"
24+
"os/signal"
2325
"path/filepath"
2426
"sort"
2527
"strconv"
28+
"strings"
29+
"syscall"
2630
"time"
2731

2832
"github.com/ethereum/go-ethereum/cmd/utils"
@@ -63,6 +67,8 @@ Remove blockchain and state databases`,
6367
dbPutCmd,
6468
dbGetSlotsCmd,
6569
dbDumpFreezerIndex,
70+
dbImportCmd,
71+
dbExportCmd,
6672
},
6773
}
6874
dbInspectCmd = cli.Command{
@@ -188,6 +194,36 @@ WARNING: This is a low-level operation which may cause database corruption!`,
188194
},
189195
Description: "This command displays information about the freezer index.",
190196
}
197+
dbImportCmd = cli.Command{
198+
Action: utils.MigrateFlags(importLDBdata),
199+
Name: "import",
200+
Usage: "Imports leveldb-data from an exported RLP dump.",
201+
ArgsUsage: "<dumpfile> <start (optional)",
202+
Flags: []cli.Flag{
203+
utils.DataDirFlag,
204+
utils.SyncModeFlag,
205+
utils.MainnetFlag,
206+
utils.RopstenFlag,
207+
utils.RinkebyFlag,
208+
utils.GoerliFlag,
209+
},
210+
Description: "The import command imports the specific chain data from an RLP encoded stream.",
211+
}
212+
dbExportCmd = cli.Command{
213+
Action: utils.MigrateFlags(exportChaindata),
214+
Name: "export",
215+
Usage: "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
216+
ArgsUsage: "<type> <dumpfile>",
217+
Flags: []cli.Flag{
218+
utils.DataDirFlag,
219+
utils.SyncModeFlag,
220+
utils.MainnetFlag,
221+
utils.RopstenFlag,
222+
utils.RinkebyFlag,
223+
utils.GoerliFlag,
224+
},
225+
Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
226+
}
191227
)
192228

193229
func removeDB(ctx *cli.Context) error {
@@ -510,3 +546,133 @@ func parseHexOrString(str string) ([]byte, error) {
510546
}
511547
return b, err
512548
}
549+
550+
func importLDBdata(ctx *cli.Context) error {
551+
start := 0
552+
switch ctx.NArg() {
553+
case 1:
554+
break
555+
case 2:
556+
s, err := strconv.Atoi(ctx.Args().Get(1))
557+
if err != nil {
558+
return fmt.Errorf("second arg must be an integer: %v", err)
559+
}
560+
start = s
561+
default:
562+
return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
563+
}
564+
var (
565+
fName = ctx.Args().Get(0)
566+
stack, _ = makeConfigNode(ctx)
567+
interrupt = make(chan os.Signal, 1)
568+
stop = make(chan struct{})
569+
)
570+
defer stack.Close()
571+
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
572+
defer signal.Stop(interrupt)
573+
defer close(interrupt)
574+
go func() {
575+
if _, ok := <-interrupt; ok {
576+
log.Info("Interrupted during ldb import, stopping at next batch")
577+
}
578+
close(stop)
579+
}()
580+
db := utils.MakeChainDatabase(ctx, stack, false)
581+
return utils.ImportLDBData(db, fName, int64(start), stop)
582+
}
583+
584+
type preimageIterator struct {
585+
iter ethdb.Iterator
586+
}
587+
588+
func (iter *preimageIterator) Next() (byte, []byte, []byte, bool) {
589+
for iter.iter.Next() {
590+
key := iter.iter.Key()
591+
if bytes.HasPrefix(key, rawdb.PreimagePrefix) && len(key) == (len(rawdb.PreimagePrefix)+common.HashLength) {
592+
return utils.OpBatchAdd, key, iter.iter.Value(), true
593+
}
594+
}
595+
return 0, nil, nil, false
596+
}
597+
598+
func (iter *preimageIterator) Release() {
599+
iter.iter.Release()
600+
}
601+
602+
type snapshotIterator struct {
603+
init bool
604+
account ethdb.Iterator
605+
storage ethdb.Iterator
606+
}
607+
608+
func (iter *snapshotIterator) Next() (byte, []byte, []byte, bool) {
609+
if !iter.init {
610+
iter.init = true
611+
return utils.OpBatchDel, rawdb.SnapshotRootKey, nil, true
612+
}
613+
for iter.account.Next() {
614+
key := iter.account.Key()
615+
if bytes.HasPrefix(key, rawdb.SnapshotAccountPrefix) && len(key) == (len(rawdb.SnapshotAccountPrefix)+common.HashLength) {
616+
return utils.OpBatchAdd, key, iter.account.Value(), true
617+
}
618+
}
619+
for iter.storage.Next() {
620+
key := iter.storage.Key()
621+
if bytes.HasPrefix(key, rawdb.SnapshotStoragePrefix) && len(key) == (len(rawdb.SnapshotStoragePrefix)+2*common.HashLength) {
622+
return utils.OpBatchAdd, key, iter.storage.Value(), true
623+
}
624+
}
625+
return 0, nil, nil, false
626+
}
627+
628+
func (iter *snapshotIterator) Release() {
629+
iter.account.Release()
630+
iter.storage.Release()
631+
}
632+
633+
// chainExporters defines the export scheme for all exportable chain data.
634+
var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
635+
"preimage": func(db ethdb.Database) utils.ChainDataIterator {
636+
iter := db.NewIterator(rawdb.PreimagePrefix, nil)
637+
return &preimageIterator{iter: iter}
638+
},
639+
"snapshot": func(db ethdb.Database) utils.ChainDataIterator {
640+
account := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
641+
storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
642+
return &snapshotIterator{account: account, storage: storage}
643+
},
644+
}
645+
646+
func exportChaindata(ctx *cli.Context) error {
647+
if ctx.NArg() < 2 {
648+
return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
649+
}
650+
// Parse the required chain data type, make sure it's supported.
651+
kind := ctx.Args().Get(0)
652+
kind = strings.ToLower(strings.Trim(kind, " "))
653+
exporter, ok := chainExporters[kind]
654+
if !ok {
655+
var kinds []string
656+
for kind := range chainExporters {
657+
kinds = append(kinds, kind)
658+
}
659+
return fmt.Errorf("invalid data type %s, supported types: %s", kind, strings.Join(kinds, ", "))
660+
}
661+
var (
662+
stack, _ = makeConfigNode(ctx)
663+
interrupt = make(chan os.Signal, 1)
664+
stop = make(chan struct{})
665+
)
666+
defer stack.Close()
667+
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
668+
defer signal.Stop(interrupt)
669+
defer close(interrupt)
670+
go func() {
671+
if _, ok := <-interrupt; ok {
672+
log.Info("Interrupted during db export, stopping at next batch")
673+
}
674+
close(stop)
675+
}()
676+
db := utils.MakeChainDatabase(ctx, stack, true)
677+
return utils.ExportChaindata(ctx.Args().Get(1), kind, exporter(db), stop)
678+
}

0 commit comments

Comments
 (0)