github.com/LampardNguyen234/go-ethereum@v1.10.16-0.20220117140830-b6a3b0260724/cmd/geth/dbcmd.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"os/signal"
    25  	"path/filepath"
    26  	"sort"
    27  	"strconv"
    28  	"strings"
    29  	"syscall"
    30  	"time"
    31  
    32  	"github.com/LampardNguyen234/go-ethereum/cmd/utils"
    33  	"github.com/LampardNguyen234/go-ethereum/common"
    34  	"github.com/LampardNguyen234/go-ethereum/common/hexutil"
    35  	"github.com/LampardNguyen234/go-ethereum/console/prompt"
    36  	"github.com/LampardNguyen234/go-ethereum/core/rawdb"
    37  	"github.com/LampardNguyen234/go-ethereum/ethdb"
    38  	"github.com/LampardNguyen234/go-ethereum/log"
    39  	"github.com/LampardNguyen234/go-ethereum/trie"
    40  	"gopkg.in/urfave/cli.v1"
    41  )
    42  
    43  var (
    44  	removedbCommand = cli.Command{
    45  		Action:    utils.MigrateFlags(removeDB),
    46  		Name:      "removedb",
    47  		Usage:     "Remove blockchain and state databases",
    48  		ArgsUsage: "",
    49  		Flags: []cli.Flag{
    50  			utils.DataDirFlag,
    51  		},
    52  		Category: "DATABASE COMMANDS",
    53  		Description: `
    54  Remove blockchain and state databases`,
    55  	}
    56  	dbCommand = cli.Command{
    57  		Name:      "db",
    58  		Usage:     "Low level database operations",
    59  		ArgsUsage: "",
    60  		Category:  "DATABASE COMMANDS",
    61  		Subcommands: []cli.Command{
    62  			dbInspectCmd,
    63  			dbStatCmd,
    64  			dbCompactCmd,
    65  			dbGetCmd,
    66  			dbDeleteCmd,
    67  			dbPutCmd,
    68  			dbGetSlotsCmd,
    69  			dbDumpFreezerIndex,
    70  			dbImportCmd,
    71  			dbExportCmd,
    72  		},
    73  	}
    74  	dbInspectCmd = cli.Command{
    75  		Action:    utils.MigrateFlags(inspect),
    76  		Name:      "inspect",
    77  		ArgsUsage: "<prefix> <start>",
    78  		Flags: []cli.Flag{
    79  			utils.DataDirFlag,
    80  			utils.AncientFlag,
    81  			utils.SyncModeFlag,
    82  			utils.MainnetFlag,
    83  			utils.RopstenFlag,
    84  			utils.SepoliaFlag,
    85  			utils.RinkebyFlag,
    86  			utils.GoerliFlag,
    87  		},
    88  		Usage:       "Inspect the storage size for each type of data in the database",
    89  		Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`,
    90  	}
    91  	dbStatCmd = cli.Command{
    92  		Action: utils.MigrateFlags(dbStats),
    93  		Name:   "stats",
    94  		Usage:  "Print leveldb statistics",
    95  		Flags: []cli.Flag{
    96  			utils.DataDirFlag,
    97  			utils.SyncModeFlag,
    98  			utils.MainnetFlag,
    99  			utils.RopstenFlag,
   100  			utils.SepoliaFlag,
   101  			utils.RinkebyFlag,
   102  			utils.GoerliFlag,
   103  		},
   104  	}
   105  	dbCompactCmd = cli.Command{
   106  		Action: utils.MigrateFlags(dbCompact),
   107  		Name:   "compact",
   108  		Usage:  "Compact leveldb database. WARNING: May take a very long time",
   109  		Flags: []cli.Flag{
   110  			utils.DataDirFlag,
   111  			utils.SyncModeFlag,
   112  			utils.MainnetFlag,
   113  			utils.RopstenFlag,
   114  			utils.SepoliaFlag,
   115  			utils.RinkebyFlag,
   116  			utils.GoerliFlag,
   117  			utils.CacheFlag,
   118  			utils.CacheDatabaseFlag,
   119  		},
   120  		Description: `This command performs a database compaction. 
   121  WARNING: This operation may take a very long time to finish, and may cause database
   122  corruption if it is aborted during execution'!`,
   123  	}
   124  	dbGetCmd = cli.Command{
   125  		Action:    utils.MigrateFlags(dbGet),
   126  		Name:      "get",
   127  		Usage:     "Show the value of a database key",
   128  		ArgsUsage: "<hex-encoded key>",
   129  		Flags: []cli.Flag{
   130  			utils.DataDirFlag,
   131  			utils.SyncModeFlag,
   132  			utils.MainnetFlag,
   133  			utils.RopstenFlag,
   134  			utils.SepoliaFlag,
   135  			utils.RinkebyFlag,
   136  			utils.GoerliFlag,
   137  		},
   138  		Description: "This command looks up the specified database key from the database.",
   139  	}
   140  	dbDeleteCmd = cli.Command{
   141  		Action:    utils.MigrateFlags(dbDelete),
   142  		Name:      "delete",
   143  		Usage:     "Delete a database key (WARNING: may corrupt your database)",
   144  		ArgsUsage: "<hex-encoded key>",
   145  		Flags: []cli.Flag{
   146  			utils.DataDirFlag,
   147  			utils.SyncModeFlag,
   148  			utils.MainnetFlag,
   149  			utils.RopstenFlag,
   150  			utils.SepoliaFlag,
   151  			utils.RinkebyFlag,
   152  			utils.GoerliFlag,
   153  		},
   154  		Description: `This command deletes the specified database key from the database. 
   155  WARNING: This is a low-level operation which may cause database corruption!`,
   156  	}
   157  	dbPutCmd = cli.Command{
   158  		Action:    utils.MigrateFlags(dbPut),
   159  		Name:      "put",
   160  		Usage:     "Set the value of a database key (WARNING: may corrupt your database)",
   161  		ArgsUsage: "<hex-encoded key> <hex-encoded value>",
   162  		Flags: []cli.Flag{
   163  			utils.DataDirFlag,
   164  			utils.SyncModeFlag,
   165  			utils.MainnetFlag,
   166  			utils.RopstenFlag,
   167  			utils.SepoliaFlag,
   168  			utils.RinkebyFlag,
   169  			utils.GoerliFlag,
   170  		},
   171  		Description: `This command sets a given database key to the given value. 
   172  WARNING: This is a low-level operation which may cause database corruption!`,
   173  	}
   174  	dbGetSlotsCmd = cli.Command{
   175  		Action:    utils.MigrateFlags(dbDumpTrie),
   176  		Name:      "dumptrie",
   177  		Usage:     "Show the storage key/values of a given storage trie",
   178  		ArgsUsage: "<hex-encoded storage trie root> <hex-encoded start (optional)> <int max elements (optional)>",
   179  		Flags: []cli.Flag{
   180  			utils.DataDirFlag,
   181  			utils.SyncModeFlag,
   182  			utils.MainnetFlag,
   183  			utils.RopstenFlag,
   184  			utils.SepoliaFlag,
   185  			utils.RinkebyFlag,
   186  			utils.GoerliFlag,
   187  		},
   188  		Description: "This command looks up the specified database key from the database.",
   189  	}
   190  	dbDumpFreezerIndex = cli.Command{
   191  		Action:    utils.MigrateFlags(freezerInspect),
   192  		Name:      "freezer-index",
   193  		Usage:     "Dump out the index of a given freezer type",
   194  		ArgsUsage: "<type> <start (int)> <end (int)>",
   195  		Flags: []cli.Flag{
   196  			utils.DataDirFlag,
   197  			utils.SyncModeFlag,
   198  			utils.MainnetFlag,
   199  			utils.RopstenFlag,
   200  			utils.SepoliaFlag,
   201  			utils.RinkebyFlag,
   202  			utils.GoerliFlag,
   203  		},
   204  		Description: "This command displays information about the freezer index.",
   205  	}
   206  	dbImportCmd = cli.Command{
   207  		Action:    utils.MigrateFlags(importLDBdata),
   208  		Name:      "import",
   209  		Usage:     "Imports leveldb-data from an exported RLP dump.",
   210  		ArgsUsage: "<dumpfile> <start (optional)",
   211  		Flags: []cli.Flag{
   212  			utils.DataDirFlag,
   213  			utils.SyncModeFlag,
   214  			utils.MainnetFlag,
   215  			utils.RopstenFlag,
   216  			utils.RinkebyFlag,
   217  			utils.GoerliFlag,
   218  		},
   219  		Description: "The import command imports the specific chain data from an RLP encoded stream.",
   220  	}
   221  	dbExportCmd = cli.Command{
   222  		Action:    utils.MigrateFlags(exportChaindata),
   223  		Name:      "export",
   224  		Usage:     "Exports the chain data into an RLP dump. If the <dumpfile> has .gz suffix, gzip compression will be used.",
   225  		ArgsUsage: "<type> <dumpfile>",
   226  		Flags: []cli.Flag{
   227  			utils.DataDirFlag,
   228  			utils.SyncModeFlag,
   229  			utils.MainnetFlag,
   230  			utils.RopstenFlag,
   231  			utils.RinkebyFlag,
   232  			utils.GoerliFlag,
   233  		},
   234  		Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.",
   235  	}
   236  )
   237  
   238  func removeDB(ctx *cli.Context) error {
   239  	stack, config := makeConfigNode(ctx)
   240  
   241  	// Remove the full node state database
   242  	path := stack.ResolvePath("chaindata")
   243  	if common.FileExist(path) {
   244  		confirmAndRemoveDB(path, "full node state database")
   245  	} else {
   246  		log.Info("Full node state database missing", "path", path)
   247  	}
   248  	// Remove the full node ancient database
   249  	path = config.Eth.DatabaseFreezer
   250  	switch {
   251  	case path == "":
   252  		path = filepath.Join(stack.ResolvePath("chaindata"), "ancient")
   253  	case !filepath.IsAbs(path):
   254  		path = config.Node.ResolvePath(path)
   255  	}
   256  	if common.FileExist(path) {
   257  		confirmAndRemoveDB(path, "full node ancient database")
   258  	} else {
   259  		log.Info("Full node ancient database missing", "path", path)
   260  	}
   261  	// Remove the light node database
   262  	path = stack.ResolvePath("lightchaindata")
   263  	if common.FileExist(path) {
   264  		confirmAndRemoveDB(path, "light node database")
   265  	} else {
   266  		log.Info("Light node database missing", "path", path)
   267  	}
   268  	return nil
   269  }
   270  
   271  // confirmAndRemoveDB prompts the user for a last confirmation and removes the
   272  // folder if accepted.
   273  func confirmAndRemoveDB(database string, kind string) {
   274  	confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database))
   275  	switch {
   276  	case err != nil:
   277  		utils.Fatalf("%v", err)
   278  	case !confirm:
   279  		log.Info("Database deletion skipped", "path", database)
   280  	default:
   281  		start := time.Now()
   282  		filepath.Walk(database, func(path string, info os.FileInfo, err error) error {
   283  			// If we're at the top level folder, recurse into
   284  			if path == database {
   285  				return nil
   286  			}
   287  			// Delete all the files, but not subfolders
   288  			if !info.IsDir() {
   289  				os.Remove(path)
   290  				return nil
   291  			}
   292  			return filepath.SkipDir
   293  		})
   294  		log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start)))
   295  	}
   296  }
   297  
   298  func inspect(ctx *cli.Context) error {
   299  	var (
   300  		prefix []byte
   301  		start  []byte
   302  	)
   303  	if ctx.NArg() > 2 {
   304  		return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage)
   305  	}
   306  	if ctx.NArg() >= 1 {
   307  		if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil {
   308  			return fmt.Errorf("failed to hex-decode 'prefix': %v", err)
   309  		} else {
   310  			prefix = d
   311  		}
   312  	}
   313  	if ctx.NArg() >= 2 {
   314  		if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil {
   315  			return fmt.Errorf("failed to hex-decode 'start': %v", err)
   316  		} else {
   317  			start = d
   318  		}
   319  	}
   320  	stack, _ := makeConfigNode(ctx)
   321  	defer stack.Close()
   322  
   323  	db := utils.MakeChainDatabase(ctx, stack, true)
   324  	defer db.Close()
   325  
   326  	return rawdb.InspectDatabase(db, prefix, start)
   327  }
   328  
   329  func showLeveldbStats(db ethdb.Stater) {
   330  	if stats, err := db.Stat("leveldb.stats"); err != nil {
   331  		log.Warn("Failed to read database stats", "error", err)
   332  	} else {
   333  		fmt.Println(stats)
   334  	}
   335  	if ioStats, err := db.Stat("leveldb.iostats"); err != nil {
   336  		log.Warn("Failed to read database iostats", "error", err)
   337  	} else {
   338  		fmt.Println(ioStats)
   339  	}
   340  }
   341  
   342  func dbStats(ctx *cli.Context) error {
   343  	stack, _ := makeConfigNode(ctx)
   344  	defer stack.Close()
   345  
   346  	db := utils.MakeChainDatabase(ctx, stack, true)
   347  	defer db.Close()
   348  
   349  	showLeveldbStats(db)
   350  	return nil
   351  }
   352  
   353  func dbCompact(ctx *cli.Context) error {
   354  	stack, _ := makeConfigNode(ctx)
   355  	defer stack.Close()
   356  
   357  	db := utils.MakeChainDatabase(ctx, stack, false)
   358  	defer db.Close()
   359  
   360  	log.Info("Stats before compaction")
   361  	showLeveldbStats(db)
   362  
   363  	log.Info("Triggering compaction")
   364  	if err := db.Compact(nil, nil); err != nil {
   365  		log.Info("Compact err", "error", err)
   366  		return err
   367  	}
   368  	log.Info("Stats after compaction")
   369  	showLeveldbStats(db)
   370  	return nil
   371  }
   372  
   373  // dbGet shows the value of a given database key
   374  func dbGet(ctx *cli.Context) error {
   375  	if ctx.NArg() != 1 {
   376  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   377  	}
   378  	stack, _ := makeConfigNode(ctx)
   379  	defer stack.Close()
   380  
   381  	db := utils.MakeChainDatabase(ctx, stack, true)
   382  	defer db.Close()
   383  
   384  	key, err := parseHexOrString(ctx.Args().Get(0))
   385  	if err != nil {
   386  		log.Info("Could not decode the key", "error", err)
   387  		return err
   388  	}
   389  
   390  	data, err := db.Get(key)
   391  	if err != nil {
   392  		log.Info("Get operation failed", "key", fmt.Sprintf("0x%#x", key), "error", err)
   393  		return err
   394  	}
   395  	fmt.Printf("key %#x: %#x\n", key, data)
   396  	return nil
   397  }
   398  
   399  // dbDelete deletes a key from the database
   400  func dbDelete(ctx *cli.Context) error {
   401  	if ctx.NArg() != 1 {
   402  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   403  	}
   404  	stack, _ := makeConfigNode(ctx)
   405  	defer stack.Close()
   406  
   407  	db := utils.MakeChainDatabase(ctx, stack, false)
   408  	defer db.Close()
   409  
   410  	key, err := parseHexOrString(ctx.Args().Get(0))
   411  	if err != nil {
   412  		log.Info("Could not decode the key", "error", err)
   413  		return err
   414  	}
   415  	data, err := db.Get(key)
   416  	if err == nil {
   417  		fmt.Printf("Previous value: %#x\n", data)
   418  	}
   419  	if err = db.Delete(key); err != nil {
   420  		log.Info("Delete operation returned an error", "key", fmt.Sprintf("0x%#x", key), "error", err)
   421  		return err
   422  	}
   423  	return nil
   424  }
   425  
   426  // dbPut overwrite a value in the database
   427  func dbPut(ctx *cli.Context) error {
   428  	if ctx.NArg() != 2 {
   429  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   430  	}
   431  	stack, _ := makeConfigNode(ctx)
   432  	defer stack.Close()
   433  
   434  	db := utils.MakeChainDatabase(ctx, stack, false)
   435  	defer db.Close()
   436  
   437  	var (
   438  		key   []byte
   439  		value []byte
   440  		data  []byte
   441  		err   error
   442  	)
   443  	key, err = parseHexOrString(ctx.Args().Get(0))
   444  	if err != nil {
   445  		log.Info("Could not decode the key", "error", err)
   446  		return err
   447  	}
   448  	value, err = hexutil.Decode(ctx.Args().Get(1))
   449  	if err != nil {
   450  		log.Info("Could not decode the value", "error", err)
   451  		return err
   452  	}
   453  	data, err = db.Get(key)
   454  	if err == nil {
   455  		fmt.Printf("Previous value: %#x\n", data)
   456  	}
   457  	return db.Put(key, value)
   458  }
   459  
   460  // dbDumpTrie shows the key-value slots of a given storage trie
   461  func dbDumpTrie(ctx *cli.Context) error {
   462  	if ctx.NArg() < 1 {
   463  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   464  	}
   465  	stack, _ := makeConfigNode(ctx)
   466  	defer stack.Close()
   467  
   468  	db := utils.MakeChainDatabase(ctx, stack, true)
   469  	defer db.Close()
   470  	var (
   471  		root  []byte
   472  		start []byte
   473  		max   = int64(-1)
   474  		err   error
   475  	)
   476  	if root, err = hexutil.Decode(ctx.Args().Get(0)); err != nil {
   477  		log.Info("Could not decode the root", "error", err)
   478  		return err
   479  	}
   480  	stRoot := common.BytesToHash(root)
   481  	if ctx.NArg() >= 2 {
   482  		if start, err = hexutil.Decode(ctx.Args().Get(1)); err != nil {
   483  			log.Info("Could not decode the seek position", "error", err)
   484  			return err
   485  		}
   486  	}
   487  	if ctx.NArg() >= 3 {
   488  		if max, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil {
   489  			log.Info("Could not decode the max count", "error", err)
   490  			return err
   491  		}
   492  	}
   493  	theTrie, err := trie.New(stRoot, trie.NewDatabase(db))
   494  	if err != nil {
   495  		return err
   496  	}
   497  	var count int64
   498  	it := trie.NewIterator(theTrie.NodeIterator(start))
   499  	for it.Next() {
   500  		if max > 0 && count == max {
   501  			fmt.Printf("Exiting after %d values\n", count)
   502  			break
   503  		}
   504  		fmt.Printf("  %d. key %#x: %#x\n", count, it.Key, it.Value)
   505  		count++
   506  	}
   507  	return it.Err
   508  }
   509  
   510  func freezerInspect(ctx *cli.Context) error {
   511  	var (
   512  		start, end    int64
   513  		disableSnappy bool
   514  		err           error
   515  	)
   516  	if ctx.NArg() < 3 {
   517  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   518  	}
   519  	kind := ctx.Args().Get(0)
   520  	if noSnap, ok := rawdb.FreezerNoSnappy[kind]; !ok {
   521  		var options []string
   522  		for opt := range rawdb.FreezerNoSnappy {
   523  			options = append(options, opt)
   524  		}
   525  		sort.Strings(options)
   526  		return fmt.Errorf("Could read freezer-type '%v'. Available options: %v", kind, options)
   527  	} else {
   528  		disableSnappy = noSnap
   529  	}
   530  	if start, err = strconv.ParseInt(ctx.Args().Get(1), 10, 64); err != nil {
   531  		log.Info("Could read start-param", "error", err)
   532  		return err
   533  	}
   534  	if end, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil {
   535  		log.Info("Could read count param", "error", err)
   536  		return err
   537  	}
   538  	stack, _ := makeConfigNode(ctx)
   539  	defer stack.Close()
   540  	path := filepath.Join(stack.ResolvePath("chaindata"), "ancient")
   541  	log.Info("Opening freezer", "location", path, "name", kind)
   542  	if f, err := rawdb.NewFreezerTable(path, kind, disableSnappy); err != nil {
   543  		return err
   544  	} else {
   545  		f.DumpIndex(start, end)
   546  	}
   547  	return nil
   548  }
   549  
   550  // ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes
   551  func parseHexOrString(str string) ([]byte, error) {
   552  	b, err := hexutil.Decode(str)
   553  	if errors.Is(err, hexutil.ErrMissingPrefix) {
   554  		return []byte(str), nil
   555  	}
   556  	return b, err
   557  }
   558  
   559  func importLDBdata(ctx *cli.Context) error {
   560  	start := 0
   561  	switch ctx.NArg() {
   562  	case 1:
   563  		break
   564  	case 2:
   565  		s, err := strconv.Atoi(ctx.Args().Get(1))
   566  		if err != nil {
   567  			return fmt.Errorf("second arg must be an integer: %v", err)
   568  		}
   569  		start = s
   570  	default:
   571  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   572  	}
   573  	var (
   574  		fName     = ctx.Args().Get(0)
   575  		stack, _  = makeConfigNode(ctx)
   576  		interrupt = make(chan os.Signal, 1)
   577  		stop      = make(chan struct{})
   578  	)
   579  	defer stack.Close()
   580  	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
   581  	defer signal.Stop(interrupt)
   582  	defer close(interrupt)
   583  	go func() {
   584  		if _, ok := <-interrupt; ok {
   585  			log.Info("Interrupted during ldb import, stopping at next batch")
   586  		}
   587  		close(stop)
   588  	}()
   589  	db := utils.MakeChainDatabase(ctx, stack, false)
   590  	return utils.ImportLDBData(db, fName, int64(start), stop)
   591  }
   592  
   593  type preimageIterator struct {
   594  	iter ethdb.Iterator
   595  }
   596  
   597  func (iter *preimageIterator) Next() (byte, []byte, []byte, bool) {
   598  	for iter.iter.Next() {
   599  		key := iter.iter.Key()
   600  		if bytes.HasPrefix(key, rawdb.PreimagePrefix) && len(key) == (len(rawdb.PreimagePrefix)+common.HashLength) {
   601  			return utils.OpBatchAdd, key, iter.iter.Value(), true
   602  		}
   603  	}
   604  	return 0, nil, nil, false
   605  }
   606  
   607  func (iter *preimageIterator) Release() {
   608  	iter.iter.Release()
   609  }
   610  
   611  type snapshotIterator struct {
   612  	init    bool
   613  	account ethdb.Iterator
   614  	storage ethdb.Iterator
   615  }
   616  
   617  func (iter *snapshotIterator) Next() (byte, []byte, []byte, bool) {
   618  	if !iter.init {
   619  		iter.init = true
   620  		return utils.OpBatchDel, rawdb.SnapshotRootKey, nil, true
   621  	}
   622  	for iter.account.Next() {
   623  		key := iter.account.Key()
   624  		if bytes.HasPrefix(key, rawdb.SnapshotAccountPrefix) && len(key) == (len(rawdb.SnapshotAccountPrefix)+common.HashLength) {
   625  			return utils.OpBatchAdd, key, iter.account.Value(), true
   626  		}
   627  	}
   628  	for iter.storage.Next() {
   629  		key := iter.storage.Key()
   630  		if bytes.HasPrefix(key, rawdb.SnapshotStoragePrefix) && len(key) == (len(rawdb.SnapshotStoragePrefix)+2*common.HashLength) {
   631  			return utils.OpBatchAdd, key, iter.storage.Value(), true
   632  		}
   633  	}
   634  	return 0, nil, nil, false
   635  }
   636  
   637  func (iter *snapshotIterator) Release() {
   638  	iter.account.Release()
   639  	iter.storage.Release()
   640  }
   641  
   642  // chainExporters defines the export scheme for all exportable chain data.
   643  var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{
   644  	"preimage": func(db ethdb.Database) utils.ChainDataIterator {
   645  		iter := db.NewIterator(rawdb.PreimagePrefix, nil)
   646  		return &preimageIterator{iter: iter}
   647  	},
   648  	"snapshot": func(db ethdb.Database) utils.ChainDataIterator {
   649  		account := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
   650  		storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
   651  		return &snapshotIterator{account: account, storage: storage}
   652  	},
   653  }
   654  
   655  func exportChaindata(ctx *cli.Context) error {
   656  	if ctx.NArg() < 2 {
   657  		return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage)
   658  	}
   659  	// Parse the required chain data type, make sure it's supported.
   660  	kind := ctx.Args().Get(0)
   661  	kind = strings.ToLower(strings.Trim(kind, " "))
   662  	exporter, ok := chainExporters[kind]
   663  	if !ok {
   664  		var kinds []string
   665  		for kind := range chainExporters {
   666  			kinds = append(kinds, kind)
   667  		}
   668  		return fmt.Errorf("invalid data type %s, supported types: %s", kind, strings.Join(kinds, ", "))
   669  	}
   670  	var (
   671  		stack, _  = makeConfigNode(ctx)
   672  		interrupt = make(chan os.Signal, 1)
   673  		stop      = make(chan struct{})
   674  	)
   675  	defer stack.Close()
   676  	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
   677  	defer signal.Stop(interrupt)
   678  	defer close(interrupt)
   679  	go func() {
   680  		if _, ok := <-interrupt; ok {
   681  			log.Info("Interrupted during db export, stopping at next batch")
   682  		}
   683  		close(stop)
   684  	}()
   685  	db := utils.MakeChainDatabase(ctx, stack, true)
   686  	return utils.ExportChaindata(ctx.Args().Get(1), kind, exporter(db), stop)
   687  }