2013-08-06 23:55:22 +02:00
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"fmt"
2013-09-15 22:21:46 +02:00
"github.com/conformal/btcdb"
2013-09-13 21:52:34 +02:00
_ "github.com/conformal/btcdb/ldb"
2013-11-11 00:12:45 +01:00
"github.com/conformal/btcutil"
2013-08-06 23:55:22 +02:00
"github.com/conformal/btcwire"
2013-08-07 07:33:46 +02:00
"github.com/conformal/go-flags"
2013-08-06 23:55:22 +02:00
"net"
"os"
"path/filepath"
2013-11-22 00:03:01 +01:00
"sort"
2013-09-17 23:40:27 +02:00
"strconv"
2013-08-08 01:53:01 +02:00
"strings"
2013-08-06 23:55:22 +02:00
"time"
)
const (
defaultConfigFilename = "btcd.conf"
2013-11-11 00:12:45 +01:00
defaultDataDirname = "data"
2013-08-06 23:55:22 +02:00
defaultLogLevel = "info"
defaultBtcnet = btcwire . MainNet
2013-10-14 22:56:39 +02:00
defaultMaxPeers = 125
2013-08-06 23:55:22 +02:00
defaultBanDuration = time . Hour * 24
defaultVerifyEnabled = false
2013-10-08 22:00:34 +02:00
defaultDbType = "leveldb"
2013-08-06 23:55:22 +02:00
)
var (
2013-11-19 18:37:22 +01:00
btcdHomeDir = btcutil . AppDataDir ( "btcd" , false )
defaultConfigFile = filepath . Join ( btcdHomeDir , defaultConfigFilename )
defaultDataDir = filepath . Join ( btcdHomeDir , defaultDataDirname )
defaultListener = net . JoinHostPort ( "" , netParams ( defaultBtcnet ) . listenPort )
knownDbTypes = btcdb . SupportedDBs ( )
defaultRPCKeyFile = filepath . Join ( btcdHomeDir , "rpc.key" )
defaultRPCCertFile = filepath . Join ( btcdHomeDir , "rpc.cert" )
2013-08-06 23:55:22 +02:00
)
// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
2013-10-10 02:34:02 +02:00
ShowVersion bool ` short:"V" long:"version" description:"Display version information and exit" `
ConfigFile string ` short:"C" long:"configfile" description:"Path to configuration file" `
DataDir string ` short:"b" long:"datadir" description:"Directory to store data" `
AddPeers [ ] string ` short:"a" long:"addpeer" description:"Add a peer to connect with at startup" `
ConnectPeers [ ] string ` long:"connect" description:"Connect only to the specified peers at startup" `
2013-11-18 17:38:24 +01:00
DisableListen bool ` long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect or --proxy options are used without also specifying listen interfaces via --listen" `
2013-11-19 17:39:38 +01:00
Listeners [ ] string ` long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 8333, testnet: 18333)" `
2013-10-10 02:34:02 +02:00
MaxPeers int ` long:"maxpeers" description:"Max number of inbound and outbound peers" `
BanDuration time . Duration ` long:"banduration" description:"How long to ban misbehaving peers. Valid time units are { s, m, h}. Minimum 1 second" `
RPCUser string ` short:"u" long:"rpcuser" description:"Username for RPC connections" `
2013-11-15 03:29:46 +01:00
RPCPass string ` short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections" `
2013-11-19 17:39:38 +01:00
RPCListeners [ ] string ` long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)" `
RPCCert string ` long:"rpccert" description:"File containing the certificate file" `
RPCKey string ` long:"rpckey" description:"File containing the certificate key" `
2013-10-10 02:34:02 +02:00
DisableRPC bool ` long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified" `
DisableDNSSeed bool ` long:"nodnsseed" description:"Disable DNS seeding for peers" `
Proxy string ` long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)" `
ProxyUser string ` long:"proxyuser" description:"Username for proxy server" `
2013-11-15 03:29:46 +01:00
ProxyPass string ` long:"proxypass" default-mask:"-" description:"Password for proxy server" `
2013-10-10 02:34:02 +02:00
UseTor bool ` long:"tor" description:"Specifies the proxy server used is a Tor node" `
TestNet3 bool ` long:"testnet" description:"Use the test network" `
RegressionTest bool ` long:"regtest" description:"Use the regression test network" `
DisableCheckpoints bool ` long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing." `
DbType string ` long:"dbtype" description:"Database backend to use for the Block Chain" `
Profile string ` long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536" `
2013-10-26 23:12:54 +02:00
CpuProfile string ` long:"cpuprofile" description:"Write CPU profile to the specified file" `
2013-11-22 00:03:01 +01:00
DebugLevel string ` short:"d" long:"debuglevel" description:"Logging level for all subsystems { trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems" `
2013-08-06 23:55:22 +02:00
}
2013-09-18 07:13:36 +02:00
// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath ( path string ) string {
// Expand initial ~ to OS specific home directory.
if strings . HasPrefix ( path , "~" ) {
2013-11-11 00:12:45 +01:00
homeDir := filepath . Dir ( btcdHomeDir )
2013-09-18 07:13:36 +02:00
path = strings . Replace ( path , "~" , homeDir , 1 )
}
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
// but they variables can still be expanded via POSIX-style $VARIABLE.
return filepath . Clean ( os . ExpandEnv ( path ) )
}
2013-08-06 23:55:22 +02:00
// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel ( logLevel string ) bool {
switch logLevel {
case "trace" :
fallthrough
case "debug" :
fallthrough
case "info" :
fallthrough
case "warn" :
fallthrough
case "error" :
fallthrough
case "critical" :
return true
}
return false
}
2013-11-22 00:03:01 +01:00
// supportedSubsystems returns a sorted slice of the supported subsystems for
// logging purposes.
func supportedSubsystems ( ) [ ] string {
// Convert the subsystemLoggers map keys to a slice.
subsystems := make ( [ ] string , 0 , len ( subsystemLoggers ) )
for subsysID := range subsystemLoggers {
subsystems = append ( subsystems , subsysID )
}
// Sort the subsytems for stable display.
sort . Strings ( subsystems )
return subsystems
}
2013-11-22 17:46:56 +01:00
// parseAndSetDebugLevels attempts to parse the specified debug level and set
// the levels accordingly. An appropriate error is returned if anything is
// invalid.
func parseAndSetDebugLevels ( debugLevel string ) error {
2013-11-22 00:03:01 +01:00
// When the specified string doesn't have any delimters, treat it as
// the log level for all subsystems.
if ! strings . Contains ( debugLevel , "," ) && ! strings . Contains ( debugLevel , "=" ) {
// Validate debug log level.
if ! validLogLevel ( debugLevel ) {
str := "The specified debug level [%v] is invalid"
return fmt . Errorf ( str , debugLevel )
}
2013-11-22 19:44:49 +01:00
// Change the logging level for all subsystems.
setLogLevels ( debugLevel )
2013-11-22 00:03:01 +01:00
return nil
}
// Split the specified string into subsystem/level pairs while detecting
// issues and update the log levels accordingly.
for _ , logLevelPair := range strings . Split ( debugLevel , "," ) {
if ! strings . Contains ( logLevelPair , "=" ) {
str := "The specified debug level contains an invalid " +
"subsystem/level pair [%v]"
return fmt . Errorf ( str , logLevelPair )
}
// Extract the specified subsystem and log level.
fields := strings . Split ( logLevelPair , "=" )
subsysID , logLevel := fields [ 0 ] , fields [ 1 ]
// Validate subsystem.
if _ , exists := subsystemLoggers [ subsysID ] ; ! exists {
2013-11-22 17:46:56 +01:00
str := "The specified subsystem [%v] is invalid -- " +
"supported subsytems %v"
return fmt . Errorf ( str , subsysID , supportedSubsystems ( ) )
2013-11-22 00:03:01 +01:00
}
// Validate log level.
if ! validLogLevel ( logLevel ) {
str := "The specified debug level [%v] is invalid"
return fmt . Errorf ( str , logLevel )
}
setLogLevel ( subsysID , logLevel )
}
return nil
}
2013-09-15 19:08:42 +02:00
// validDbType returns whether or not dbType is a supported database type.
func validDbType ( dbType string ) bool {
for _ , knownType := range knownDbTypes {
if dbType == knownType {
return true
}
}
return false
}
2013-08-06 23:55:22 +02:00
// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses ( addrs [ ] string ) [ ] string {
result := make ( [ ] string , 0 )
seen := map [ string ] bool { }
for _ , val := range addrs {
if _ , ok := seen [ val ] ; ! ok {
result = append ( result , val )
seen [ val ] = true
}
}
return result
}
2013-09-19 17:46:33 +02:00
// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress ( addr , defaultPort string ) string {
_ , _ , err := net . SplitHostPort ( addr )
if err != nil {
return net . JoinHostPort ( addr , defaultPort )
}
return addr
}
// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses ( addrs [ ] string , defaultPort string ) [ ] string {
2013-08-06 23:55:22 +02:00
for i , addr := range addrs {
2013-09-19 17:46:33 +02:00
addrs [ i ] = normalizeAddress ( addr , defaultPort )
2013-08-06 23:55:22 +02:00
}
2013-09-19 17:46:33 +02:00
return removeDuplicateAddresses ( addrs )
}
2013-08-06 23:55:22 +02:00
// filesExists reports whether the named file or directory exists.
func fileExists ( name string ) bool {
if _ , err := os . Stat ( name ) ; err != nil {
if os . IsNotExist ( err ) {
return false
}
}
return true
}
// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 1) Start with a default config with sane settings
// 2) Pre-parse the command line to check for an alternative config file
// 3) Load configuration file overwriting defaults with any specified options
// 4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options. Command line options always take precedence.
func loadConfig ( ) ( * config , [ ] string , error ) {
// Default config.
cfg := config {
DebugLevel : defaultLogLevel ,
MaxPeers : defaultMaxPeers ,
BanDuration : defaultBanDuration ,
ConfigFile : defaultConfigFile ,
2013-09-14 01:02:10 +02:00
DataDir : defaultDataDir ,
2013-09-15 19:08:42 +02:00
DbType : defaultDbType ,
2013-11-19 18:37:22 +01:00
RPCKey : defaultRPCKeyFile ,
RPCCert : defaultRPCCertFile ,
2013-08-06 23:55:22 +02:00
}
2013-11-19 22:40:20 +01:00
// Create the home directory if it doesn't already exist.
err := os . MkdirAll ( btcdHomeDir , 0700 )
if err != nil {
2013-11-21 19:03:56 +01:00
btcdLog . Errorf ( "%v" , err )
2013-11-19 22:56:21 +01:00
return nil , nil , err
2013-11-19 22:40:20 +01:00
}
2013-08-06 23:55:22 +02:00
// Pre-parse the command line options to see if an alternative config
2013-11-15 03:29:46 +01:00
// file or the version flag was specified. Any errors can be ignored
// here since they will be caught be the final parse below.
2013-08-06 23:55:22 +02:00
preCfg := cfg
2013-11-15 03:29:46 +01:00
preParser := flags . NewParser ( & preCfg , flags . None )
preParser . Parse ( )
2013-08-06 23:55:22 +02:00
2013-08-08 01:53:01 +02:00
// Show the version and exit if the version flag was specified.
if preCfg . ShowVersion {
appName := filepath . Base ( os . Args [ 0 ] )
appName = strings . TrimSuffix ( appName , filepath . Ext ( appName ) )
fmt . Println ( appName , "version" , version ( ) )
os . Exit ( 0 )
}
2013-08-06 23:55:22 +02:00
// Load additional config from file.
2013-11-24 19:33:36 +01:00
var configFileError error
2013-08-06 23:55:22 +02:00
parser := flags . NewParser ( & cfg , flags . Default )
2013-11-15 21:42:53 +01:00
if ! preCfg . RegressionTest || preCfg . ConfigFile != defaultConfigFile {
2013-11-24 19:33:36 +01:00
err := flags . NewIniParser ( parser ) . ParseFile ( preCfg . ConfigFile )
2013-11-15 21:42:53 +01:00
if err != nil {
if _ , ok := err . ( * os . PathError ) ; ! ok {
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-11-24 19:33:36 +01:00
configFileError = err
2013-08-06 23:55:22 +02:00
}
}
2013-08-17 21:44:43 +02:00
// Don't add peers from the config file when in regression test mode.
if preCfg . RegressionTest && len ( cfg . AddPeers ) > 0 {
cfg . AddPeers = nil
}
2013-08-06 23:55:22 +02:00
// Parse command line options again to ensure they take precedence.
remainingArgs , err := parser . Parse ( )
if err != nil {
if e , ok := err . ( * flags . Error ) ; ! ok || e . Type != flags . ErrHelp {
parser . WriteHelp ( os . Stderr )
}
return nil , nil , err
}
2013-11-24 19:33:36 +01:00
// Warn about missing config file after the final command line parse
// succeeds. This prevents the warning on help messages and invalid
// options.
if configFileError != nil {
btcdLog . Warnf ( "%v" , configFileError )
}
2013-08-06 23:55:22 +02:00
// The two test networks can't be selected simultaneously.
if cfg . TestNet3 && cfg . RegressionTest {
str := "%s: The testnet and regtest params can't be used " +
"together -- choose one of the two"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" )
2013-08-06 23:55:22 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
// Choose the active network params based on the testnet and regression
// test net flags.
if cfg . TestNet3 {
activeNetParams = netParams ( btcwire . TestNet3 )
} else if cfg . RegressionTest {
activeNetParams = netParams ( btcwire . TestNet )
}
2013-11-22 17:46:56 +01:00
// Special show command to list supported subsystems and exit.
if cfg . DebugLevel == "show" {
fmt . Println ( "Supported subsystems" , supportedSubsystems ( ) )
os . Exit ( 0 )
}
2013-11-22 00:03:01 +01:00
// Parse, validate, and set debug log level(s).
2013-11-22 17:46:56 +01:00
if err := parseAndSetDebugLevels ( cfg . DebugLevel ) ; err != nil {
2013-11-22 00:03:01 +01:00
err := fmt . Errorf ( "%s: %v" , "loadConfig" , err . Error ( ) )
2013-08-06 23:55:22 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-09-15 19:08:42 +02:00
// Validate database type.
if ! validDbType ( cfg . DbType ) {
str := "%s: The specified database type [%v] is invalid -- " +
"supported types %v"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" , cfg . DbType , knownDbTypes )
2013-09-15 19:08:42 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-09-17 23:40:27 +02:00
// Validate profile port number
2013-09-17 23:46:03 +02:00
if cfg . Profile != "" {
2013-09-18 00:04:40 +02:00
profilePort , err := strconv . Atoi ( cfg . Profile )
if err != nil || profilePort < 1024 || profilePort > 65535 {
str := "%s: The profile port must be between 1024 and 65535"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" )
2013-09-18 00:04:40 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-09-17 23:40:27 +02:00
}
2013-09-15 19:08:42 +02:00
// Append the network type to the data directory so it is "namespaced"
// per network. In addition to the block database, there are other
// pieces of data that are saved to disk such as address manager state.
// All data is specific to a network, so namespacing the data directory
// means each individual piece of serialized data does not have to
// worry about changing names per network and such.
2013-09-18 07:13:36 +02:00
cfg . DataDir = cleanAndExpandPath ( cfg . DataDir )
2013-09-15 19:08:42 +02:00
cfg . DataDir = filepath . Join ( cfg . DataDir , activeNetParams . netName )
2013-08-06 23:55:22 +02:00
// Don't allow ban durations that are too short.
if cfg . BanDuration < time . Duration ( time . Second ) {
str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" , cfg . BanDuration )
2013-08-06 23:55:22 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
// --addPeer and --connect do not mix.
if len ( cfg . AddPeers ) > 0 && len ( cfg . ConnectPeers ) > 0 {
str := "%s: the --addpeer and --connect options can not be " +
"mixed"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" )
2013-08-06 23:55:22 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-08-08 18:11:39 +02:00
// --tor requires --proxy to be set.
if cfg . UseTor && cfg . Proxy == "" {
str := "%s: the --tor option requires --proxy to be set"
2013-09-18 07:36:40 +02:00
err := fmt . Errorf ( str , "loadConfig" )
2013-08-08 18:11:39 +02:00
fmt . Fprintln ( os . Stderr , err )
parser . WriteHelp ( os . Stderr )
return nil , nil , err
}
2013-09-19 17:46:33 +02:00
// --proxy or --connect without --listen disables listening.
if ( cfg . Proxy != "" || len ( cfg . ConnectPeers ) > 0 ) &&
len ( cfg . Listeners ) == 0 {
2013-08-08 18:11:39 +02:00
cfg . DisableListen = true
}
2013-09-19 17:46:33 +02:00
// Connect means no DNS seeding.
2013-08-06 23:55:22 +02:00
if len ( cfg . ConnectPeers ) > 0 {
cfg . DisableDNSSeed = true
2013-09-19 17:46:33 +02:00
}
// Add the default listener if none were specified. The default
// listener is all addresses on the listen port for the network
// we are to connect to.
if len ( cfg . Listeners ) == 0 {
cfg . Listeners = [ ] string {
net . JoinHostPort ( "" , activeNetParams . listenPort ) ,
}
2013-08-06 23:55:22 +02:00
}
2013-08-07 18:02:31 +02:00
// The RPC server is disabled if no username or password is provided.
2013-09-18 07:36:40 +02:00
if cfg . RPCUser == "" || cfg . RPCPass == "" {
cfg . DisableRPC = true
2013-08-07 18:02:31 +02:00
}
2013-11-20 00:33:07 +01:00
// Default RPC to listen on localhost only.
if ! cfg . DisableRPC && len ( cfg . RPCListeners ) == 0 {
addrs , err := net . LookupHost ( "localhost" )
if err != nil {
return nil , nil , err
2013-11-14 02:51:37 +01:00
}
2013-11-20 00:33:07 +01:00
cfg . RPCListeners = make ( [ ] string , 0 , len ( addrs ) )
for _ , addr := range addrs {
addr = net . JoinHostPort ( addr , activeNetParams . rpcPort )
cfg . RPCListeners = append ( cfg . RPCListeners , addr )
}
2013-11-14 02:51:37 +01:00
}
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg . Listeners = normalizeAddresses ( cfg . Listeners ,
activeNetParams . listenPort )
// Add default port to all rpc listener addresses if needed and remove
2013-09-19 17:46:33 +02:00
// duplicate addresses.
2013-11-14 02:51:37 +01:00
cfg . RPCListeners = normalizeAddresses ( cfg . RPCListeners ,
activeNetParams . rpcPort )
2013-09-19 17:46:33 +02:00
2013-08-06 23:55:22 +02:00
// Add default port to all added peer addresses if needed and remove
// duplicate addresses.
2013-11-14 02:51:37 +01:00
cfg . AddPeers = normalizeAddresses ( cfg . AddPeers ,
activeNetParams . peerPort )
cfg . ConnectPeers = normalizeAddresses ( cfg . ConnectPeers ,
activeNetParams . peerPort )
2013-08-06 23:55:22 +02:00
return & cfg , remainingArgs , nil
}