code.vegaprotocol.io/vega@v0.79.0/cmd/vega/commands/init.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package commands
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/config"
    27  	"code.vegaprotocol.io/vega/core/config/encoding"
    28  	"code.vegaprotocol.io/vega/core/nodewallets/registry"
    29  	vgjson "code.vegaprotocol.io/vega/libs/json"
    30  	"code.vegaprotocol.io/vega/logging"
    31  	"code.vegaprotocol.io/vega/paths"
    32  
    33  	tmcfg "github.com/cometbft/cometbft/config"
    34  	tmos "github.com/cometbft/cometbft/libs/os"
    35  	tmrand "github.com/cometbft/cometbft/libs/rand"
    36  	"github.com/cometbft/cometbft/p2p"
    37  	"github.com/cometbft/cometbft/privval"
    38  	"github.com/cometbft/cometbft/types"
    39  	"github.com/jessevdk/go-flags"
    40  )
    41  
    42  type InitCmd struct {
    43  	config.VegaHomeFlag
    44  	config.OutputFlag
    45  	config.Passphrase `long:"nodewallet-passphrase-file"`
    46  
    47  	Force bool `description:"Erase existing vega configuration at the specified path" long:"force" short:"f"`
    48  
    49  	NoTendermint   bool   `description:"Disable tendermint configuration generation" long:"no-tendermint"`
    50  	TendermintHome string `default:"$HOME/.cometbft"                                 description:"Directory for tendermint config and data" long:"tendermint-home" required:"true"`
    51  	TendermintKey  string `choice:"ed25519"                                          choice:"secp256k1"                                     default:"ed25519"      description:"Key type to generate privval file with" long:"tendermint-key"`
    52  }
    53  
    54  var initCmd InitCmd
    55  
    56  func (opts *InitCmd) Usage() string {
    57  	return "<full | validator>"
    58  }
    59  
    60  func (opts *InitCmd) Execute(args []string) error {
    61  	logger := logging.NewLoggerFromConfig(logging.NewDefaultConfig())
    62  	defer logger.AtExit()
    63  
    64  	if len(args) != 1 {
    65  		return errors.New("require exactly 1 parameter mode, expected modes [validator, full, seed]")
    66  	}
    67  
    68  	mode, err := encoding.NodeModeFromString(args[0])
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	output, err := opts.GetOutput()
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	vegaPaths := paths.New(opts.VegaHome)
    79  
    80  	// a nodewallet will be required only for a validator node
    81  	var nwRegistry *registry.Loader
    82  	if mode == encoding.NodeModeValidator {
    83  		pass, err := opts.Get("node wallet", true)
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		nwRegistry, err = registry.NewLoader(vegaPaths, pass)
    89  		if err != nil {
    90  			return err
    91  		}
    92  	}
    93  
    94  	cfgLoader, err := config.InitialiseLoader(vegaPaths)
    95  	if err != nil {
    96  		return fmt.Errorf("couldn't initialise configuration loader: %w", err)
    97  	}
    98  
    99  	configExists, err := cfgLoader.ConfigExists()
   100  	if err != nil {
   101  		return fmt.Errorf("couldn't verify configuration presence: %w", err)
   102  	}
   103  
   104  	if configExists && !opts.Force {
   105  		return fmt.Errorf("configuration already exists at `%s` please remove it first or re-run using -f", cfgLoader.ConfigFilePath())
   106  	}
   107  
   108  	if configExists && opts.Force {
   109  		if output.IsHuman() {
   110  			logger.Info("removing existing configuration", logging.String("path", cfgLoader.ConfigFilePath()))
   111  		}
   112  		cfgLoader.Remove()
   113  	}
   114  
   115  	cfg := config.NewDefaultConfig()
   116  	cfg.NodeMode = mode
   117  	cfg.SetDefaultMaxMemoryPercent()
   118  
   119  	if err := cfgLoader.Save(&cfg); err != nil {
   120  		return fmt.Errorf("couldn't save configuration file: %w", err)
   121  	}
   122  
   123  	if output.IsHuman() {
   124  		logger.Info("configuration generated successfully",
   125  			logging.String("path", cfgLoader.ConfigFilePath()))
   126  	}
   127  
   128  	if !initCmd.NoTendermint {
   129  		tmCfg := tmcfg.DefaultConfig()
   130  		tmCfg.SetRoot(os.ExpandEnv(initCmd.TendermintHome))
   131  		// add a few defaults
   132  		tmCfg.P2P.MaxPacketMsgPayloadSize = 16384
   133  		tmCfg.P2P.SendRate = 20000000
   134  		tmCfg.P2P.RecvRate = 20000000
   135  		tmCfg.Mempool.Size = 10000
   136  		tmCfg.Mempool.CacheSize = 20000
   137  		tmCfg.Consensus.TimeoutCommit = 0 * time.Second
   138  		tmCfg.Consensus.SkipTimeoutCommit = true
   139  		tmCfg.Consensus.CreateEmptyBlocksInterval = 1 * time.Second
   140  		tmCfg.Storage.DiscardABCIResponses = true
   141  		tmcfg.EnsureRoot(tmCfg.RootDir)
   142  		// then rewrite the config to apply the changes, EnsureRoot create the config, but with a default config
   143  		tmcfg.WriteConfigFile(filepath.Join(tmCfg.RootDir, tmcfg.DefaultConfigDir, tmcfg.DefaultConfigFileName), tmCfg)
   144  		if err := initTendermintConfiguration(output, logger, tmCfg); err != nil {
   145  			return fmt.Errorf("couldn't initialise tendermint %w", err)
   146  		}
   147  	}
   148  
   149  	if output.IsJSON() {
   150  		if mode == encoding.NodeModeValidator {
   151  			return vgjson.Print(struct {
   152  				ConfigFilePath           string `json:"configFilePath"`
   153  				NodeWalletConfigFilePath string `json:"nodeWalletConfigFilePath"`
   154  			}{
   155  				ConfigFilePath:           cfgLoader.ConfigFilePath(),
   156  				NodeWalletConfigFilePath: nwRegistry.RegistryFilePath(),
   157  			})
   158  		}
   159  		return vgjson.Print(struct {
   160  			ConfigFilePath string `json:"configFilePath"`
   161  		}{
   162  			ConfigFilePath: cfgLoader.ConfigFilePath(),
   163  		})
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func initTendermintConfiguration(output config.Output, logger *logging.Logger, config *tmcfg.Config) error {
   170  	// private validator
   171  	privValKeyFile := config.PrivValidatorKeyFile()
   172  	privValStateFile := config.PrivValidatorStateFile()
   173  	var pv *privval.FilePV
   174  	if tmos.FileExists(privValKeyFile) {
   175  		pv = privval.LoadFilePV(privValKeyFile, privValStateFile)
   176  		if output.IsHuman() {
   177  			logger.Info("Found private validator",
   178  				logging.String("keyFile", privValKeyFile),
   179  				logging.String("stateFile", privValStateFile),
   180  			)
   181  		}
   182  	} else {
   183  		pv = privval.GenFilePV(privValKeyFile, privValStateFile)
   184  		pv.Save()
   185  		if output.IsHuman() {
   186  			logger.Info("Generated private validator",
   187  				logging.String("keyFile", privValKeyFile),
   188  				logging.String("stateFile", privValStateFile),
   189  			)
   190  		}
   191  	}
   192  
   193  	nodeKeyFile := config.NodeKeyFile()
   194  	if tmos.FileExists(nodeKeyFile) {
   195  		if output.IsHuman() {
   196  			logger.Info("Found node key", logging.String("path", nodeKeyFile))
   197  		}
   198  	} else {
   199  		if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil {
   200  			return err
   201  		}
   202  		if output.IsHuman() {
   203  			logger.Info("Generated node key", logging.String("path", nodeKeyFile))
   204  		}
   205  	}
   206  
   207  	// genesis file
   208  	genFile := config.GenesisFile()
   209  	if tmos.FileExists(genFile) {
   210  		if output.IsHuman() {
   211  			logger.Info("Found genesis file", logging.String("path", genFile))
   212  		}
   213  	} else {
   214  		genDoc := types.GenesisDoc{
   215  			ChainID:         fmt.Sprintf("test-chain-%v", tmrand.Str(6)),
   216  			GenesisTime:     time.Now().Round(0).UTC(),
   217  			ConsensusParams: types.DefaultConsensusParams(),
   218  		}
   219  		pubKey, err := pv.GetPubKey()
   220  		if err != nil {
   221  			return fmt.Errorf("can't get pubkey: %w", err)
   222  		}
   223  		genDoc.Validators = []types.GenesisValidator{{
   224  			Address: pubKey.Address(),
   225  			PubKey:  pubKey,
   226  			Power:   10,
   227  		}}
   228  
   229  		if err := genDoc.SaveAs(genFile); err != nil {
   230  			return err
   231  		}
   232  		if output.IsHuman() {
   233  			logger.Info("Generated genesis file", logging.String("path", genFile))
   234  		}
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  func Init(ctx context.Context, parser *flags.Parser) error {
   241  	initCmd = InitCmd{}
   242  
   243  	var (
   244  		short = "Initializes a vega node"
   245  		long  = "Generate the minimal configuration required for a vega node to start. You must specify 'full' or 'validator'"
   246  	)
   247  	_, err := parser.AddCommand("init", short, long, &initCmd)
   248  	return err
   249  }