Skip to content
Snippets Groups Projects
serve.go 8.37 KiB
Newer Older
// Copyright 2017 Monax Industries Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Benjamin Bollen's avatar
Benjamin Bollen committed

package commands

import (
Silas Davis's avatar
Silas Davis committed
	"fmt"
Silas Davis's avatar
Silas Davis committed
	"os"
Silas Davis's avatar
Silas Davis committed
	"path"
	"github.com/monax/eris-db/core"
	"github.com/monax/eris-db/definitions"
	"github.com/monax/eris-db/logging"
	"github.com/monax/eris-db/logging/lifecycle"
	"github.com/monax/eris-db/util"
Silas Davis's avatar
Silas Davis committed
	vm "github.com/monax/eris-db/manager/eris-mint/evm"
const (
	DefaultConfigBasename = "config"
	DefaultConfigType     = "toml"
)

var DefaultConfigFilename = fmt.Sprintf("%s.%s",
	DefaultConfigBasename,
	DefaultConfigType)

Silas Davis's avatar
Silas Davis committed
// build the serve subcommand
func buildServeCommand(do *definitions.Do) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "serve",
		Short: "Eris-DB serve starts an eris-db node with client API enabled by default.",
		Long: `Eris-DB serve starts an eris-db node with client API enabled by default.
The Eris-DB node is modularly configured for the consensus engine and application
manager.  The client API can be disabled.`,
Silas Davis's avatar
Silas Davis committed
		Example: fmt.Sprintf(`$ eris-db serve -- will start the Eris-DB node based on the configuration file "%s" in the current working directory
$ eris-db serve --work-dir <path-to-working-directory> -- will start the Eris-DB node based on the configuration file "%s" in the provided working directory
$ eris-db serve --chain-id <CHAIN_ID> -- will overrule the configuration entry assert_chain_id`,
Silas Davis's avatar
Silas Davis committed
			DefaultConfigFilename, DefaultConfigFilename),
		PreRun: func(cmd *cobra.Command, args []string) {
			// if WorkDir was not set by a flag or by $ERIS_DB_WORKDIR
			// NOTE [ben]: we can consider an `Explicit` flag that eliminates
			// the use of any assumptions while starting Eris-DB
			if do.WorkDir == "" {
				if currentDirectory, err := os.Getwd(); err != nil {
					panic(fmt.Sprintf("No directory provided and failed to get current "+
						"working directory: %v", err))
					os.Exit(1)
				} else {
					do.WorkDir = currentDirectory
				}
			}
			if !util.IsDir(do.WorkDir) {
				panic(fmt.Sprintf("Provided working directory %s is not a directory",
					do.WorkDir))
Silas Davis's avatar
Silas Davis committed
				os.Exit(1)
			}
Silas Davis's avatar
Silas Davis committed
		},
		Run: ServeRunner(do),
	}
	addServeFlags(do, cmd)
	return cmd
Silas Davis's avatar
Silas Davis committed
func addServeFlags(do *definitions.Do, serveCmd *cobra.Command) {
	serveCmd.PersistentFlags().StringVarP(&do.ChainId, "chain-id", "c",
		defaultChainId(), "specify the chain id to use for assertion against the genesis file or the existing state. If omitted, and no id is set in $CHAIN_ID, then assert_chain_id is used from the configuration file.")
Silas Davis's avatar
Silas Davis committed
	serveCmd.PersistentFlags().StringVarP(&do.WorkDir, "work-dir", "w",
Silas Davis's avatar
Silas Davis committed
		defaultWorkDir(), "specify the working directory for the chain to run.  If omitted, and no path set in $ERIS_DB_WORKDIR, the current working directory is taken.")
Silas Davis's avatar
Silas Davis committed
	serveCmd.PersistentFlags().StringVarP(&do.DataDir, "data-dir", "",
Silas Davis's avatar
Silas Davis committed
		defaultDataDir(), "specify the data directory.  If omitted and not set in $ERIS_DB_DATADIR, <working_directory>/data is taken.")
Silas Davis's avatar
Silas Davis committed
	serveCmd.PersistentFlags().BoolVarP(&do.DisableRpc, "disable-rpc", "",
		defaultDisableRpc(), "indicate for the RPC to be disabled. If omitted the RPC is enabled by default, unless (deprecated) $ERISDB_API is set to false.")
}

