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 }