diff --git a/cmd/erisdb/main.go b/cmd/erisdb/main.go index 9de6b6b69c4df0b177acc0c039669760bff2b711..a3ef77a82c44cc4c1b463f25a92875cfac9067e8 100644 --- a/cmd/erisdb/main.go +++ b/cmd/erisdb/main.go @@ -8,14 +8,21 @@ import ( // TODO the input stuff. func main() { + args := os.Args[1:] var baseDir string - if len(os.Args) == 2 { - baseDir = os.Args[1] + var inProc bool + if len(args) > 0 { + baseDir = args[0] + if len(args) > 1 { + if args[1] == "inproc" { + inProc = true + } + } } else { baseDir = os.Getenv("HOME") + "/.erisdb" } - proc, errSt := edb.ServeErisDB(baseDir) + proc, errSt := edb.ServeErisDB(baseDir, inProc) if errSt != nil { panic(errSt.Error()) } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000000000000000000000000000000000000..21c6b81f8ef0fcfcc48bcd2284583811c315b0eb --- /dev/null +++ b/config/config.go @@ -0,0 +1,117 @@ + +package config + +import ( + "github.com/naoina/toml" + "sync" + "time" + + . "github.com/tendermint/go-common" +) + +type Config interface { + Get(key string) interface{} + GetBool(key string) bool + GetFloat64(key string) float64 + GetInt(key string) int + GetString(key string) string + GetStringMap(key string) map[string]interface{} + GetStringMapString(key string) map[string]string + GetStringSlice(key string) []string + GetTime(key string) time.Time + IsSet(key string) bool + Set(key string, value interface{}) +} + +type MapConfig struct { + required map[string]struct{} // blows up if trying to use before setting. + data map[string]interface{} +} + +func ReadMapConfigFromFile(filePath string) (MapConfig, error) { + var configData = make(map[string]interface{}) + fileBytes := MustReadFile(filePath) + err := toml.Unmarshal(fileBytes, configData) + if err != nil { + return MapConfig{}, err + } + return NewMapConfig(configData), nil +} + +func NewMapConfig(data map[string]interface{}) MapConfig { + if data == nil { + data = make(map[string]interface{}) + } + return MapConfig{ + required: make(map[string]struct{}), + data: data, + } +} + +func (cfg MapConfig) Get(key string) interface{} { + if _, ok := cfg.required[key]; ok { + PanicSanity(Fmt("config key %v is required but was not set.", key)) + } + return cfg.data[key] +} +func (cfg MapConfig) GetBool(key string) bool { return cfg.Get(key).(bool) } +func (cfg MapConfig) GetFloat64(key string) float64 { return cfg.Get(key).(float64) } +func (cfg MapConfig) GetInt(key string) int { return cfg.Get(key).(int) } +func (cfg MapConfig) GetString(key string) string { return cfg.Get(key).(string) } +func (cfg MapConfig) GetStringMap(key string) map[string]interface{} { + return cfg.Get(key).(map[string]interface{}) +} +func (cfg MapConfig) GetStringMapString(key string) map[string]string { + return cfg.Get(key).(map[string]string) +} +func (cfg MapConfig) GetStringSlice(key string) []string { return cfg.Get(key).([]string) } +func (cfg MapConfig) GetTime(key string) time.Time { return cfg.Get(key).(time.Time) } +func (cfg MapConfig) IsSet(key string) bool { _, ok := cfg.data[key]; return ok } +func (cfg MapConfig) Set(key string, value interface{}) { + delete(cfg.required, key) + cfg.data[key] = value +} +func (cfg MapConfig) SetDefault(key string, value interface{}) { + delete(cfg.required, key) + if cfg.IsSet(key) { + return + } + cfg.data[key] = value +} +func (cfg MapConfig) SetRequired(key string) { + if cfg.IsSet(key) { + return + } + cfg.required[key] = struct{}{} +} + +//-------------------------------------------------------------------------------- +// A little convenient hack to notify listeners upon config changes. + +type Configurable func(Config) + +var mtx sync.Mutex +var globalConfig Config +var confs []Configurable + +func OnConfig(conf func(Config)) { + mtx.Lock() + defer mtx.Unlock() + + confs = append(confs, conf) + if globalConfig != nil { + conf(globalConfig) + } +} + +func ApplyConfig(config Config) { + mtx.Lock() + globalConfig = config + confsCopy := make([]Configurable, len(confs)) + copy(confsCopy, confs) + mtx.Unlock() + + for _, conf := range confsCopy { + conf(config) + } +} diff --git a/config/tendermint/config.go b/config/tendermint/config.go new file mode 100644 index 0000000000000000000000000000000000000000..25c85efa3874885d086c9ba80e0c1746122a9d2f --- /dev/null +++ b/config/tendermint/config.go @@ -0,0 +1,104 @@ +package tendermint + +import ( + "os" + "path" + "strings" + + . "github.com/tendermint/go-common" + cfg "github.com/tendermint/go-config" +) + +func getTMRoot(rootDir string) string { + if rootDir == "" { + rootDir = os.Getenv("TMROOT") + } + if rootDir == "" { + rootDir = os.Getenv("HOME") + "/.tendermint" + } + return rootDir +} + +func initTMRoot(rootDir string) { + rootDir = getTMRoot(rootDir) + EnsureDir(rootDir, 0700) + + configFilePath := path.Join(rootDir, "config.toml") + + // Write default config file if missing. + if !FileExists(configFilePath) { + // Ask user for moniker + // moniker := cfg.Prompt("Type hostname: ", "anonymous") + MustWriteFile(configFilePath, []byte(defaultConfig("anonymous")), 0644) + } +} + +func GetConfig(rootDir string) cfg.Config { + rootDir = getTMRoot(rootDir) + initTMRoot(rootDir) + + configFilePath := path.Join(rootDir, "config.toml") + mapConfig, err := cfg.ReadMapConfigFromFile(configFilePath) + if err != nil { + Exit(Fmt("Could not read config: %v", err)) + } + + // Set defaults or panic + if mapConfig.IsSet("chain_id") { + Exit("Cannot set 'chain_id' via config.toml") + } + if mapConfig.IsSet("revision_file") { + Exit("Cannot set 'revision_file' via config.toml. It must match what's in the Makefile") + } + mapConfig.SetRequired("chain_id") // blows up if you try to use it before setting. + mapConfig.SetDefault("erisdb_genesis_file", rootDir+"/erisdb_genesis.json") + mapConfig.SetDefault("tendermint_genesis_file", rootDir+"/tendermint_genesis.json") + mapConfig.SetDefault("genesis_file", rootDir+"/tendermint_genesis.json") // copy tendermint_genesis_file + mapConfig.SetDefault("proxy_app", "tcp://127.0.0.1:46658") + mapConfig.SetDefault("moniker", "anonymous") + mapConfig.SetDefault("node_laddr", "0.0.0.0:46656") + // mapConfig.SetDefault("seeds", "goldenalchemist.chaintest.net:46656") + mapConfig.SetDefault("fast_sync", true) + mapConfig.SetDefault("skip_upnp", false) + mapConfig.SetDefault("addrbook_file", rootDir+"/addrbook.json") + mapConfig.SetDefault("priv_validator_file", rootDir+"/priv_validator.json") + mapConfig.SetDefault("db_backend", "leveldb") + mapConfig.SetDefault("db_dir", rootDir+"/data") + mapConfig.SetDefault("log_level", "info") + mapConfig.SetDefault("rpc_laddr", "0.0.0.0:46657") + mapConfig.SetDefault("prof_laddr", "") + mapConfig.SetDefault("revision_file", rootDir+"/revision") + mapConfig.SetDefault("cswal", rootDir+"/data/cswal") + mapConfig.SetDefault("cswal_light", false) + + mapConfig.SetDefault("block_size", 10000) + mapConfig.SetDefault("timeout_propose", 3000) + mapConfig.SetDefault("timeout_propose_delta", 500) + mapConfig.SetDefault("timeout_prevote", 1000) + mapConfig.SetDefault("timeout_prevote_delta", 500) + mapConfig.SetDefault("timeout_precommit", 1000) + mapConfig.SetDefault("timeout_precommit_delta", 500) + mapConfig.SetDefault("timeout_commit", 1000) + mapConfig.SetDefault("mempool_recheck", true) + mapConfig.SetDefault("mempool_broadcast", true) + + return mapConfig +} + +var defaultConfigTmpl = `# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "tcp://127.0.0.1:46658" +moniker = "__MONIKER__" +node_laddr = "0.0.0.0:46656" +seeds = "" +fast_sync = true +db_backend = "leveldb" +log_level = "notice" +rpc_laddr = "0.0.0.0:46657" +` + +func defaultConfig(moniker string) (defaultConfig string) { + defaultConfig = strings.Replace(defaultConfigTmpl, "__MONIKER__", moniker, -1) + return +} diff --git a/config/tendermint/genesis.json b/config/tendermint/genesis.json new file mode 100644 index 0000000000000000000000000000000000000000..eca00696edb4414df42fe6770cc23cde5a758529 --- /dev/null +++ b/config/tendermint/genesis.json @@ -0,0 +1,75 @@ +{ + "chain_id": "tendermint_testnet_11.c", + "accounts": [ + { + "address": "9FCBA7F840A0BFEBBE755E853C9947270A912D04", + "amount": 1991999998000000 + }, + { + "address": "964B1493BBE3312278B7DEB94C39149F7899A345", + "amount": 100000000000000 + }, + { + "address": "B9FA4AB462B9C6BF6A62DB4AE77C9E7087209A04", + "amount": 1000000000000 + }, + { + "address": "F171824590D69386F709E7B6704B369C5A370D60", + "amount": 1000000000000 + }, + { + "address": "56EFE746A13D9A6054AC89C3E2A361C2DB8B9EAE", + "amount": 1000000000000 + }, + { + "address": "7C2E032D8407EDF66A04D88CF0E1D9B15D98AE2D", + "amount": 1000000000000 + }, + { + "address": "636EF5823E082AD66EBC203FD4DFB1031F0C61CA", + "amount": 1000000000000 + }, + { + "address": "9008419E6351360A59B124E707E4CA2A5BFB9BE6", + "amount": 1000000000000 + }, + { + "address": "C78F48919B8A4030AD3E5ED643F8D2302E41953D", + "amount": 1000000000000 + }, + { + "address": "5290AC90CE2422DDC3F91F6A246F7E3C542EA51A", + "amount": 1000000000000 + }, + { + "address": "A88A61069B6660F30F65E8786AFDD4F1D8F625E9", + "amount": 1000000 + }, + { + "address": "EE2EE9247973B4AFC3867CFE5F415410AC251B61", + "amount": 1000000 + } + ], + "validators": [ + { + "pub_key": [1, "178EC6008A4364508979C70CBF100BD4BCBAA12DDE6251F5F486B4FD09014F06"], + "amount": 100000000000 + }, + { + "pub_key": [1, "2A77777CC51467DE42350D4A8F34720D527734189BE64C7A930DD169E1FED3C6"], + "amount": 100000000000 + }, + { + "pub_key": [1, "3718E69D09B11B3AD3FA31AEF07EC416D2AEED241CACE7B0F30AE9803FFB0F08"], + "amount": 100000000000 + }, + { + "pub_key": [1, "C6B0440DEACD1E4CF1C736CEB8E38E788B700BA2B2045A55CB657A455CF5F889"], + "amount": 100000000000 + }, + { + "pub_key": [1, "3BA1190D54F91EFBF8B0125F7EC116AD4BA2894B6EE38564A5D5FD3230D91F7B"], + "amount": 100000000000 + } + ] +} diff --git a/config/tendermint/logrotate.config b/config/tendermint/logrotate.config new file mode 100644 index 0000000000000000000000000000000000000000..73eaf74e74b50cb744de5dfba2dfa3835f0f5c45 --- /dev/null +++ b/config/tendermint/logrotate.config @@ -0,0 +1,22 @@ +// If you wanted to use logrotate, I suppose this might be the config you want. +// Instead, I'll just write our own, that way we don't need sudo to install. + +$HOME/.tendermint/logs/tendermint.log { + missingok + notifempty + rotate 12 + daily + size 10M + compress + delaycompress +} + +$HOME/.barak/logs/barak.log { + missingok + notifempty + rotate 12 + weekly + size 10M + compress + delaycompress +} diff --git a/config/tendermint_test/config.go b/config/tendermint_test/config.go new file mode 100644 index 0000000000000000000000000000000000000000..b90fe25491b6860808dfd9a5b7204a2456e435c6 --- /dev/null +++ b/config/tendermint_test/config.go @@ -0,0 +1,151 @@ +// Import this in all *_test.go files to initialize ~/.tendermint_test. + +package tendermint_test + +import ( + "os" + "path" + "strings" + + . "github.com/tendermint/go-common" + cfg "github.com/tendermint/go-config" +) + +func init() { + // Creates ~/.tendermint_test + EnsureDir(os.Getenv("HOME")+"/.tendermint_test", 0700) +} + +func ResetConfig(path string) { + rootDir := os.Getenv("HOME") + "/.tendermint_test/" + path + cfg.ApplyConfig(GetConfig(rootDir)) +} + +func initTMRoot(rootDir string) { + // Remove ~/.tendermint_test_bak + if FileExists(rootDir + "_bak") { + err := os.RemoveAll(rootDir + "_bak") + if err != nil { + PanicSanity(err.Error()) + } + } + // Move ~/.tendermint_test to ~/.tendermint_test_bak + if FileExists(rootDir) { + err := os.Rename(rootDir, rootDir+"_bak") + if err != nil { + PanicSanity(err.Error()) + } + } + // Create new dir + EnsureDir(rootDir, 0700) + + configFilePath := path.Join(rootDir, "config.toml") + genesisFilePath := path.Join(rootDir, "genesis.json") + privFilePath := path.Join(rootDir, "priv_validator.json") + + // Write default config file if missing. + if !FileExists(configFilePath) { + // Ask user for moniker + // moniker := cfg.Prompt("Type hostname: ", "anonymous") + MustWriteFile(configFilePath, []byte(defaultConfig("anonymous")), 0644) + } + if !FileExists(genesisFilePath) { + MustWriteFile(genesisFilePath, []byte(defaultGenesis), 0644) + } + // we always overwrite the priv val + MustWriteFile(privFilePath, []byte(defaultPrivValidator), 0644) +} + +func GetConfig(rootDir string) cfg.Config { + initTMRoot(rootDir) + + configFilePath := path.Join(rootDir, "config.toml") + mapConfig, err := cfg.ReadMapConfigFromFile(configFilePath) + if err != nil { + Exit(Fmt("Could not read config: %v", err)) + } + + // Set defaults or panic + if mapConfig.IsSet("chain_id") { + Exit("Cannot set 'chain_id' via config.toml") + } + mapConfig.SetDefault("chain_id", "tendermint_test") + mapConfig.SetDefault("genesis_file", rootDir+"/genesis.json") + mapConfig.SetDefault("proxy_app", "dummy") + mapConfig.SetDefault("moniker", "anonymous") + mapConfig.SetDefault("node_laddr", "0.0.0.0:36656") + mapConfig.SetDefault("fast_sync", false) + mapConfig.SetDefault("skip_upnp", true) + mapConfig.SetDefault("addrbook_file", rootDir+"/addrbook.json") + mapConfig.SetDefault("priv_validator_file", rootDir+"/priv_validator.json") + mapConfig.SetDefault("db_backend", "memdb") + mapConfig.SetDefault("db_dir", rootDir+"/data") + mapConfig.SetDefault("log_level", "debug") + mapConfig.SetDefault("rpc_laddr", "0.0.0.0:36657") + mapConfig.SetDefault("prof_laddr", "") + mapConfig.SetDefault("revision_file", rootDir+"/revision") + mapConfig.SetDefault("cswal", rootDir+"/data/cswal") + mapConfig.SetDefault("cswal_light", false) + + mapConfig.SetDefault("block_size", 10000) + mapConfig.SetDefault("timeout_propose", 100) + mapConfig.SetDefault("timeout_propose_delta", 1) + mapConfig.SetDefault("timeout_prevote", 1) + mapConfig.SetDefault("timeout_prevote_delta", 1) + mapConfig.SetDefault("timeout_precommit", 1) + mapConfig.SetDefault("timeout_precommit_delta", 1) + mapConfig.SetDefault("timeout_commit", 1) + mapConfig.SetDefault("mempool_recheck", true) + mapConfig.SetDefault("mempool_broadcast", true) + + return mapConfig +} + +var defaultConfigTmpl = `# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +proxy_app = "dummy" +moniker = "__MONIKER__" +node_laddr = "0.0.0.0:36656" +seeds = "" +fast_sync = false +db_backend = "memdb" +log_level = "debug" +rpc_laddr = "0.0.0.0:36657" +` + +func defaultConfig(moniker string) (defaultConfig string) { + defaultConfig = strings.Replace(defaultConfigTmpl, "__MONIKER__", moniker, -1) + return +} + +var defaultGenesis = `{ + "genesis_time": "0001-01-01T00:00:00.000Z", + "chain_id": "tendermint_test", + "validators": [ + { + "pub_key": [ + 1, + "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" + ], + "amount": 10, + "name": "" + } + ], + "app_hash": "" +}` + +var defaultPrivValidator = `{ + "address": "D028C9981F7A87F3093672BF0D5B0E2A1B3ED456", + "pub_key": [ + 1, + "3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" + ], + "priv_key": [ + 1, + "27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" + ], + "last_height": 0, + "last_round": 0, + "last_step": 0 +}` diff --git a/erisdb/pipe/blockchain.go b/erisdb/pipe/blockchain.go index d2bca86d91bf5dfb1983e03b8fef2c8de91b7982..d6e650377409876b2b6b17da065745d1d23a36c3 100644 --- a/erisdb/pipe/blockchain.go +++ b/erisdb/pipe/blockchain.go @@ -2,9 +2,9 @@ package pipe import ( "fmt" + "github.com/eris-ltd/eris-db/state" dbm "github.com/tendermint/go-db" "github.com/tendermint/tendermint/types" - "github.com/eris-ltd/eris-db/state" "math" "strconv" "strings" @@ -42,7 +42,7 @@ func newBlockchain(blockStore BlockStore) *blockchain { func (this *blockchain) Info() (*BlockchainInfo, error) { chainId := config.GetString("chain_id") db := dbm.NewMemDB() - _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) + _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("erisdb_genesis_file")) genesisHash := genesisState.Hash() latestHeight := this.blockStore.Height() @@ -68,7 +68,7 @@ func (this *blockchain) ChainId() (string, error) { // Get the hash of the genesis block. func (this *blockchain) GenesisHash() ([]byte, error) { db := dbm.NewMemDB() - _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("genesis_file")) + _, genesisState := state.MakeGenesisStateFromFile(db, config.GetString("erisdb_genesis_file")) return genesisState.Hash(), nil } diff --git a/erisdb/serve.go b/erisdb/serve.go index 478ae9aa7b3a1b09d79e7b6dab45ae5fbd2df481..203e42618a7226df35cb3a0ada7d33746a253c91 100644 --- a/erisdb/serve.go +++ b/erisdb/serve.go @@ -4,10 +4,12 @@ package erisdb import ( "bytes" + "fmt" + "io/ioutil" "path" + "strings" + "sync" - sm "github.com/eris-ltd/eris-db/state" - stypes "github.com/eris-ltd/eris-db/state/types" . "github.com/tendermint/go-common" cfg "github.com/tendermint/go-config" dbm "github.com/tendermint/go-db" @@ -15,14 +17,19 @@ import ( "github.com/tendermint/go-p2p" "github.com/tendermint/go-wire" "github.com/tendermint/log15" - tmcfg "github.com/tendermint/tendermint/config/tendermint" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" + tmspcli "github.com/tendermint/tmsp/client" + tmsp "github.com/tendermint/tmsp/server" + tmcfg "github.com/eris-ltd/eris-db/config/tendermint" ep "github.com/eris-ltd/eris-db/erisdb/pipe" "github.com/eris-ltd/eris-db/server" - + sm "github.com/eris-ltd/eris-db/state" + stypes "github.com/eris-ltd/eris-db/state/types" edbapp "github.com/eris-ltd/eris-db/tmsp" - tmsp "github.com/tendermint/tmsp/server" ) const ERISDB_VERSION = "0.11.5" @@ -35,7 +42,7 @@ var tmConfig cfg.Config // with a tmsp listener for talking to tendermint core. // To start listening for incoming requests, call 'Start()' on the process. // Make sure to register any start event listeners first -func ServeErisDB(workDir string) (*server.ServeProcess, error) { +func ServeErisDB(workDir string, inProc bool) (*server.ServeProcess, error) { log.Info("ErisDB Serve initializing.") errEns := EnsureDir(workDir, 0777) @@ -68,19 +75,15 @@ func ServeErisDB(workDir string) (*server.ServeProcess, error) { tmConfig.Set("version", TENDERMINT_VERSION) cfg.ApplyConfig(tmConfig) // Notify modules of new config - // Set the node up. - // nodeRd := make(chan struct{}) - // nd := node.NewNode() - // Load the application state // The app state used to be managed by tendermint node, // but is now managed by ErisDB. // The tendermint core only stores the blockchain (history of txs) - stateDB := dbm.GetDB("state") + stateDB := dbm.GetDB("app_state") state := sm.LoadState(stateDB) var genDoc *stypes.GenesisDoc if state == nil { - genDoc, state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file")) + genDoc, state = sm.MakeGenesisStateFromFile(stateDB, config.GetString("erisdb_genesis_file")) state.Save() // write the gendoc to db buf, n, err := new(bytes.Buffer), new(int), new(error) @@ -102,18 +105,25 @@ func ServeErisDB(workDir string) (*server.ServeProcess, error) { evsw := events.NewEventSwitch() evsw.Start() + app := edbapp.NewErisDBApp(state, evsw) app.SetHostAddress(sConf.Consensus.TendermintHost) - // Start the tmsp listener for state update commands - go func() { - // TODO config - _, err := tmsp.NewServer(sConf.Consensus.TMSPListener, app) - if err != nil { - // TODO: play nice - Exit(err.Error()) - } - }() + if inProc { + fmt.Println("Starting tm node in proc") + startTMNode(app) + } else { + fmt.Println("Starting tmsp listener") + // Start the tmsp listener for state update commands + go func() { + // TODO config + _, err := tmsp.NewServer(sConf.Consensus.TMSPListener, app) + if err != nil { + // TODO: play nice + Exit(err.Error()) + } + }() + } // Load supporting objects. pipe := ep.NewPipe(app, evsw) @@ -135,6 +145,51 @@ func ServeErisDB(workDir string) (*server.ServeProcess, error) { return proc, nil } +func startTMNode(app *edbapp.ErisDBApp) { + // get the genesis + genDocFile := config.GetString("tendermint_genesis_file") + jsonBlob, err := ioutil.ReadFile(genDocFile) + if err != nil { + Exit(Fmt("Couldn't read GenesisDoc file: %v", err)) + } + genDoc := types.GenesisDocFromJSON(jsonBlob) + if genDoc.ChainID == "" { + PanicSanity(Fmt("Genesis doc %v must include non-empty chain_id", genDocFile)) + } + config.Set("chain_id", genDoc.ChainID) + config.Set("genesis_doc", genDoc) + + // Get PrivValidator + privValidatorFile := config.GetString("priv_validator_file") + privValidator := types.LoadOrGenPrivValidator(privValidatorFile) + nd := node.NewNode(privValidator, func(addr string, hash []byte) proxy.AppConn { + // TODO: Check the hash + return tmspcli.NewLocalClient(new(sync.Mutex), app) + }) + + l := p2p.NewDefaultListener("tcp", config.GetString("node_laddr"), config.GetBool("skip_upnp")) + nd.AddListener(l) + if err := nd.Start(); err != nil { + Exit(Fmt("Failed to start node: %v", err)) + } + + log.Notice("Started node", "nodeInfo", nd.NodeInfo()) + + // If seedNode is provided by config, dial out. + if config.GetString("seeds") != "" { + seeds := strings.Split(config.GetString("seeds"), ",") + nd.DialSeeds(seeds) + } + + // Run the RPC server. + if config.GetString("rpc_laddr") != "" { + _, err := nd.StartRPC() + if err != nil { + PanicCrisis(err) + } + } +} + // Private. Create a new node. func startNode(nd *node.Node, ready chan struct{}, shutDown <-chan struct{}) { laddr := tmConfig.GetString("node_laddr")