diff --git a/README.md b/README.md index 36b29679d..a8db1b797 100644 --- a/README.md +++ b/README.md @@ -52,44 +52,38 @@ make `./tendermint daemon --help` +### Editing your config file +When `./tendermint daemon` is first run, a file will be create in ~/.tendermint/config.toml -### Editing your config.json +There is not official or testnet SeedNode yet. Will updated with an official list of seed nodes. -The file will be create in ~/.tendermint/config.json +//TODO Explanation of other config.toml fields -There is not official or testnet SeedNode yet. Will updated with an official list of seed nodes. +```toml +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml -//TODO Explanation of other config.json fields +Network = "tendermint_testnet0" +ListenAddr = "0.0.0.0:0" +# First node to connect to. Command-line overridable. +# Seed = "a.b.c.d:pppp" -``` -{ - "Network": "tendermint_testnet0", - "LAddr": "0.0.0.0:0", - "SeedNode": "", - "DB": { - "Backend": "leveldb", - "Dir": "/home/zaki/.tendermint/data" - }, - "Alert": { - "MinInterval": 0, - "TwilioSid": "", - "TwilioToken": "", - "TwilioFrom": "", - "TwilioTo": "", - "EmailRecipients": null - }, - "SMTP": { - "User": "", - "Password": "", - "Host": "", - "Port": 0 - }, - "RPC": { - "HTTPLAddr": "0.0.0.0:0" - } -} +[DB] +# The only other available backend is "memdb" +Backend = "leveldb" +# The leveldb data directory. +# Dir = "/.tendermint/data" + +[RPC] +# For the RPC API HTTP server. Port required. +HTTP.ListenAddr = "0.0.0.0:8080" + +[Alert] +# TODO: Document options +[SMTP] +# TODO: Document options ``` You will also to need to have a genesis.json in ~/.tendermint/. This must be the common genesis.json as the network you are joining from the Seed Node diff --git a/alert/alert.go b/alert/alert.go index 64e6c3830..959e81459 100644 --- a/alert/alert.go +++ b/alert/alert.go @@ -5,7 +5,7 @@ import ( "time" "github.com/sfreiberg/gotwilio" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" ) var lastAlertUnix int64 = 0 @@ -15,16 +15,16 @@ var alertCountSince int = 0 func Alert(message string) { log.Error(" ALERT \n" + message) now := time.Now().Unix() - if now-lastAlertUnix > int64(Config.Alert.MinInterval) { - message = fmt.Sprintf("%v:%v", Config.Network, message) + if now-lastAlertUnix > int64(config.App.GetInt("Alert.MinInterval")) { + message = fmt.Sprintf("%v:%v", config.App.GetString("Network"), message) if alertCountSince > 0 { message = fmt.Sprintf("%v (+%v more since)", message, alertCountSince) alertCountSince = 0 } - if len(Config.Alert.TwilioSid) > 0 { + if len(config.App.GetString("Alert.TwilioSid")) > 0 { go sendTwilio(message) } - if len(Config.Alert.EmailRecipients) > 0 { + if len(config.App.GetString("Alert.EmailRecipients")) > 0 { go sendEmail(message) } } else { @@ -41,8 +41,8 @@ func sendTwilio(message string) { if len(message) > 50 { message = message[:50] } - twilio := gotwilio.NewTwilioClient(Config.Alert.TwilioSid, Config.Alert.TwilioToken) - res, exp, err := twilio.SendSMS(Config.Alert.TwilioFrom, Config.Alert.TwilioTo, message, "", "") + twilio := gotwilio.NewTwilioClient(config.App.GetString("Alert.TwilioSid"), config.App.GetString("Alert.TwilioToken")) + res, exp, err := twilio.SendSMS(config.App.GetString("Alert.TwilioFrom"), config.App.GetString("Alert.TwilioTo"), message, "", "") if exp != nil || err != nil { log.Error("sendTwilio error", "res", res, "exp", exp, "error", err) } @@ -58,7 +58,7 @@ func sendEmail(message string) { if len(subject) > 80 { subject = subject[:80] } - err := SendEmail(subject, message, Config.Alert.EmailRecipients) + err := SendEmail(subject, message, config.App.GetStringSlice("Alert.EmailRecipients")) if err != nil { log.Error("sendEmail error", "error", err, "message", message) } diff --git a/alert/email.go b/alert/email.go index d4c12e95a..0a7102234 100644 --- a/alert/email.go +++ b/alert/email.go @@ -13,13 +13,13 @@ import ( "regexp" "strings" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" ) // Convenience function func SendEmail(subject, body string, tos []string) error { email := Compose(subject, body) - email.From = Config.SMTP.User + email.From = config.App.GetString("SMTP.User") email.ContentType = "text/html; charset=utf-8" email.AddRecipients(tos...) err := email.Send() @@ -86,12 +86,12 @@ func (e *Email) Send() error { auth := smtp.PlainAuth( "", - Config.SMTP.User, - Config.SMTP.Password, - Config.SMTP.Host, + config.App.GetString("SMTP.User"), + config.App.GetString("SMTP.Password"), + config.App.GetString("SMTP.Host"), ) - conn, err := smtp.Dial(fmt.Sprintf("%v:%v", Config.SMTP.Host, Config.SMTP.Port)) + conn, err := smtp.Dial(fmt.Sprintf("%v:%v", config.App.GetString("SMTP.Host"), config.App.GetString("SMTP.Port"))) if err != nil { return err } diff --git a/block/block.go b/block/block.go index 60fb2615e..1ff1378e7 100644 --- a/block/block.go +++ b/block/block.go @@ -11,7 +11,7 @@ import ( . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/merkle" ) @@ -27,7 +27,7 @@ type Block struct { // Basic validation that doesn't involve state data. func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte, lastBlockParts PartSetHeader, lastBlockTime time.Time) error { - if b.Network != Config.Network { + if b.Network != config.App.GetString("Network") { return errors.New("Wrong Block.Header.Network") } if b.Height != lastBlockHeight+1 { diff --git a/cmd/daemon.go b/cmd/daemon.go index 75ba49d58..10e9b5232 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -36,18 +36,18 @@ func NewNode() *Node { stateDB := db_.GetDB("state") state := state_.LoadState(stateDB) if state == nil { - state = state_.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + state = state_.MakeGenesisStateFromFile(stateDB, config.App.GetString("GenesisFile")) state.Save() } // Get PrivValidator var privValidator *state_.PrivValidator - if _, err := os.Stat(config.PrivValidatorFile()); err == nil { - privValidator = state_.LoadPrivValidator(config.PrivValidatorFile()) + if _, err := os.Stat(config.App.GetString("PrivValidatorFile")); err == nil { + privValidator = state_.LoadPrivValidator(config.App.GetString("PrivValidatorFile")) } // Get PEXReactor - book := p2p.NewAddrBook(config.AddrBookFile()) + book := p2p.NewAddrBook(config.App.GetString("AddrBookFile")) pexReactor := p2p.NewPEXReactor(book) // Get MempoolReactor @@ -122,13 +122,13 @@ func daemon() { // Create & start node n := NewNode() - l := p2p.NewDefaultListener("tcp", config.Config.LAddr, false) + l := p2p.NewDefaultListener("tcp", config.App.GetString("ListenAddr"), false) n.AddListener(l) n.Start() // If seedNode is provided by config, dial out. - if config.Config.SeedNode != "" { - peer, err := n.sw.DialPeerWithAddress(p2p.NewNetAddressString(config.Config.SeedNode)) + if config.App.GetString("SeedNode") != "" { + peer, err := n.sw.DialPeerWithAddress(p2p.NewNetAddressString(config.App.GetString("SeedNode"))) if err != nil { log.Error("Error dialing seed", "error", err) //n.book.MarkAttempt(addr) @@ -139,7 +139,7 @@ func daemon() { } // Run the RPC server. - if config.Config.RPC.HTTPLAddr != "" { + if config.App.GetString("RPC.HTTP.ListenAddr") != "" { rpc.SetRPCBlockStore(n.blockStore) rpc.SetRPCState(n.state) rpc.SetRPCMempoolReactor(n.mempoolReactor) diff --git a/cmd/gen_validator.go b/cmd/gen_validator.go index 8145bcea6..8b5e3e943 100644 --- a/cmd/gen_validator.go +++ b/cmd/gen_validator.go @@ -17,7 +17,7 @@ Paste the following JSON into your %v file %v `, - config.PrivValidatorFile(), + config.App.GetString("PrivValidatorFile"), string(privValidatorJSONBytes), ) } diff --git a/config/config.go b/config/config.go index be9447a83..f0dfdc112 100644 --- a/config/config.go +++ b/config/config.go @@ -1,182 +1,114 @@ package config import ( - "encoding/json" - "errors" - "flag" "fmt" "io/ioutil" - "net" "os" "path/filepath" "strings" - . "github.com/tendermint/tendermint/common" + "github.com/tendermint/confer" + flag "github.com/spf13/pflag" ) -//-----------------------------------------------------------------------------j -// Configuration types - -type ConfigType struct { - Network string - LAddr string - SeedNode string - DB DBConfig - Alert AlertConfig - SMTP SMTPConfig - RPC RPCConfig -} +var rootDir string +var App *confer.Config +var defaultConfig = ` +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml -type DBConfig struct { - Backend string - Dir string -} +Network = "tendermint_testnet0" +ListenAddr = "0.0.0.0:0" +# First node to connect to. Command-line overridable. +# SeedNode = "a.b.c.d:pppp" -type AlertConfig struct { - MinInterval int +[DB] +# The only other available backend is "memdb" +Backend = "leveldb" +# The leveldb data directory. +# Dir = "/.tendermint/data" - TwilioSid string - TwilioToken string - TwilioFrom string - TwilioTo string +[RPC] +# For the RPC API HTTP server. Port required. +HTTP.ListenAddr = "0.0.0.0:8080" - EmailRecipients []string -} +[Alert] +# TODO: Document options -type SMTPConfig struct { - User string - Password string - Host string - Port uint -} - -type RPCConfig struct { - HTTPLAddr string -} - -func (cfg *ConfigType) validate() error { - if cfg.Network == "" { - cfg.Network = defaultConfig.Network - } - if cfg.LAddr == "" { - cfg.LAddr = defaultConfig.LAddr - } - if cfg.SeedNode == "" { - cfg.SeedNode = defaultConfig.SeedNode - } - if cfg.DB.Backend == "" { - return errors.New("DB.Backend must be set") - } - if cfg.RPC.HTTPLAddr == "" { - fmt.Println("Set RPC.HTTPLAddr to \"0.0.0.0:8888\" in your config.json to enable the RPC API server.") - } else { - _, port, err := net.SplitHostPort(cfg.RPC.HTTPLAddr) - if err != nil { - return errors.New(Fmt("RPC.HTTPLAddr is invalid. %v", err)) - } - if port == "" || port == "0" { - return errors.New("RPC.HTTPLAddr is invalid. Port number must be defined") - } - } - return nil -} - -func (cfg *ConfigType) bytes() []byte { - configBytes, err := json.MarshalIndent(cfg, "", "\t") - if err != nil { - panic(err) - } - return configBytes -} - -func (cfg *ConfigType) write(configFile string) { - if strings.Index(configFile, "/") != -1 { - err := os.MkdirAll(filepath.Dir(configFile), 0700) - if err != nil { - panic(err) - } - } - err := ioutil.WriteFile(configFile, cfg.bytes(), 0600) - if err != nil { - panic(err) - } -} - -//----------------------------------------------------------------------------- - -var rootDir string -var defaultConfig ConfigType +[SMTP] +# TODO: Document options +` func init() { + // Get RootDir rootDir = os.Getenv("TMROOT") if rootDir == "" { rootDir = os.Getenv("HOME") + "/.tendermint" } + configFile := rootDir + "/config.toml" + + // Write default config file if missing. + if _, err := os.Stat(configFile); os.IsNotExist(err) { + if strings.Index(configFile, "/") != -1 { + err := os.MkdirAll(filepath.Dir(configFile), 0700) + if err != nil { + fmt.Printf("Could not create directory: %v", err) + os.Exit(1) + } + } + err := ioutil.WriteFile(configFile, []byte(defaultConfig), 0600) + if err != nil { + fmt.Printf("Could not write config file: %v", err) + os.Exit(1) + } + fmt.Printf("Config file written to %v. Please edit & run again\n", configFile) + os.Exit(1) + } - // Compute defaultConfig - defaultConfig = ConfigType{ - Network: "tendermint_testnet0", - LAddr: "0.0.0.0:0", - SeedNode: "", - DB: DBConfig{ - Backend: "leveldb", - Dir: DataDir(), - }, - Alert: AlertConfig{}, - SMTP: SMTPConfig{}, - RPC: RPCConfig{ - HTTPLAddr: "0.0.0.0:0", - }, + // Initialize Config + App = confer.NewConfig() + initDefaults() + paths := []string{configFile} + if err := App.ReadPaths(paths...); err != nil { + log.Warn("Error reading configuration", "paths", paths, "error", err) } } -func ConfigFile() string { return rootDir + "/config.json" } -func GenesisFile() string { return rootDir + "/genesis.json" } -func AddrBookFile() string { return rootDir + "/addrbook.json" } -func PrivValidatorFile() string { return rootDir + "/priv_validator.json" } -func DataDir() string { return rootDir + "/data" } - -// The actual global config singleton object. -var Config ConfigType - -func parseFlags(flags *flag.FlagSet, args []string) (printHelp bool) { - flags.BoolVar(&printHelp, "help", false, "Print this help message.") - flags.StringVar(&Config.LAddr, "laddr", Config.LAddr, "Listen address. (0.0.0.0:0 means any interface, any port)") - flags.StringVar(&Config.SeedNode, "seed", Config.SeedNode, "Address of seed node") - flags.StringVar(&Config.RPC.HTTPLAddr, "rpc_http_laddr", Config.RPC.HTTPLAddr, "RPC listen address. (0.0.0.0:0 means any interface, any port)") - flags.Parse(args) - return +func initDefaults() { + App.SetDefault("Network", "tendermint_testnet0") + App.SetDefault("ListenAddr", "0.0.0.0:0") + App.SetDefault("DB.Backend", "leveldb") + App.SetDefault("DB.Dir", rootDir+"/data") + App.SetDefault("Log.Level", "debug") + App.SetDefault("Log.Dir", rootDir+"/log") + App.SetDefault("RPC.HTTP.ListenAddr", "0.0.0.0:8080") + + App.SetDefault("GenesisFile", rootDir+"/genesis.json") + App.SetDefault("AddrbookFile", rootDir+"/addrbook.json") + App.SetDefault("PrivValidatorfile", rootDir+"/priv_valdiator.json") } func ParseFlags(args []string) { - configFile := ConfigFile() - - // try to read configuration from file. if missing, write default - configBytes, err := ioutil.ReadFile(configFile) - if err != nil { - defaultConfig.write(configFile) - fmt.Println("Config file written to config.json. Please edit & run again") - os.Exit(1) - return - } - - // try to parse configuration. on error, die - Config = ConfigType{} - err = json.Unmarshal(configBytes, &Config) - if err != nil { - Exit(Fmt("Invalid configuration file %s:\n%v\n", configFile, err)) - } - err = Config.validate() - if err != nil { - Exit(Fmt("Invalid configuration file %s:\n%v\n", configFile, err)) - } + var flags = flag.NewFlagSet("main", flag.ExitOnError) + var printHelp = false - // try to parse arg flags, which can override file configuration. - flags := flag.NewFlagSet("main", flag.ExitOnError) - printHelp := parseFlags(flags, args) + // Declare flags + flags.BoolVar(&printHelp, "help", false, "Print this help message.") + flags.String("listen_addr", App.GetString("ListenAddr"), "Listen address. (0.0.0.0:0 means any interface, any port)") + flags.String("seed_node", App.GetString("SeedNode"), "Address of seed node") + flags.String("rpc_http_listen_addr", App.GetString("RPC.HTTP.ListenAddr"), "RPC listen address. Port required") + flags.Parse(args) if printHelp { flags.PrintDefaults() os.Exit(0) } + + // Merge parsed flag values onto App. + App.BindPFlag("ListenAddr", flags.Lookup("listen_addr")) + App.BindPFlag("SeedNode", flags.Lookup("seed_node")) + App.BindPFlag("RPC.HTTP.ListenAddr", flags.Lookup("rpc_http_listen_addr")) + + // Confused? + //App.Debug() } diff --git a/config/log.go b/config/log.go new file mode 100644 index 000000000..570c2b126 --- /dev/null +++ b/config/log.go @@ -0,0 +1,7 @@ +package config + +import ( + "github.com/tendermint/log15" +) + +var log = log15.New("module", "config") diff --git a/consensus/state.go b/consensus/state.go index 120229b21..f0f14bb25 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -64,7 +64,7 @@ import ( . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/block" . "github.com/tendermint/tendermint/common" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" . "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/state" @@ -598,7 +598,7 @@ func (cs *ConsensusState) RunActionPropose(height uint, round uint) { txs := cs.mempoolReactor.Mempool.GetProposalTxs() block = &Block{ Header: &Header{ - Network: Config.Network, + Network: config.App.GetString("Network"), Height: cs.Height, Time: time.Now(), Fees: 0, // TODO fees diff --git a/db/db.go b/db/db.go index 3626ab00f..241a0f42d 100644 --- a/db/db.go +++ b/db/db.go @@ -4,7 +4,7 @@ import ( "path" . "github.com/tendermint/tendermint/common" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" ) type DB interface { @@ -28,19 +28,19 @@ func GetDB(name string) DB { if db != nil { return db.(DB) } - switch Config.DB.Backend { + switch config.App.GetString("DB.Backend") { case DBBackendMemDB: db := NewMemDB() dbs.Set(name, db) return db case DBBackendLevelDB: - db, err := NewLevelDB(path.Join(Config.DB.Dir, name+".db")) + db, err := NewLevelDB(path.Join(config.App.GetString("DB.Dir"), name+".db")) if err != nil { panic(err) } dbs.Set(name, db) return db default: - panic(Fmt("Unknown DB backend: %v", Config.DB.Backend)) + panic(Fmt("Unknown DB backend: %v", config.App.GetString("DB.Backend"))) } } diff --git a/p2p/README.md b/p2p/README.md index 941c93eec..cfbbf5c7d 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -70,7 +70,7 @@ for _, peer := range switch.Peers().List() { A `PEXReactor` reactor implementation is provided to automate peer discovery. ```go -book := p2p.NewAddrBook(config.AddrBookFile()) +book := p2p.NewAddrBook(config.App.GetString("AddrBookFile")) pexReactor := p2p.NewPEXReactor(book) ... switch := NewSwitch([]Reactor{pexReactor, myReactor, ...}) diff --git a/rpc/http_server.go b/rpc/http_server.go index 6a78ecb12..bd8d3b752 100644 --- a/rpc/http_server.go +++ b/rpc/http_server.go @@ -4,7 +4,7 @@ import ( "net/http" . "github.com/tendermint/tendermint/common" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" ) func StartHTTPServer() { @@ -13,9 +13,9 @@ func StartHTTPServer() { http.HandleFunc("/block", BlockHandler) http.HandleFunc("/broadcast_tx", BroadcastTxHandler) - log.Info(Fmt("Starting RPC HTTP server on %s", Config.RPC.HTTPLAddr)) + log.Info(Fmt("Starting RPC HTTP server on %s", config.App.GetString("RPC.HTTP.ListenAddr"))) go func() { - log.Crit("RPC HTTPServer stopped", "result", http.ListenAndServe(Config.RPC.HTTPLAddr, RecoverAndLogHandler(http.DefaultServeMux))) + log.Crit("RPC HTTPServer stopped", "result", http.ListenAndServe(config.App.GetString("RPC.HTTP.ListenAddr"), RecoverAndLogHandler(http.DefaultServeMux))) }() } diff --git a/state/priv_validator.go b/state/priv_validator.go index 575ba5bee..9c1ec7f3e 100644 --- a/state/priv_validator.go +++ b/state/priv_validator.go @@ -16,7 +16,7 @@ import ( . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/block" . "github.com/tendermint/tendermint/common" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" . "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/go-ed25519" @@ -70,7 +70,7 @@ func GenPrivValidator() *PrivValidator { LastHeight: 0, LastRound: 0, LastStep: stepNone, - filename: PrivValidatorFile(), + filename: config.App.GetString("PrivValidatorFile"), } } diff --git a/state/state_test.go b/state/state_test.go index fa30d283b..5e13f82dc 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -4,7 +4,7 @@ import ( . "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/block" - . "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/config" "bytes" "testing" @@ -60,7 +60,7 @@ func TestGenesisSaveLoad(t *testing.T) { // Mutate the state to append one empty block. block := &Block{ Header: &Header{ - Network: Config.Network, + Network: config.App.GetString("Network"), Height: 1, Time: s0.LastBlockTime.Add(time.Minute), Fees: 0,