github.com/Finschia/finschia-sdk@v0.48.1/server/util.go (about)

     1  package server
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"os"
     9  	"os/signal"
    10  	"path"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"syscall"
    15  	"time"
    16  
    17  	ostcmd "github.com/Finschia/ostracon/cmd/ostracon/commands"
    18  	ostcfg "github.com/Finschia/ostracon/config"
    19  	ostlog "github.com/Finschia/ostracon/libs/log"
    20  	"github.com/spf13/cobra"
    21  	"github.com/spf13/pflag"
    22  	"github.com/spf13/viper"
    23  	dbm "github.com/tendermint/tm-db"
    24  
    25  	"github.com/Finschia/finschia-sdk/client/flags"
    26  	"github.com/Finschia/finschia-sdk/server/config"
    27  	"github.com/Finschia/finschia-sdk/server/types"
    28  	sdk "github.com/Finschia/finschia-sdk/types"
    29  	"github.com/Finschia/finschia-sdk/version"
    30  )
    31  
    32  // DONTCOVER
    33  
    34  // ServerContextKey defines the context key used to retrieve a server.Context from
    35  // a command's Context.
    36  const ServerContextKey = sdk.ContextKey("server.context")
    37  
    38  // server context
    39  type Context struct {
    40  	Viper  *viper.Viper
    41  	Config *ostcfg.Config
    42  	Logger ostlog.Logger
    43  }
    44  
    45  // ErrorCode contains the exit code for server exit.
    46  type ErrorCode struct {
    47  	Code int
    48  }
    49  
    50  func (e ErrorCode) Error() string {
    51  	return strconv.Itoa(e.Code)
    52  }
    53  
    54  func NewDefaultContext() *Context {
    55  	return NewContext(
    56  		viper.New(),
    57  		ostcfg.DefaultConfig(),
    58  		ostlog.ZeroLogWrapper{},
    59  	)
    60  }
    61  
    62  func NewContext(v *viper.Viper, config *ostcfg.Config, logger ostlog.Logger) *Context {
    63  	return &Context{v, config, logger}
    64  }
    65  
    66  func bindFlags(basename string, cmd *cobra.Command, v *viper.Viper) (err error) {
    67  	defer func() {
    68  		recover()
    69  	}()
    70  
    71  	cmd.Flags().VisitAll(func(f *pflag.Flag) {
    72  		// Environment variables can't have dashes in them, so bind them to their equivalent
    73  		// keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
    74  		err = v.BindEnv(f.Name, fmt.Sprintf("%s_%s", basename, strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))))
    75  		if err != nil {
    76  			panic(err)
    77  		}
    78  
    79  		err = v.BindPFlag(f.Name, f)
    80  		if err != nil {
    81  			panic(err)
    82  		}
    83  
    84  		// Apply the viper config value to the flag when the flag is not set and viper has a value
    85  		if !f.Changed && v.IsSet(f.Name) {
    86  			val := v.Get(f.Name)
    87  			err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
    88  			if err != nil {
    89  				panic(err)
    90  			}
    91  		}
    92  	})
    93  
    94  	return
    95  }
    96  
    97  // InterceptConfigsPreRunHandler performs a pre-run function for the root daemon
    98  // application command. It will create a Viper literal and a default server
    99  // Context. The server Tendermint configuration will either be read and parsed
   100  // or created and saved to disk, where the server Context is updated to reflect
   101  // the Tendermint configuration. It takes custom app config template and config
   102  // settings to create a custom Tendermint configuration. If the custom template
   103  // is empty, it uses default-template provided by the server. The Viper literal
   104  // is used to read and parse the application configuration. Command handlers can
   105  // fetch the server Context to get the Tendermint configuration or to get access
   106  // to Viper.
   107  func InterceptConfigsPreRunHandler(cmd *cobra.Command, customAppConfigTemplate string, customAppConfig interface{}) error {
   108  	serverCtx := NewDefaultContext()
   109  
   110  	// Get the executable name and configure the viper instance so that environmental
   111  	// variables are checked based off that name. The underscore character is used
   112  	// as a separator
   113  	executableName, err := os.Executable()
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	basename := path.Base(executableName)
   119  
   120  	// Configure the viper instance
   121  	if err = serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
   122  		return err
   123  	}
   124  	if err = serverCtx.Viper.BindPFlags(cmd.PersistentFlags()); err != nil {
   125  		return err
   126  	}
   127  	serverCtx.Viper.SetEnvPrefix(basename)
   128  	serverCtx.Viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
   129  	serverCtx.Viper.AutomaticEnv()
   130  
   131  	// intercept configuration files, using both Viper instances separately
   132  	config, err := interceptConfigs(serverCtx.Viper, customAppConfigTemplate, customAppConfig)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	// return value is a ostracon configuration object
   138  	serverCtx.Config = config
   139  	if err = bindFlags(basename, cmd, serverCtx.Viper); err != nil {
   140  		return err
   141  	}
   142  
   143  	isLogPlain := false
   144  	if strings.ToLower(serverCtx.Viper.GetString(flags.FlagLogFormat)) == ostcfg.LogFormatPlain {
   145  		isLogPlain = true
   146  	}
   147  
   148  	logLevel := serverCtx.Viper.GetString(flags.FlagLogLevel)
   149  	if logLevel == "" {
   150  		logLevel = ostcfg.DefaultPackageLogLevels()
   151  	}
   152  
   153  	zerologCfg := ostlog.NewZeroLogConfig(
   154  		isLogPlain,
   155  		logLevel,
   156  		serverCtx.Viper.GetString(flags.FlagLogPath),
   157  		serverCtx.Viper.GetInt(flags.FlagLogMaxAge),
   158  		serverCtx.Viper.GetInt(flags.FlagLogMaxSize),
   159  		serverCtx.Viper.GetInt(flags.FlagLogMaxBackups),
   160  	)
   161  
   162  	serverCtx.Logger, err = ostlog.NewZeroLogLogger(zerologCfg, os.Stderr)
   163  	if err != nil {
   164  		return fmt.Errorf("failed to initialize logger: %w", err)
   165  	}
   166  
   167  	return SetCmdServerContext(cmd, serverCtx)
   168  }
   169  
   170  // GetServerContextFromCmd returns a Context from a command or an empty Context
   171  // if it has not been set.
   172  func GetServerContextFromCmd(cmd *cobra.Command) *Context {
   173  	if v := cmd.Context().Value(ServerContextKey); v != nil {
   174  		serverCtxPtr := v.(*Context)
   175  		return serverCtxPtr
   176  	}
   177  
   178  	return NewDefaultContext()
   179  }
   180  
   181  // SetCmdServerContext sets a command's Context value to the provided argument.
   182  func SetCmdServerContext(cmd *cobra.Command, serverCtx *Context) error {
   183  	v := cmd.Context().Value(ServerContextKey)
   184  	if v == nil {
   185  		return errors.New("server context not set")
   186  	}
   187  
   188  	serverCtxPtr := v.(*Context)
   189  	*serverCtxPtr = *serverCtx
   190  
   191  	return nil
   192  }
   193  
   194  // interceptConfigs parses and updates a Tendermint configuration file or
   195  // creates a new one and saves it. It also parses and saves the application
   196  // configuration file. The Tendermint configuration file is parsed given a root
   197  // Viper object, whereas the application is parsed with the private package-aware
   198  // viperCfg object.
   199  func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customConfig interface{}) (*ostcfg.Config, error) {
   200  	rootDir := rootViper.GetString(flags.FlagHome)
   201  	configPath := filepath.Join(rootDir, "config")
   202  	tmCfgFile := filepath.Join(configPath, "config.toml")
   203  
   204  	conf := ostcfg.DefaultConfig()
   205  
   206  	switch _, err := os.Stat(tmCfgFile); {
   207  	case os.IsNotExist(err):
   208  		ostcfg.EnsureRoot(rootDir)
   209  
   210  		if err = conf.ValidateBasic(); err != nil {
   211  			return nil, fmt.Errorf("error in config file: %v", err)
   212  		}
   213  
   214  		conf.RPC.PprofListenAddress = "localhost:6060"
   215  		conf.P2P.RecvRate = 5120000
   216  		conf.P2P.SendRate = 5120000
   217  		conf.P2P.PexRecvBufSize = 10000
   218  		conf.P2P.MempoolRecvBufSize = 100000
   219  		conf.P2P.EvidenceRecvBufSize = 10000
   220  		conf.P2P.ConsensusRecvBufSize = 10000
   221  		conf.P2P.BlockchainRecvBufSize = 10000
   222  		conf.Consensus.TimeoutCommit = 5 * time.Second
   223  		ostcfg.WriteConfigFile(tmCfgFile, conf)
   224  
   225  	case err != nil:
   226  		return nil, err
   227  
   228  	default:
   229  		rootViper.SetConfigType("toml")
   230  		rootViper.SetConfigName("config")
   231  		rootViper.AddConfigPath(configPath)
   232  
   233  		if err := rootViper.ReadInConfig(); err != nil {
   234  			return nil, fmt.Errorf("failed to read in %s: %w", tmCfgFile, err)
   235  		}
   236  	}
   237  
   238  	// Read into the configuration whatever data the viper instance has for it.
   239  	// This may come from the configuration file above but also any of the other
   240  	// sources viper uses.
   241  	if err := rootViper.Unmarshal(conf); err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	conf.SetRoot(rootDir)
   246  
   247  	appCfgFilePath := filepath.Join(configPath, "app.toml")
   248  	if _, err := os.Stat(appCfgFilePath); os.IsNotExist(err) {
   249  		if customAppTemplate != "" {
   250  			config.SetConfigTemplate(customAppTemplate)
   251  
   252  			if err = rootViper.Unmarshal(&customConfig); err != nil {
   253  				return nil, fmt.Errorf("failed to parse %s: %w", appCfgFilePath, err)
   254  			}
   255  
   256  			config.WriteConfigFile(appCfgFilePath, customConfig)
   257  		} else {
   258  			appConf, err := config.ParseConfig(rootViper)
   259  			if err != nil {
   260  				return nil, fmt.Errorf("failed to parse %s: %w", appCfgFilePath, err)
   261  			}
   262  
   263  			config.WriteConfigFile(appCfgFilePath, appConf)
   264  		}
   265  	}
   266  
   267  	rootViper.SetConfigType("toml")
   268  	rootViper.SetConfigName("app")
   269  	rootViper.AddConfigPath(configPath)
   270  
   271  	if err := rootViper.MergeInConfig(); err != nil {
   272  		return nil, fmt.Errorf("failed to merge configuration: %w", err)
   273  	}
   274  
   275  	return conf, nil
   276  }
   277  
   278  // add server commands
   279  func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator, appExport types.AppExporter, addStartFlags types.ModuleInitFlags) {
   280  	ostraconCmd := &cobra.Command{
   281  		Use:   "ostracon",
   282  		Short: "Ostracon subcommands",
   283  	}
   284  
   285  	ostraconCmd.AddCommand(
   286  		ShowNodeIDCmd(),
   287  		ShowValidatorCmd(),
   288  		ShowAddressCmd(),
   289  		VersionCmd(),
   290  		ostcmd.ResetAllCmd,
   291  		ostcmd.ResetStateCmd,
   292  	)
   293  
   294  	startCmd := StartCmd(appCreator, defaultNodeHome)
   295  	addStartFlags(startCmd)
   296  
   297  	rootCmd.AddCommand(
   298  		startCmd,
   299  		ostraconCmd,
   300  		ExportCmd(appExport, defaultNodeHome),
   301  		version.NewVersionCommand(),
   302  		NewRollbackCmd(appCreator, defaultNodeHome),
   303  	)
   304  }
   305  
   306  // https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
   307  // TODO there must be a better way to get external IP
   308  func ExternalIP() (string, error) {
   309  	ifaces, err := net.Interfaces()
   310  	if err != nil {
   311  		return "", err
   312  	}
   313  
   314  	for _, iface := range ifaces {
   315  		if skipInterface(iface) {
   316  			continue
   317  		}
   318  		addrs, err := iface.Addrs()
   319  		if err != nil {
   320  			return "", err
   321  		}
   322  
   323  		for _, addr := range addrs {
   324  			ip := addrToIP(addr)
   325  			if ip == nil || ip.IsLoopback() {
   326  				continue
   327  			}
   328  			ip = ip.To4()
   329  			if ip == nil {
   330  				continue // not an ipv4 address
   331  			}
   332  			return ip.String(), nil
   333  		}
   334  	}
   335  	return "", errors.New("are you connected to the network?")
   336  }
   337  
   338  // TrapSignal traps SIGINT and SIGTERM and terminates the server correctly.
   339  func TrapSignal(cleanupFunc func()) {
   340  	sigs := make(chan os.Signal, 1)
   341  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   342  
   343  	go func() {
   344  		sig := <-sigs
   345  
   346  		if cleanupFunc != nil {
   347  			cleanupFunc()
   348  		}
   349  		exitCode := 128
   350  
   351  		switch sig {
   352  		case syscall.SIGINT:
   353  			exitCode += int(syscall.SIGINT)
   354  		case syscall.SIGTERM:
   355  			exitCode += int(syscall.SIGTERM)
   356  		}
   357  
   358  		os.Exit(exitCode)
   359  	}()
   360  }
   361  
   362  // WaitForQuitSignals waits for SIGINT and SIGTERM and returns.
   363  func WaitForQuitSignals() ErrorCode {
   364  	sigs := make(chan os.Signal, 1)
   365  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   366  	sig := <-sigs
   367  	return ErrorCode{Code: int(sig.(syscall.Signal)) + 128}
   368  }
   369  
   370  func skipInterface(iface net.Interface) bool {
   371  	if iface.Flags&net.FlagUp == 0 {
   372  		return true // interface down
   373  	}
   374  
   375  	if iface.Flags&net.FlagLoopback != 0 {
   376  		return true // loopback interface
   377  	}
   378  
   379  	return false
   380  }
   381  
   382  func addrToIP(addr net.Addr) net.IP {
   383  	var ip net.IP
   384  
   385  	switch v := addr.(type) {
   386  	case *net.IPNet:
   387  		ip = v.IP
   388  	case *net.IPAddr:
   389  		ip = v.IP
   390  	}
   391  	return ip
   392  }
   393  
   394  func openDB(rootDir string) (dbm.DB, error) {
   395  	dataDir := filepath.Join(rootDir, "data")
   396  	return sdk.NewLevelDB("application", dataDir)
   397  }
   398  
   399  func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
   400  	if traceWriterFile == "" {
   401  		return
   402  	}
   403  	return os.OpenFile(
   404  		traceWriterFile,
   405  		os.O_WRONLY|os.O_APPEND|os.O_CREATE,
   406  		0o666,
   407  	)
   408  }