diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 3befae27..6a793706 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -67,7 +67,15 @@ func NewRunNodeCmd(signerAndApp FuncSignerAndApp) *cobra.Command { // Create & start node privVal, clientCreator := signerAndApp(config) - n := node.NewNode(config, privVal, clientCreator, logger.With("module", "node")) + n, err := node.NewNode(config, + privVal, + clientCreator, + node.DefaultGenesisDocProviderFunc(config), + node.DefaultDBProvider, + logger.With("module", "node")) + if err != nil { + return fmt.Errorf("Failed to create node: %v", err) + } if _, err := n.Start(); err != nil { return fmt.Errorf("Failed to start node: %v", err) diff --git a/consensus/common_test.go b/consensus/common_test.go index bf56b8bf..a746ec59 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -268,7 +268,7 @@ func loadPrivValidator(config *cfg.Config) *types.PrivValidatorFS { func fixedConsensusStateDummy() *ConsensusState { stateDB := dbm.NewMemDB() - state := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) state.SetLogger(log.TestingLogger().With("module", "state")) privValidator := loadPrivValidator(config) cs := newConsensusState(state, privValidator, dummy.NewDummyApplication()) @@ -338,7 +338,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou logger := consensusLogger() for i := 0; i < nValidators; i++ { db := dbm.NewMemDB() // each state needs its own db - state := sm.MakeGenesisState(db, genDoc) + state, _ := sm.MakeGenesisState(db, genDoc) state.SetLogger(logger.With("module", "state", "validator", i)) state.Save() thisConfig := ResetConfig(Fmt("%s_%d", testName, i)) @@ -359,7 +359,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF css := make([]*ConsensusState, nPeers) for i := 0; i < nPeers; i++ { db := dbm.NewMemDB() // each state needs its own db - state := sm.MakeGenesisState(db, genDoc) + state, _ := sm.MakeGenesisState(db, genDoc) state.SetLogger(log.TestingLogger().With("module", "state")) state.Save() thisConfig := ResetConfig(Fmt("%s_%d", testName, i)) @@ -414,7 +414,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G func randGenesisState(numValidators int, randPower bool, minPower int64) (*sm.State, []*types.PrivValidatorFS) { genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) db := dbm.NewMemDB() - s0 := sm.MakeGenesisState(db, genDoc) + s0, _ := sm.MakeGenesisState(db, genDoc) s0.SetLogger(log.TestingLogger().With("module", "state")) s0.Save() return s0, privValidators diff --git a/consensus/replay_file.go b/consensus/replay_file.go index 39ce47c5..1182aaf0 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -241,12 +241,15 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo // Get State stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir()) - state := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + state, err := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + if err != nil { + cmn.Exit(err.Error()) + } // Create proxyAppConn connection (consensus, mempool, query) clientCreator := proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()) proxyApp := proxy.NewAppConns(clientCreator, NewHandshaker(state, blockStore)) - _, err := proxyApp.Start() + _, err = proxyApp.Start() if err != nil { cmn.Exit(cmn.Fmt("Error starting proxy app conns: %v", err)) } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index d2d848bc..c6504380 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -558,7 +558,7 @@ func readPieceFromWAL(msgBytes []byte) (interface{}, error) { // fresh state and mock store func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBlockStore) { stateDB := dbm.NewMemDB() - state := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) + state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile()) state.SetLogger(log.TestingLogger().With("module", "state")) store := NewMockBlockStore(config, state.Params()) diff --git a/node/node.go b/node/node.go index 4f22ee92..761f4d5e 100644 --- a/node/node.go +++ b/node/node.go @@ -3,6 +3,7 @@ package node import ( "bytes" "errors" + "fmt" "net" "net/http" "strings" @@ -10,11 +11,15 @@ import ( abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" + cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/consensus" mempl "github.com/tendermint/tendermint/mempool" - p2p "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" rpccore "github.com/tendermint/tendermint/rpc/core" grpccore "github.com/tendermint/tendermint/rpc/grpc" @@ -26,13 +31,44 @@ import ( "github.com/tendermint/tendermint/state/txindex/null" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" _ "net/http/pprof" ) +//------------------------------------------------------------------------------ + +// DBContext specifies config information for loading a new DB. +type DBContext struct { + ID string + Config *cfg.Config +} + +// DBProvider takes a DBContext and returns an instantiated DB. +type DBProvider func(*DBContext) (dbm.DB, error) + +// DefaultDBProvider returns a database using the DBBackend and DBDir +// specified in the ctx.Config. +func DefaultDBProvider(ctx *DBContext) (dbm.DB, error) { + return dbm.NewDB(ctx.ID, ctx.Config.DBBackend, ctx.Config.DBDir()), nil +} + +// GenesisDocProvider returns a GenesisDoc. +// It allows the GenesisDoc to be pulled from sources other than the +// filesystem, for instance from a distributed key-value store cluster. +type GenesisDocProvider func() (*types.GenesisDoc, error) + +// DefaultGenesisDocProviderFunc returns a GenesisDocProvider that loads +// the GenesisDoc from the config.GenesisFile() on the filesystem. +func DefaultGenesisDocProviderFunc(config *cfg.Config) GenesisDocProvider { + return func() (*types.GenesisDoc, error) { + return types.GenesisDocFromFile(config.GenesisFile()) + } +} + +//------------------------------------------------------------------------------ + +// Node is the highest level interface to a full Tendermint node. +// It includes all configuration information and running services. type Node struct { cmn.BaseService @@ -58,24 +94,55 @@ type Node struct { txIndexer txindex.TxIndexer } -func NewNodeDefault(config *cfg.Config, logger log.Logger) *Node { +// NewNodeDefault returns a Tendermint node with default settings for the +// PrivValidator, ClientCreator, GenesisDoc, and DBProvider, +func NewNodeDefault(config *cfg.Config, logger log.Logger) (*Node, error) { // Get PrivValidator privValidator := types.LoadOrGenPrivValidatorFS(config.PrivValidatorFile()) - return NewNode(config, privValidator, - proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), logger) + return NewNode(config, + privValidator, + proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), + DefaultGenesisDocProviderFunc(config), + DefaultDBProvider, + logger) } -func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreator proxy.ClientCreator, logger log.Logger) *Node { +// NewNode returns a new, ready to go, Tendermint Node. +func NewNode(config *cfg.Config, + privValidator types.PrivValidator, + clientCreator proxy.ClientCreator, + genDocProvider GenesisDocProvider, + dbProvider DBProvider, + logger log.Logger) (*Node, error) { + // Get BlockStore - blockStoreDB := dbm.NewDB("blockstore", config.DBBackend, config.DBDir()) + blockStoreDB, err := dbProvider(&DBContext{"blockstore", config}) + if err != nil { + return nil, err + } blockStore := bc.NewBlockStore(blockStoreDB) consensusLogger := logger.With("module", "consensus") stateLogger := logger.With("module", "state") // Get State - stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir()) - state := sm.GetState(stateDB, config.GenesisFile()) + stateDB, err := dbProvider(&DBContext{"state", config}) + if err != nil { + return nil, err + } + state := sm.LoadState(stateDB) + if state == nil { + genDoc, err := genDocProvider() + if err != nil { + return nil, err + } + state, err = sm.MakeGenesisState(stateDB, genDoc) + if err != nil { + return nil, err + } + state.Save() + } + state.SetLogger(stateLogger) // Create the proxyApp, which manages connections (consensus, mempool, query) @@ -85,7 +152,7 @@ func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreato proxyApp := proxy.NewAppConns(clientCreator, handshaker) proxyApp.SetLogger(logger.With("module", "proxy")) if _, err := proxyApp.Start(); err != nil { - cmn.Exit(cmn.Fmt("Error starting proxy app connections: %v", err)) + return nil, fmt.Errorf("Error starting proxy app connections: %v", err) } // reload the state (it may have been updated by the handshake) @@ -96,7 +163,10 @@ func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreato var txIndexer txindex.TxIndexer switch config.TxIndex { case "kv": - store := dbm.NewDB("tx_index", config.DBBackend, config.DBDir()) + store, err := dbProvider(&DBContext{"tx_index", config}) + if err != nil { + return nil, err + } txIndexer = kv.NewTxIndex(store) default: txIndexer = &null.TxIndex{} @@ -109,9 +179,8 @@ func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreato // Make event switch eventSwitch := types.NewEventSwitch() eventSwitch.SetLogger(logger.With("module", "types")) - _, err := eventSwitch.Start() - if err != nil { - cmn.Exit(cmn.Fmt("Failed to start switch: %v", err)) + if _, err := eventSwitch.Start(); err != nil { + return nil, fmt.Errorf("Failed to start switch: %v", err) } // Decide whether to fast-sync or not @@ -232,9 +301,10 @@ func NewNode(config *cfg.Config, privValidator types.PrivValidator, clientCreato txIndexer: txIndexer, } node.BaseService = *cmn.NewBaseService(logger, "Node", node) - return node + return node, nil } +// OnStart starts the Node. It implements cmn.Service. func (n *Node) OnStart() error { // Create & add listener protocol, address := ProtocolAndAddress(n.config.P2P.ListenAddress) @@ -270,6 +340,7 @@ func (n *Node) OnStart() error { return nil } +// OnStop stops the Node. It implements cmn.Service. func (n *Node) OnStop() { n.BaseService.OnStop() @@ -285,6 +356,7 @@ func (n *Node) OnStop() { } } +// RunForever waits for an interupt signal and stops the node. func (n *Node) RunForever() { // Sleep forever and then... cmn.TrapSignal(func() { @@ -292,15 +364,15 @@ func (n *Node) RunForever() { }) } -// Add the event switch to reactors, mempool, etc. +// SetEventSwitch adds the event switch to reactors, mempool, etc. func SetEventSwitch(evsw types.EventSwitch, eventables ...types.Eventable) { for _, e := range eventables { e.SetEventSwitch(evsw) } } -// Add a Listener to accept inbound peer connections. -// Add listeners before starting the Node. +// AddListener adds a listener to accept inbound peer connections. +// It should be called before starting the Node. // The first listener is the primary listener (in NodeInfo) func (n *Node) AddListener(l p2p.Listener) { n.sw.AddListener(l) @@ -360,39 +432,48 @@ func (n *Node) startRPC() ([]net.Listener, error) { return listeners, nil } +// Switch returns the Node's Switch. func (n *Node) Switch() *p2p.Switch { return n.sw } +// BlockStore returns the Node's BlockStore. func (n *Node) BlockStore() *bc.BlockStore { return n.blockStore } +// ConsensusState returns the Node's ConsensusState. func (n *Node) ConsensusState() *consensus.ConsensusState { return n.consensusState } +// ConsensusReactor returns the Node's ConsensusReactor. func (n *Node) ConsensusReactor() *consensus.ConsensusReactor { return n.consensusReactor } +// MempoolReactor returns the Node's MempoolReactor. func (n *Node) MempoolReactor() *mempl.MempoolReactor { return n.mempoolReactor } +// EventSwitch returns the Node's EventSwitch. func (n *Node) EventSwitch() types.EventSwitch { return n.evsw } -// XXX: for convenience +// PrivValidator returns the Node's PrivValidator. +// XXX: for convenience only! func (n *Node) PrivValidator() types.PrivValidator { return n.privValidator } +// GenesisDoc returns the Node's GenesisDoc. func (n *Node) GenesisDoc() *types.GenesisDoc { return n.genesisDoc } +// ProxyApp returns the Node's AppConns, representing its connections to the ABCI application. func (n *Node) ProxyApp() proxy.AppConns { return n.proxyApp } @@ -442,15 +523,18 @@ func (n *Node) makeNodeInfo() *p2p.NodeInfo { //------------------------------------------------------------------------------ +// NodeInfo returns the Node's Info from the Switch. func (n *Node) NodeInfo() *p2p.NodeInfo { return n.sw.NodeInfo() } +// DialSeeds dials the given seeds on the Switch. func (n *Node) DialSeeds(seeds []string) error { return n.sw.DialSeeds(n.addrBook, seeds) } -// Defaults to tcp +// ProtocolAndAddress returns the transport protocol +// and the ip address from the given string. Defaults to tcp. func ProtocolAndAddress(listenAddr string) (string, string) { protocol, address := "tcp", listenAddr parts := strings.SplitN(address, "://", 2) diff --git a/node/node_test.go b/node/node_test.go index 3c751af6..1b6d27f4 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -4,15 +4,19 @@ import ( "testing" "time" - cfg "github.com/tendermint/tendermint/config" + "github.com/stretchr/testify/assert" + "github.com/tendermint/tmlibs/log" + + cfg "github.com/tendermint/tendermint/config" ) func TestNodeStartStop(t *testing.T) { config := cfg.ResetTestRoot("node_node_test") // Create & start node - n := NewNodeDefault(config, log.TestingLogger()) + n, err := NewNodeDefault(config, log.TestingLogger()) + assert.NoError(t, err, "expected no err on NewNodeDefault") n.Start() t.Logf("Started node %v", n.sw.NodeInfo()) diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 82b14ce3..55e27f5b 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -81,6 +81,11 @@ func NewTendermint(app abci.Application) *nm.Node { privValidatorFile := config.PrivValidatorFile() privValidator := types.LoadOrGenPrivValidatorFS(privValidatorFile) papp := proxy.NewLocalClientCreator(app) - node := nm.NewNode(config, privValidator, papp, logger) + node, err := nm.NewNode(config, privValidator, papp, + nm.DefaultGenesisDocProviderFunc(config), + nm.DefaultDBProvider, logger) + if err != nil { + panic(err) + } return node } diff --git a/state/execution_test.go b/state/execution_test.go index 3ebc05d3..2987667c 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -55,13 +55,14 @@ func makeTxs(blockNum int) (txs []types.Tx) { } func state() *State { - return MakeGenesisState(dbm.NewMemDB(), &types.GenesisDoc{ + s, _ := MakeGenesisState(dbm.NewMemDB(), &types.GenesisDoc{ ChainID: chainID, Validators: []types.GenesisValidator{ types.GenesisValidator{privKey.PubKey(), 10000, "test"}, }, AppHash: nil, }) + return s } func makeBlock(num int, state *State) *types.Block { diff --git a/state/state.go b/state/state.go index 2ebbec10..53ec8cc0 100644 --- a/state/state.go +++ b/state/state.go @@ -68,14 +68,17 @@ type State struct { // GetState loads the most recent state from the database, // or creates a new one from the given genesisFile and persists the result // to the database. -func GetState(stateDB dbm.DB, genesisFile string) *State { +func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { + var err error state := LoadState(stateDB) if state == nil { - state = MakeGenesisStateFromFile(stateDB, genesisFile) + state, err = MakeGenesisStateFromFile(stateDB, genesisFile) + if err != nil { + return nil, err + } state.Save() } - - return state + return state, nil } // LoadState loads the State from the database. @@ -316,25 +319,34 @@ func (vi *ValidatorsInfo) Bytes() []byte { // file. // // Used during replay and in tests. -func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) *State { +func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*State, error) { + genDoc, err := MakeGenesisDocFromFile(genDocFile) + if err != nil { + return nil, err + } + return MakeGenesisState(db, genDoc) +} + +// MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file. +func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) { genDocJSON, err := ioutil.ReadFile(genDocFile) if err != nil { - cmn.Exit(cmn.Fmt("Couldn't read GenesisDoc file: %v", err)) + return nil, fmt.Errorf("Couldn't read GenesisDoc file: %v", err) } genDoc, err := types.GenesisDocFromJSON(genDocJSON) if err != nil { - cmn.Exit(cmn.Fmt("Error reading GenesisDoc: %v", err)) + return nil, fmt.Errorf("Error reading GenesisDoc: %v", err) } - return MakeGenesisState(db, genDoc) + return genDoc, nil } // MakeGenesisState creates state from types.GenesisDoc. // // Used in tests. -func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State { +func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { err := genDoc.ValidateAndComplete() if err != nil { - cmn.Exit(cmn.Fmt("Error in genesis file: %v", err)) + return nil, fmt.Errorf("Error in genesis file: %v", err) } // Make validators slice @@ -363,5 +375,5 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State { AppHash: genDoc.AppHash, TxIndexer: &null.TxIndex{}, // we do not need indexer during replay and in tests LastHeightValidatorsChanged: 1, - } + }, nil } diff --git a/state/state_test.go b/state/state_test.go index 4ff367fa..2c19a416 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -23,7 +23,8 @@ import ( func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, *State) { config := cfg.ResetTestRoot("state_") stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir()) - state := GetState(stateDB, config.GenesisFile()) + state, err := GetState(stateDB, config.GenesisFile()) + assert.NoError(t, err, "expected no error on GetState") state.SetLogger(log.TestingLogger()) tearDown := func(t *testing.T) {}