github.com/bcskill/bcschain/v3@v3.4.9-beta2/cmd/gochain/chaincmd.go (about)

     1  // Copyright 2015 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  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"runtime"
    25  	"strconv"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	"github.com/bcskill/bcschain/v3/console/prompt"
    30  
    31  	"github.com/bcskill/bcschain/v3/cmd/utils"
    32  	"github.com/bcskill/bcschain/v3/common"
    33  	"github.com/bcskill/bcschain/v3/core"
    34  	"github.com/bcskill/bcschain/v3/core/state"
    35  	"github.com/bcskill/bcschain/v3/core/types"
    36  	"github.com/bcskill/bcschain/v3/eth/downloader"
    37  	"github.com/bcskill/bcschain/v3/ethdb"
    38  	"github.com/bcskill/bcschain/v3/log"
    39  	"github.com/urfave/cli"
    40  )
    41  
    42  var (
    43  	initCommand = cli.Command{
    44  		Action:    utils.MigrateFlags(initGenesis),
    45  		Name:      "init",
    46  		Usage:     "Bootstrap and initialize a new genesis block",
    47  		ArgsUsage: "<genesisPath>",
    48  		Flags: []cli.Flag{
    49  			utils.DataDirFlag,
    50  			utils.LightModeFlag,
    51  		},
    52  		Category: "BLOCKCHAIN COMMANDS",
    53  		Description: `
    54  The init command initializes a new genesis block and definition for the network.
    55  This is a destructive action and changes the network in which you will be
    56  participating.
    57  
    58  It expects the genesis file as argument.`,
    59  	}
    60  	importCommand = cli.Command{
    61  		Action:    utils.MigrateFlags(importChain),
    62  		Name:      "import",
    63  		Usage:     "Import a blockchain file",
    64  		ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
    65  		Flags: []cli.Flag{
    66  			utils.DataDirFlag,
    67  			utils.CacheFlag,
    68  			utils.LightModeFlag,
    69  			utils.GCModeFlag,
    70  			utils.CacheDatabaseFlag,
    71  			utils.CacheGCFlag,
    72  		},
    73  		Category: "BLOCKCHAIN COMMANDS",
    74  		Description: `
    75  The import command imports blocks from an RLP-encoded form. The form can be one file
    76  with several RLP-encoded blocks, or several files can be used.
    77  
    78  If only one file is used, import error will result in failure. If several files are used,
    79  processing will proceed even if an individual RLP-file import failure occurs.`,
    80  	}
    81  	exportCommand = cli.Command{
    82  		Action:    utils.MigrateFlags(exportChain),
    83  		Name:      "export",
    84  		Usage:     "Export blockchain into file",
    85  		ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
    86  		Flags: []cli.Flag{
    87  			utils.DataDirFlag,
    88  			utils.CacheFlag,
    89  			utils.LightModeFlag,
    90  		},
    91  		Category: "BLOCKCHAIN COMMANDS",
    92  		Description: `
    93  Requires a first argument of the file to write to.
    94  Optional second and third arguments control the first and
    95  last block to write. In this mode, the file will be appended
    96  if already existing.`,
    97  	}
    98  	copydbCommand = cli.Command{
    99  		Action:    utils.MigrateFlags(copyDb),
   100  		Name:      "copydb",
   101  		Usage:     "Create a local chain from a target chaindata folder",
   102  		ArgsUsage: "<sourceChaindataDir>",
   103  		Flags: []cli.Flag{
   104  			utils.DataDirFlag,
   105  			utils.CacheFlag,
   106  			utils.SyncModeFlag,
   107  			utils.FakePoWFlag,
   108  			utils.TestnetFlag,
   109  		},
   110  		Category: "BLOCKCHAIN COMMANDS",
   111  		Description: `
   112  The first argument must be the directory containing the blockchain to download from`,
   113  	}
   114  	removedbCommand = cli.Command{
   115  		Action:    utils.MigrateFlags(removeDB),
   116  		Name:      "removedb",
   117  		Usage:     "Remove blockchain and state databases",
   118  		ArgsUsage: " ",
   119  		Flags: []cli.Flag{
   120  			utils.DataDirFlag,
   121  			utils.LightModeFlag,
   122  		},
   123  		Category: "BLOCKCHAIN COMMANDS",
   124  		Description: `
   125  Remove blockchain and state databases`,
   126  	}
   127  	dumpCommand = cli.Command{
   128  		Action:    utils.MigrateFlags(dump),
   129  		Name:      "dump",
   130  		Usage:     "Dump a specific block from storage",
   131  		ArgsUsage: "[<blockHash> | <blockNum>]...",
   132  		Flags: []cli.Flag{
   133  			utils.DataDirFlag,
   134  			utils.CacheFlag,
   135  			utils.LightModeFlag,
   136  		},
   137  		Category: "BLOCKCHAIN COMMANDS",
   138  		Description: `
   139  The arguments are interpreted as block numbers or hashes.
   140  Use "ethereum dump 0" to dump the genesis block.`,
   141  	}
   142  )
   143  
   144  // initGenesis will initialise the given JSON format genesis file and writes it as
   145  // the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
   146  func initGenesis(ctx *cli.Context) error {
   147  	var genesis *core.Genesis
   148  	genesisPath := ctx.Args().First()
   149  	if len(genesisPath) > 0 {
   150  		file, err := os.Open(genesisPath)
   151  		if err != nil {
   152  			utils.Fatalf("Failed to read genesis file: %v", err)
   153  		}
   154  		defer file.Close()
   155  
   156  		genesis = new(core.Genesis)
   157  		if err := json.NewDecoder(file).Decode(genesis); err != nil {
   158  			utils.Fatalf("invalid genesis file: %v", err)
   159  		}
   160  	} else {
   161  		genesis = utils.MakeGenesis(ctx)
   162  		if genesis == nil {
   163  			utils.Fatalf("No default genesis available.")
   164  		}
   165  	}
   166  	// Open an initialise both full and light databases
   167  	stack := makeFullNode(ctx)
   168  	for _, name := range []string{"chaindata", "lightchaindata"} {
   169  		chaindb, err := stack.OpenDatabase(name, 0, 0)
   170  		if err != nil {
   171  			utils.Fatalf("Failed to open database: %v", err)
   172  		}
   173  		_, hash, err := core.SetupGenesisBlock(chaindb, genesis)
   174  		if err != nil {
   175  			utils.Fatalf("Failed to write genesis block: %v", err)
   176  		}
   177  		log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
   178  	}
   179  	return nil
   180  }
   181  
   182  func importChain(ctx *cli.Context) error {
   183  	if len(ctx.Args()) < 1 {
   184  		utils.Fatalf("This command requires an argument.")
   185  	}
   186  	stack := makeFullNode(ctx)
   187  	chain, chainDb := utils.MakeChain(ctx, stack)
   188  	defer chainDb.Close()
   189  
   190  	// Start periodically gathering memory profiles
   191  	var peakMemAlloc, peakMemSys uint64
   192  	go func() {
   193  		stats := new(runtime.MemStats)
   194  		for {
   195  			runtime.ReadMemStats(stats)
   196  			if atomic.LoadUint64(&peakMemAlloc) < stats.Alloc {
   197  				atomic.StoreUint64(&peakMemAlloc, stats.Alloc)
   198  			}
   199  			if atomic.LoadUint64(&peakMemSys) < stats.Sys {
   200  				atomic.StoreUint64(&peakMemSys, stats.Sys)
   201  			}
   202  			time.Sleep(5 * time.Second)
   203  		}
   204  	}()
   205  	// Import the chain
   206  	start := time.Now()
   207  
   208  	if len(ctx.Args()) == 1 {
   209  		if err := utils.ImportChain(context.TODO(), chain, ctx.Args().First()); err != nil {
   210  			log.Error("Import error", "err", err)
   211  		}
   212  	} else {
   213  		for _, arg := range ctx.Args() {
   214  			if err := utils.ImportChain(context.TODO(), chain, arg); err != nil {
   215  				log.Error("Import error", "file", arg, "err", err)
   216  			}
   217  		}
   218  	}
   219  	chain.Stop()
   220  	fmt.Printf("Import done in %v.\n\n", time.Since(start))
   221  
   222  	/*
   223  		// Output pre-compaction stats mostly to see the import trashing
   224  		db := chainDb.(*ethdb.LDBDatabase)
   225  
   226  		stats, err := db.LDB().GetProperty("leveldb.stats")
   227  		if err != nil {
   228  			utils.Fatalf("Failed to read database stats: %v", err)
   229  		}
   230  		fmt.Println(stats)
   231  		fmt.Printf("Trie cache misses:  %d\n", trie.CacheMisses())
   232  		fmt.Printf("Trie cache unloads: %d\n\n", trie.CacheUnloads())
   233  
   234  		// Print the memory statistics used by the importing
   235  		mem := new(runtime.MemStats)
   236  		runtime.ReadMemStats(mem)
   237  
   238  		fmt.Printf("Object memory: %.3f MB current, %.3f MB peak\n", float64(mem.Alloc)/1024/1024, float64(atomic.LoadUint64(&peakMemAlloc))/1024/1024)
   239  		fmt.Printf("System memory: %.3f MB current, %.3f MB peak\n", float64(mem.Sys)/1024/1024, float64(atomic.LoadUint64(&peakMemSys))/1024/1024)
   240  		fmt.Printf("Allocations:   %.3f million\n", float64(mem.Mallocs)/1000000)
   241  		fmt.Printf("GC pause:      %v\n\n", time.Duration(mem.PauseTotalNs))
   242  
   243  		if ctx.GlobalIsSet(utils.NoCompactionFlag.Name) {
   244  			return nil
   245  		}
   246  
   247  		// Compact the entire database to more accurately measure disk io and print the stats
   248  		start = time.Now()
   249  		fmt.Println("Compacting entire database...")
   250  		if err = db.LDB().CompactRange(util.Range{}); err != nil {
   251  			utils.Fatalf("Compaction failed: %v", err)
   252  		}
   253  		fmt.Printf("Compaction done in %v.\n\n", time.Since(start))
   254  
   255  		stats, err = db.LDB().GetProperty("leveldb.stats")
   256  		if err != nil {
   257  			utils.Fatalf("Failed to read database stats: %v", err)
   258  		}
   259  		fmt.Println(stats)
   260  	*/
   261  
   262  	return nil
   263  }
   264  
   265  func exportChain(ctx *cli.Context) error {
   266  	if len(ctx.Args()) < 1 {
   267  		utils.Fatalf("This command requires an argument.")
   268  	}
   269  	stack := makeFullNode(ctx)
   270  	chain, _ := utils.MakeChain(ctx, stack)
   271  	start := time.Now()
   272  
   273  	var err error
   274  	fp := ctx.Args().First()
   275  	if len(ctx.Args()) < 3 {
   276  		err = utils.ExportChain(chain, fp)
   277  	} else {
   278  		// This can be improved to allow for numbers larger than 9223372036854775807
   279  		first, ferr := strconv.ParseInt(ctx.Args().Get(1), 10, 64)
   280  		last, lerr := strconv.ParseInt(ctx.Args().Get(2), 10, 64)
   281  		if ferr != nil || lerr != nil {
   282  			utils.Fatalf("Export error in parsing parameters: block number not an integer\n")
   283  		}
   284  		if first < 0 || last < 0 {
   285  			utils.Fatalf("Export error: block number must be greater than 0\n")
   286  		}
   287  		err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last))
   288  	}
   289  
   290  	if err != nil {
   291  		utils.Fatalf("Export error: %v\n", err)
   292  	}
   293  	fmt.Printf("Export done in %v", time.Since(start))
   294  	return nil
   295  }
   296  
   297  func copyDb(ctx *cli.Context) error {
   298  	// Ensure we have a source chain directory to copy
   299  	if len(ctx.Args()) != 1 {
   300  		utils.Fatalf("Source chaindata directory path argument missing")
   301  	}
   302  	// Initialize a new chain for the running node to sync into
   303  	stack := makeFullNode(ctx)
   304  	chain, chainDb := utils.MakeChain(ctx, stack)
   305  
   306  	syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode)
   307  	dl := downloader.New(syncmode, chainDb, new(core.InterfaceFeed), chain, nil, nil)
   308  
   309  	// Create a source peer to satisfy downloader requests from
   310  	db := ethdb.NewDB(ctx.Args().First())
   311  	if err := db.Open(); err != nil {
   312  		return err
   313  	}
   314  	hc, err := core.NewHeaderChain(db, chain.Config(), chain.Engine(), func() bool { return false })
   315  	if err != nil {
   316  		return err
   317  	}
   318  	peer := downloader.NewFakePeer("local", db, hc, dl)
   319  	if err = dl.RegisterPeer("local", 63, peer); err != nil {
   320  		return err
   321  	}
   322  	// Synchronise with the simulated peer
   323  	start := time.Now()
   324  
   325  	currentHeader := hc.CurrentHeader()
   326  	if err = dl.Synchronise("local", currentHeader.Hash(), hc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()), syncmode); err != nil {
   327  		return err
   328  	}
   329  	for dl.Synchronising() {
   330  		time.Sleep(10 * time.Millisecond)
   331  	}
   332  	fmt.Printf("Database copy done in %v\n", time.Since(start))
   333  
   334  	// Compact the entire database to remove any sync overhead
   335  	/*
   336  		start = time.Now()
   337  		fmt.Println("Compacting entire database...")
   338  		if err = chainDb.(*ethdb.LDBDatabase).LDB().CompactRange(util.Range{}); err != nil {
   339  			utils.Fatalf("Compaction failed: %v", err)
   340  		}
   341  		fmt.Printf("Compaction done in %v.\n\n", time.Since(start))
   342  	*/
   343  
   344  	return nil
   345  }
   346  
   347  func removeDB(ctx *cli.Context) error {
   348  	stack, _ := makeConfigNode(ctx)
   349  
   350  	for _, name := range []string{"chaindata", "lightchaindata"} {
   351  		// Ensure the database exists in the first place
   352  		logger := log.New("database", name)
   353  
   354  		dbdir := stack.ResolvePath(name)
   355  		if !common.FileExist(dbdir) {
   356  			logger.Info("Database doesn't exist, skipping", "path", dbdir)
   357  			continue
   358  		}
   359  		// Confirm removal and execute
   360  		fmt.Println(dbdir)
   361  		confirm, err := prompt.Stdin.PromptConfirm("Remove this database?")
   362  		switch {
   363  		case err != nil:
   364  			utils.Fatalf("%v", err)
   365  		case !confirm:
   366  			logger.Warn("Database deletion aborted")
   367  		default:
   368  			start := time.Now()
   369  			os.RemoveAll(dbdir)
   370  			logger.Info("Database successfully deleted", "elapsed", common.PrettyDuration(time.Since(start)))
   371  		}
   372  	}
   373  	return nil
   374  }
   375  
   376  func dump(ctx *cli.Context) error {
   377  	stack := makeFullNode(ctx)
   378  	chain, chainDb := utils.MakeChain(ctx, stack)
   379  	for _, arg := range ctx.Args() {
   380  		var block *types.Block
   381  		if hashish(arg) {
   382  			block = chain.GetBlockByHash(common.HexToHash(arg))
   383  		} else {
   384  			num, _ := strconv.Atoi(arg)
   385  			block = chain.GetBlockByNumber(uint64(num))
   386  		}
   387  		if block == nil {
   388  			fmt.Println("{}")
   389  			utils.Fatalf("block not found")
   390  		} else {
   391  			state, err := state.New(block.Root(), state.NewDatabase(chainDb))
   392  			if err != nil {
   393  				utils.Fatalf("could not create new state: %v", err)
   394  			}
   395  			fmt.Printf("%s\n", state.Dump())
   396  		}
   397  	}
   398  	chainDb.Close()
   399  	return nil
   400  }
   401  
   402  // hashish returns true for strings that look like hashes.
   403  func hashish(x string) bool {
   404  	_, err := strconv.Atoi(x)
   405  	return err != nil
   406  }