github.com/klaytn/klaytn@v1.12.1/cmd/utils/nodecmd/chaincmd.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // go-ethereum is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // go-ethereum is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from cmd/geth/chaincmd.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package nodecmd
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"os"
    27  	"strings"
    28  
    29  	"github.com/klaytn/klaytn/blockchain"
    30  	"github.com/klaytn/klaytn/blockchain/types"
    31  	"github.com/klaytn/klaytn/cmd/utils"
    32  	"github.com/klaytn/klaytn/governance"
    33  	"github.com/klaytn/klaytn/log"
    34  	"github.com/klaytn/klaytn/params"
    35  	"github.com/klaytn/klaytn/rlp"
    36  	"github.com/klaytn/klaytn/storage/database"
    37  	"github.com/urfave/cli/v2"
    38  )
    39  
    40  var logger = log.NewModuleLogger(log.CMDUtilsNodeCMD)
    41  
    42  var (
    43  	InitCommand = &cli.Command{
    44  		Action:    initGenesis,
    45  		Name:      "init",
    46  		Usage:     "Bootstrap and initialize a new genesis block",
    47  		ArgsUsage: "<genesisPath>",
    48  		Flags: []cli.Flag{
    49  			utils.DbTypeFlag,
    50  			utils.SingleDBFlag,
    51  			utils.NumStateTrieShardsFlag,
    52  			utils.DynamoDBTableNameFlag,
    53  			utils.DynamoDBRegionFlag,
    54  			utils.DynamoDBIsProvisionedFlag,
    55  			utils.DynamoDBReadCapacityFlag,
    56  			utils.DynamoDBWriteCapacityFlag,
    57  			utils.DynamoDBReadOnlyFlag,
    58  			utils.LevelDBCompressionTypeFlag,
    59  			utils.DataDirFlag,
    60  			utils.ChainDataDirFlag,
    61  			utils.RocksDBSecondaryFlag,
    62  			utils.RocksDBCacheSizeFlag,
    63  			utils.RocksDBDumpMallocStatFlag,
    64  			utils.RocksDBFilterPolicyFlag,
    65  			utils.RocksDBCompressionTypeFlag,
    66  			utils.RocksDBBottommostCompressionTypeFlag,
    67  			utils.RocksDBDisableMetricsFlag,
    68  			utils.RocksDBMaxOpenFilesFlag,
    69  			utils.RocksDBCacheIndexAndFilterFlag,
    70  			utils.OverwriteGenesisFlag,
    71  			utils.LivePruningFlag,
    72  		},
    73  		Category: "BLOCKCHAIN COMMANDS",
    74  		Description: `
    75  The init command initializes a new genesis block and definition for the network.
    76  This is a destructive action and changes the network in which you will be
    77  participating.
    78  
    79  It expects the genesis file as argument.`,
    80  	}
    81  
    82  	DumpGenesisCommand = &cli.Command{
    83  		Action:    dumpGenesis,
    84  		Name:      "dumpgenesis",
    85  		Usage:     "Dumps genesis block JSON configuration to stdout",
    86  		ArgsUsage: "",
    87  		Flags: []cli.Flag{
    88  			utils.CypressFlag,
    89  			utils.BaobabFlag,
    90  		},
    91  		Category: "BLOCKCHAIN COMMANDS",
    92  		Description: `
    93  The dumpgenesis command dumps the genesis block configuration in JSON format to stdout.`,
    94  	}
    95  )
    96  
    97  // initGenesis will initialise the given JSON format genesis file and writes it as
    98  // the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
    99  func initGenesis(ctx *cli.Context) error {
   100  	// Make sure we have a valid genesis JSON
   101  	genesisPath := ctx.Args().First()
   102  	if len(genesisPath) == 0 {
   103  		logger.Crit("Must supply path to genesis JSON file")
   104  	}
   105  	file, err := os.Open(genesisPath)
   106  	if err != nil {
   107  		logger.Crit("Failed to read genesis file", "err", err)
   108  	}
   109  	defer file.Close()
   110  
   111  	genesis := new(blockchain.Genesis)
   112  	if err := json.NewDecoder(file).Decode(genesis); err != nil {
   113  		logger.Crit("Invalid genesis file", "err", err)
   114  		return err
   115  	}
   116  	if genesis.Config == nil {
   117  		logger.Crit("Genesis config is not set")
   118  	}
   119  
   120  	// Update undefined config with default values
   121  	genesis.Config.SetDefaultsForGenesis()
   122  
   123  	// Validate config values
   124  	if err := ValidateGenesisConfig(genesis); err != nil {
   125  		logger.Crit("Invalid genesis", "err", err)
   126  	}
   127  
   128  	// Set genesis.Governance and reward intervals
   129  	govSet := governance.GetGovernanceItemsFromChainConfig(genesis.Config)
   130  	govItemBytes, err := json.Marshal(govSet.Items())
   131  	if err != nil {
   132  		logger.Crit("Failed to json marshaling governance data", "err", err)
   133  	}
   134  	if genesis.Governance, err = rlp.EncodeToBytes(govItemBytes); err != nil {
   135  		logger.Crit("Failed to encode initial settings. Check your genesis.json", "err", err)
   136  	}
   137  	params.SetStakingUpdateInterval(genesis.Config.Governance.Reward.StakingUpdateInterval)
   138  	params.SetProposerUpdateInterval(genesis.Config.Governance.Reward.ProposerUpdateInterval)
   139  
   140  	// Open an initialise both full and light databases
   141  	stack := MakeFullNode(ctx)
   142  	parallelDBWrite := !ctx.Bool(utils.NoParallelDBWriteFlag.Name)
   143  	singleDB := ctx.Bool(utils.SingleDBFlag.Name)
   144  	numStateTrieShards := ctx.Uint(utils.NumStateTrieShardsFlag.Name)
   145  	overwriteGenesis := ctx.Bool(utils.OverwriteGenesisFlag.Name)
   146  	livePruning := ctx.Bool(utils.LivePruningFlag.Name)
   147  
   148  	dbtype := database.DBType(ctx.String(utils.DbTypeFlag.Name)).ToValid()
   149  	if len(dbtype) == 0 {
   150  		logger.Crit("invalid dbtype", "dbtype", ctx.String(utils.DbTypeFlag.Name))
   151  	}
   152  
   153  	var dynamoDBConfig *database.DynamoDBConfig
   154  	if dbtype == database.DynamoDB {
   155  		dynamoDBConfig = &database.DynamoDBConfig{
   156  			TableName:          ctx.String(utils.DynamoDBTableNameFlag.Name),
   157  			Region:             ctx.String(utils.DynamoDBRegionFlag.Name),
   158  			IsProvisioned:      ctx.Bool(utils.DynamoDBIsProvisionedFlag.Name),
   159  			ReadCapacityUnits:  ctx.Int64(utils.DynamoDBReadCapacityFlag.Name),
   160  			WriteCapacityUnits: ctx.Int64(utils.DynamoDBWriteCapacityFlag.Name),
   161  			ReadOnly:           ctx.Bool(utils.DynamoDBReadOnlyFlag.Name),
   162  		}
   163  	}
   164  	rocksDBConfig := database.GetDefaultRocksDBConfig()
   165  	if dbtype == database.RocksDB {
   166  		rocksDBConfig = &database.RocksDBConfig{
   167  			Secondary:                 ctx.Bool(utils.RocksDBSecondaryFlag.Name),
   168  			DumpMallocStat:            ctx.Bool(utils.RocksDBDumpMallocStatFlag.Name),
   169  			DisableMetrics:            ctx.Bool(utils.RocksDBDisableMetricsFlag.Name),
   170  			CacheSize:                 ctx.Uint64(utils.RocksDBCacheSizeFlag.Name),
   171  			CompressionType:           ctx.String(utils.RocksDBCompressionTypeFlag.Name),
   172  			BottommostCompressionType: ctx.String(utils.RocksDBBottommostCompressionTypeFlag.Name),
   173  			FilterPolicy:              ctx.String(utils.RocksDBFilterPolicyFlag.Name),
   174  			MaxOpenFiles:              ctx.Int(utils.RocksDBMaxOpenFilesFlag.Name),
   175  			CacheIndexAndFilter:       ctx.Bool(utils.RocksDBCacheIndexAndFilterFlag.Name),
   176  		}
   177  	}
   178  
   179  	for _, name := range []string{"chaindata"} { // Removed "lightchaindata" since Klaytn doesn't use it
   180  		dbc := &database.DBConfig{
   181  			Dir: name, DBType: dbtype, ParallelDBWrite: parallelDBWrite,
   182  			SingleDB: singleDB, NumStateTrieShards: numStateTrieShards,
   183  			LevelDBCacheSize: 0, OpenFilesLimit: 0, DynamoDBConfig: dynamoDBConfig, RocksDBConfig: rocksDBConfig,
   184  		}
   185  		chainDB := stack.OpenDatabase(dbc)
   186  
   187  		// Initialize DeriveSha implementation
   188  		blockchain.InitDeriveSha(genesis.Config)
   189  
   190  		_, hash, err := blockchain.SetupGenesisBlock(chainDB, genesis, params.UnusedNetworkId, false, overwriteGenesis)
   191  		if err != nil {
   192  			logger.Crit("Failed to write genesis block", "err", err)
   193  		}
   194  
   195  		// Write governance items to database
   196  		// If governance data already exist, it'll be skipped with an error log and will not return an error
   197  		gov := governance.NewMixedEngineNoInit(genesis.Config, chainDB)
   198  		if err := gov.WriteGovernance(0, govSet, governance.NewGovernanceSet()); err != nil {
   199  			logger.Crit("Failed to write governance items", "err", err)
   200  		}
   201  
   202  		// Write the live pruning flag to database
   203  		if livePruning {
   204  			logger.Info("Writing live pruning flag to database")
   205  			chainDB.WritePruningEnabled()
   206  		}
   207  
   208  		logger.Info("Successfully wrote genesis state", "database", name, "hash", hash.String())
   209  		chainDB.Close()
   210  	}
   211  	return nil
   212  }
   213  
   214  func dumpGenesis(ctx *cli.Context) error {
   215  	genesis := MakeGenesis(ctx)
   216  	if genesis == nil {
   217  		genesis = blockchain.DefaultGenesisBlock()
   218  	}
   219  	if err := json.NewEncoder(os.Stdout).Encode(genesis); err != nil {
   220  		logger.Crit("could not encode genesis")
   221  	}
   222  	return nil
   223  }
   224  
   225  func MakeGenesis(ctx *cli.Context) *blockchain.Genesis {
   226  	var genesis *blockchain.Genesis
   227  	switch {
   228  	case ctx.Bool(utils.CypressFlag.Name):
   229  		genesis = blockchain.DefaultGenesisBlock()
   230  	case ctx.Bool(utils.BaobabFlag.Name):
   231  		genesis = blockchain.DefaultBaobabGenesisBlock()
   232  	}
   233  	return genesis
   234  }
   235  
   236  func ValidateGenesisConfig(g *blockchain.Genesis) error {
   237  	if g.Config.ChainID == nil {
   238  		return errors.New("chainID is not specified")
   239  	}
   240  
   241  	if g.Config.Clique == nil && g.Config.Istanbul == nil {
   242  		return errors.New("consensus engine should be configured")
   243  	}
   244  
   245  	if g.Config.Clique != nil && g.Config.Istanbul != nil {
   246  		return errors.New("only one consensus engine can be configured")
   247  	}
   248  
   249  	if g.Config.Governance == nil || g.Config.Governance.Reward == nil {
   250  		return errors.New("governance and reward policies should be configured")
   251  	}
   252  
   253  	if g.Config.Governance.Reward.ProposerUpdateInterval == 0 || g.Config.Governance.Reward.
   254  		StakingUpdateInterval == 0 {
   255  		return errors.New("proposerUpdateInterval and stakingUpdateInterval cannot be zero")
   256  	}
   257  
   258  	if g.Config.Istanbul != nil {
   259  		if err := governance.CheckGenesisValues(g.Config); err != nil {
   260  			return err
   261  		}
   262  
   263  		// TODO-Klaytn: Add validation logic for other GovernanceModes
   264  		// Check if governingNode is properly set
   265  		if strings.ToLower(g.Config.Governance.GovernanceMode) == "single" {
   266  			var found bool
   267  
   268  			istanbulExtra, err := types.ExtractIstanbulExtra(&types.Header{Extra: g.ExtraData})
   269  			if err != nil {
   270  				return err
   271  			}
   272  
   273  			for _, v := range istanbulExtra.Validators {
   274  				if v == g.Config.Governance.GoverningNode {
   275  					found = true
   276  					break
   277  				}
   278  			}
   279  			if !found {
   280  				return errors.New("governingNode is not in the validator list")
   281  			}
   282  		}
   283  	}
   284  	return nil
   285  }