cosmos-sdk/server/util.go

389 lines
10 KiB
Go
Raw Normal View History

package server
import (
"errors"
"fmt"
2020-07-29 07:53:14 -07:00
"io"
2018-04-23 12:15:50 -07:00
"net"
"os"
2018-11-02 06:44:40 -07:00
"os/signal"
"path"
"path/filepath"
"strconv"
"strings"
2018-11-02 06:44:40 -07:00
"syscall"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
tmcfg "github.com/tendermint/tendermint/config"
tmlog "github.com/tendermint/tendermint/libs/log"
2020-07-29 07:53:14 -07:00
dbm "github.com/tendermint/tm-db"
2018-12-10 06:27:25 -08:00
"github.com/cosmos/cosmos-sdk/client/flags"
2018-12-10 06:27:25 -08:00
"github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
2018-12-10 06:27:25 -08:00
"github.com/cosmos/cosmos-sdk/version"
)
// DONTCOVER
// ServerContextKey defines the context key used to retrieve a server.Context from
// a command's Context.
const ServerContextKey = sdk.ContextKey("server.context")
2018-04-18 21:49:24 -07:00
// server context
2018-04-05 03:31:33 -07:00
type Context struct {
Viper *viper.Viper
Config *tmcfg.Config
Logger tmlog.Logger
2018-04-05 03:31:33 -07:00
}
// ErrorCode contains the exit code for server exit.
type ErrorCode struct {
Code int
}
func (e ErrorCode) Error() string {
return strconv.Itoa(e.Code)
}
2018-04-05 04:16:20 -07:00
func NewDefaultContext() *Context {
return NewContext(
viper.New(),
tmcfg.DefaultConfig(),
ZeroLogWrapper{log.Logger},
)
2018-04-05 04:16:20 -07:00
}
func NewContext(v *viper.Viper, config *tmcfg.Config, logger tmlog.Logger) *Context {
return &Context{v, config, logger}
2018-04-05 03:31:33 -07:00
}
func bindFlags(basename string, cmd *cobra.Command, v *viper.Viper) (err error) {
defer func() {
recover()
}()
cmd.Flags().VisitAll(func(f *pflag.Flag) {
// Environment variables can't have dashes in them, so bind them to their equivalent
// keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
err = v.BindEnv(f.Name, fmt.Sprintf("%s_%s", basename, strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))))
if err != nil {
panic(err)
}
err = v.BindPFlag(f.Name, f)
if err != nil {
panic(err)
}
// Apply the viper config value to the flag when the flag is not set and viper has a value
if !f.Changed && v.IsSet(f.Name) {
val := v.Get(f.Name)
err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
if err != nil {
panic(err)
}
}
})
return
}
// InterceptConfigsPreRunHandler performs a pre-run function for the root daemon
// application command. It will create a Viper literal and a default server
// Context. The server Tendermint configuration will either be read and parsed
// or created and saved to disk, where the server Context is updated to reflect
feat: Allow app developers to override default appConfig template (#9550) <!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #5540 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
2021-06-23 01:42:39 -07:00
// the Tendermint configuration. It takes custom app config template and config
// settings to create a custom Tendermint configuration. If the custom template
// is empty, it uses default-template provided by the server. The Viper literal
// is used to read and parse the application configuration. Command handlers can
// fetch the server Context to get the Tendermint configuration or to get access
// to Viper.
func InterceptConfigsPreRunHandler(cmd *cobra.Command, customAppConfigTemplate string, customAppConfig interface{}) error {
serverCtx := NewDefaultContext()
// Get the executable name and configure the viper instance so that environmental
// variables are checked based off that name. The underscore character is used
// as a separator
executableName, err := os.Executable()
if err != nil {
return err
}
2020-11-03 09:14:56 -08:00
basename := path.Base(executableName)
// Configure the viper instance
serverCtx.Viper.BindPFlags(cmd.Flags())
serverCtx.Viper.BindPFlags(cmd.PersistentFlags())
serverCtx.Viper.SetEnvPrefix(basename)
serverCtx.Viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
serverCtx.Viper.AutomaticEnv()
// intercept configuration files, using both Viper instances separately
feat: Allow app developers to override default appConfig template (#9550) <!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #5540 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
2021-06-23 01:42:39 -07:00
config, err := interceptConfigs(serverCtx.Viper, customAppConfigTemplate, customAppConfig)
if err != nil {
return err
}
// return value is a tendermint configuration object
serverCtx.Config = config
if err = bindFlags(basename, cmd, serverCtx.Viper); err != nil {
return err
}
var logWriter io.Writer
if strings.ToLower(serverCtx.Viper.GetString(flags.FlagLogFormat)) == tmlog.LogFormatPlain {
logWriter = zerolog.ConsoleWriter{Out: os.Stderr}
} else {
logWriter = os.Stderr
}
logLvlStr := serverCtx.Viper.GetString(flags.FlagLogLevel)
logLvl, err := zerolog.ParseLevel(logLvlStr)
if err != nil {
return fmt.Errorf("failed to parse log level (%s): %w", logLvlStr, err)
}
serverCtx.Logger = ZeroLogWrapper{zerolog.New(logWriter).Level(logLvl).With().Timestamp().Logger()}
return SetCmdServerContext(cmd, serverCtx)
}
// GetServerContextFromCmd returns a Context from a command or an empty Context
// if it has not been set.
func GetServerContextFromCmd(cmd *cobra.Command) *Context {
if v := cmd.Context().Value(ServerContextKey); v != nil {
serverCtxPtr := v.(*Context)
return serverCtxPtr
}
return NewDefaultContext()
}
// SetCmdServerContext sets a command's Context value to the provided argument.
func SetCmdServerContext(cmd *cobra.Command, serverCtx *Context) error {
v := cmd.Context().Value(ServerContextKey)
if v == nil {
return errors.New("server context not set")
}
serverCtxPtr := v.(*Context)
*serverCtxPtr = *serverCtx
return nil
}
// interceptConfigs parses and updates a Tendermint configuration file or
// creates a new one and saves it. It also parses and saves the application
// configuration file. The Tendermint configuration file is parsed given a root
// Viper object, whereas the application is parsed with the private package-aware
// viperCfg object.
feat: Allow app developers to override default appConfig template (#9550) <!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #5540 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
2021-06-23 01:42:39 -07:00
func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customConfig interface{}) (*tmcfg.Config, error) {
rootDir := rootViper.GetString(flags.FlagHome)
configPath := filepath.Join(rootDir, "config")
tmCfgFile := filepath.Join(configPath, "config.toml")
conf := tmcfg.DefaultConfig()
switch _, err := os.Stat(tmCfgFile); {
case os.IsNotExist(err):
tmcfg.EnsureRoot(rootDir)
if err = conf.ValidateBasic(); err != nil {
return nil, fmt.Errorf("error in config file: %v", err)
}
conf.RPC.PprofListenAddress = "localhost:6060"
conf.P2P.RecvRate = 5120000
conf.P2P.SendRate = 5120000
conf.Consensus.TimeoutCommit = 5 * time.Second
tmcfg.WriteConfigFile(rootDir, conf)
case err != nil:
return nil, err
default:
rootViper.SetConfigType("toml")
rootViper.SetConfigName("config")
rootViper.AddConfigPath(configPath)
if err := rootViper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read in %s: %w", tmCfgFile, err)
}
}
// Read into the configuration whatever data the viper instance has for it.
// This may come from the configuration file above but also any of the other
// sources viper uses.
if err := rootViper.Unmarshal(conf); err != nil {
return nil, err
}
conf.SetRoot(rootDir)
appCfgFilePath := filepath.Join(configPath, "app.toml")
if _, err := os.Stat(appCfgFilePath); os.IsNotExist(err) {
feat: Allow app developers to override default appConfig template (#9550) <!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #5540 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
2021-06-23 01:42:39 -07:00
if customAppTemplate != "" {
config.SetConfigTemplate(customAppTemplate)
if err = rootViper.Unmarshal(&customConfig); err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", appCfgFilePath, err)
}
feat: Allow app developers to override default appConfig template (#9550) <!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #5540 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
2021-06-23 01:42:39 -07:00
config.WriteConfigFile(appCfgFilePath, customConfig)
} else {
appConf, err := config.ParseConfig(rootViper)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", appCfgFilePath, err)
}
config.WriteConfigFile(appCfgFilePath, appConf)
}
}
rootViper.SetConfigType("toml")
rootViper.SetConfigName("app")
rootViper.AddConfigPath(configPath)
if err := rootViper.MergeInConfig(); err != nil {
return nil, fmt.Errorf("failed to merge configuration: %w", err)
}
return conf, nil
}
2018-04-05 03:24:53 -07:00
2018-04-18 21:49:24 -07:00
// add server commands
func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator, appExport types.AppExporter, addStartFlags types.ModuleInitFlags) {
2018-05-31 18:38:15 -07:00
tendermintCmd := &cobra.Command{
Use: "tendermint",
Short: "Tendermint subcommands",
}
2018-05-31 18:38:15 -07:00
tendermintCmd.AddCommand(
ShowNodeIDCmd(),
ShowValidatorCmd(),
ShowAddressCmd(),
VersionCmd(),
)
startCmd := StartCmd(appCreator, defaultNodeHome)
addStartFlags(startCmd)
rootCmd.AddCommand(
startCmd,
UnsafeResetAllCmd(),
2018-05-31 18:38:15 -07:00
tendermintCmd,
ExportCmd(appExport, defaultNodeHome),
version.NewVersionCommand(),
2018-04-05 03:24:53 -07:00
)
}
2018-04-21 19:26:46 -07:00
2018-04-23 12:15:50 -07:00
// https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
// TODO there must be a better way to get external IP
func ExternalIP() (string, error) {
2018-04-23 12:15:50 -07:00
ifaces, err := net.Interfaces()
if err != nil {
return "", err
}
2018-04-23 12:15:50 -07:00
for _, iface := range ifaces {
if skipInterface(iface) {
continue
2018-04-23 12:15:50 -07:00
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
2018-04-23 12:15:50 -07:00
for _, addr := range addrs {
ip := addrToIP(addr)
2018-04-23 12:15:50 -07:00
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip.String(), nil
}
}
return "", errors.New("are you connected to the network?")
}
2018-11-02 06:44:40 -07:00
// TrapSignal traps SIGINT and SIGTERM and terminates the server correctly.
func TrapSignal(cleanupFunc func()) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
2018-11-02 06:44:40 -07:00
go func() {
sig := <-sigs
if cleanupFunc != nil {
cleanupFunc()
}
exitCode := 128
2018-11-02 06:44:40 -07:00
switch sig {
case syscall.SIGINT:
exitCode += int(syscall.SIGINT)
case syscall.SIGTERM:
exitCode += int(syscall.SIGTERM)
2018-11-02 06:44:40 -07:00
}
os.Exit(exitCode)
2018-11-02 06:44:40 -07:00
}()
}
// WaitForQuitSignals waits for SIGINT and SIGTERM and returns.
func WaitForQuitSignals() ErrorCode {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
return ErrorCode{Code: int(sig.(syscall.Signal)) + 128}
}
func skipInterface(iface net.Interface) bool {
if iface.Flags&net.FlagUp == 0 {
return true // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
return true // loopback interface
}
return false
}
func addrToIP(addr net.Addr) net.IP {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
return ip
}
2020-07-29 07:53:14 -07:00
func openDB(rootDir string) (dbm.DB, error) {
dataDir := filepath.Join(rootDir, "data")
return sdk.NewLevelDB("application", dataDir)
}
func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
if traceWriterFile == "" {
return
}
return os.OpenFile(
traceWriterFile,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
}