//------------------------------------------------------------------------------
// functions
Silas Davis's avatar
Silas Davis committed
func NewCoreFromDo(do *definitions.Do) (*core.Core, error) {
Silas Davis's avatar
Silas Davis committed
	// load the genesis file path
	do.GenesisFile = path.Join(do.WorkDir,
		do.Config.GetString("chain.genesis_file"))
Silas Davis's avatar
Silas Davis committed

Silas Davis's avatar
Silas Davis committed
	if do.Config.GetString("chain.genesis_file") == "" {
Silas Davis's avatar
Silas Davis committed
		return nil, fmt.Errorf("The config value chain.genesis_file is empty, " +
Silas Davis's avatar
Silas Davis committed
			"but should be set to the location of the genesis.json file.")
Silas Davis's avatar
Silas Davis committed
	}
	// Ensure data directory is set and accessible
	if err := do.InitialiseDataDirectory(); err != nil {
Silas Davis's avatar
Silas Davis committed
		return nil, fmt.Errorf("Failed to initialise data directory (%s): %v", do.DataDir, err)
Silas Davis's avatar
Silas Davis committed
	loggerConfig, err := core.LoadLoggingConfigFromDo(do)
Silas Davis's avatar
Silas Davis committed
	if err != nil {
Silas Davis's avatar
Silas Davis committed
		return nil, fmt.Errorf("Failed to load logging config: %s", err)
Silas Davis's avatar
Silas Davis committed
	// Create a root logger to pass through to dependencies
	logger := logging.WithScope(lifecycle.NewLoggerFromLoggingConfig(loggerConfig), "Serve")
Silas Davis's avatar
Silas Davis committed
	// Capture all logging from tendermint/tendermint and tendermint/go-*
	// dependencies
	lifecycle.CaptureTendermintLog15Output(logger)
Silas Davis's avatar
Silas Davis committed
	// And from stdlib go log
	lifecycle.CaptureStdlibLogOutput(logger)
Silas Davis's avatar
Silas Davis committed
	// if do.ChainId is not yet set, load chain_id for assertion from configuration file

	if do.ChainId == "" {
		if do.ChainId = do.Config.GetString("chain.assert_chain_id"); do.ChainId == "" {
			return nil, fmt.Errorf("The config chain.assert_chain_id is empty, " +
Silas Davis's avatar
Silas Davis committed
				"but should be set to the chain_id of the chain we are trying to run.")
	logging.Msg(logger, "Loading configuration for serve command",
		"chainId", do.ChainId,
Silas Davis's avatar
Silas Davis committed
		"workingDirectory", do.WorkDir,
		"dataDirectory", do.DataDir,
		"genesisFile", do.GenesisFile)
Silas Davis's avatar
Silas Davis committed

	consensusConfig, err := core.LoadConsensusModuleConfig(do)
Silas Davis's avatar
Silas Davis committed
	if err != nil {
Silas Davis's avatar
Silas Davis committed
		return nil, fmt.Errorf("Failed to load consensus module configuration: %s.", err)
Silas Davis's avatar
Silas Davis committed
	managerConfig, err := core.LoadApplicationManagerModuleConfig(do)
Silas Davis's avatar
Silas Davis committed
	if err != nil {
Silas Davis's avatar
Silas Davis committed
		return nil, fmt.Errorf("Failed to load application manager module configuration: %s.", err)
	logging.Msg(logger, "Modules configured",
		"consensusModule", consensusConfig.Version,
		"applicationManager", managerConfig.Version)
Silas Davis's avatar
Silas Davis committed
	return core.NewCore(do.ChainId, consensusConfig, managerConfig, logger)
}

// ServeRunner() returns a command runner that prepares the environment and sets
// up the core for Eris-DB to run. After the setup succeeds, it starts the core
// and waits for the core to terminate.
func ServeRunner(do *definitions.Do) func(*cobra.Command, []string) {
	return func(cmd *cobra.Command, args []string) {
		// load configuration from a single location to avoid a wrong configuration
		// file is loaded.
		err := do.ReadConfig(do.WorkDir, DefaultConfigBasename, DefaultConfigType)
Silas Davis's avatar
Silas Davis committed
			util.Fatalf("Fatal error reading configuration from %s/%s", do.WorkDir,
				DefaultConfigFilename)
Silas Davis's avatar
Silas Davis committed

Silas Davis's avatar
Silas Davis committed
		vm.SetDebug(do.Debug)

Silas Davis's avatar
Silas Davis committed
		newCore, err := NewCoreFromDo(do)

Silas Davis's avatar
Silas Davis committed
			util.Fatalf("Failed to load core: %s", err)
Silas Davis's avatar
Silas Davis committed

		if !do.DisableRpc {
			serverConfig, err := core.LoadServerConfig(do)
			if err != nil {
				util.Fatalf("Failed to load server configuration: %s.", err)
			}
			serverProcess, err := newCore.NewGatewayV0(serverConfig)
			if err != nil {
				util.Fatalf("Failed to load servers: %s.", err)
			}
			err = serverProcess.Start()
			if err != nil {
				util.Fatalf("Failed to start servers: %s.", err)
			}
			_, err = newCore.NewGatewayTendermint(serverConfig)
			if err != nil {
				util.Fatalf("Failed to start Tendermint gateway")
			}
			<-serverProcess.StopEventChannel()
		} else {
			signals := make(chan os.Signal, 1)
			signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
			fmt.Fprintf(os.Stderr, "Received %s signal. Marmots out.", <-signals)

//------------------------------------------------------------------------------
// Defaults
func defaultChainId() string {
	// if CHAIN_ID environment variable is not set, keep do.ChainId empty to read
	// assert_chain_id from configuration file
	return setDefaultString("CHAIN_ID", "")
}

func defaultWorkDir() string {
Silas Davis's avatar
Silas Davis committed
	// if ERIS_DB_WORKDIR environment variable is not set, keep do.WorkDir empty
	// as do.WorkDir is set by the PreRun
Silas Davis's avatar
Silas Davis committed
	return setDefaultString("ERIS_DB_WORKDIR", "")
func defaultDataDir() string {
Silas Davis's avatar
Silas Davis committed
	// As the default data directory depends on the default working directory,
	// wait setting a default value, and initialise the data directory from serve()
	return setDefaultString("ERIS_DB_DATADIR", "")

func defaultDisableRpc() bool {
	// we currently observe environment variable ERISDB_API (true = enable)
	// and default to enabling the RPC if it is not set.
	// TODO: [ben] deprecate ERISDB_API across the stack for 0.12.1, and only disable
	// the rpc through a command line flag --disable-rpc
	return !setDefaultBool("ERISDB_API", true)
}