github.com/btcsuite/btcd@v0.24.0/btcd.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	_ "net/http/pprof"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"runtime/debug"
    16  	"runtime/pprof"
    17  
    18  	"github.com/btcsuite/btcd/blockchain/indexers"
    19  	"github.com/btcsuite/btcd/database"
    20  	"github.com/btcsuite/btcd/limits"
    21  	"github.com/btcsuite/btcd/ossec"
    22  )
    23  
    24  const (
    25  	// blockDbNamePrefix is the prefix for the block database name.  The
    26  	// database type is appended to this value to form the full block
    27  	// database name.
    28  	blockDbNamePrefix = "blocks"
    29  )
    30  
    31  var (
    32  	cfg *config
    33  )
    34  
    35  // winServiceMain is only invoked on Windows.  It detects when btcd is running
    36  // as a service and reacts accordingly.
    37  var winServiceMain func() (bool, error)
    38  
    39  // btcdMain is the real main function for btcd.  It is necessary to work around
    40  // the fact that deferred functions do not run when os.Exit() is called.  The
    41  // optional serverChan parameter is mainly used by the service code to be
    42  // notified with the server once it is setup so it can gracefully stop it when
    43  // requested from the service control manager.
    44  func btcdMain(serverChan chan<- *server) error {
    45  	// Load configuration and parse command line.  This function also
    46  	// initializes logging and configures it accordingly.
    47  	tcfg, _, err := loadConfig()
    48  	if err != nil {
    49  		return err
    50  	}
    51  	cfg = tcfg
    52  	defer func() {
    53  		if logRotator != nil {
    54  			logRotator.Close()
    55  		}
    56  	}()
    57  
    58  	// Get a channel that will be closed when a shutdown signal has been
    59  	// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
    60  	// another subsystem such as the RPC server.
    61  	interrupt := interruptListener()
    62  	defer btcdLog.Info("Shutdown complete")
    63  
    64  	// Show version at startup.
    65  	btcdLog.Infof("Version %s", version())
    66  
    67  	// Enable http profiling server if requested.
    68  	if cfg.Profile != "" {
    69  		go func() {
    70  			listenAddr := net.JoinHostPort("", cfg.Profile)
    71  			btcdLog.Infof("Profile server listening on %s", listenAddr)
    72  			profileRedirect := http.RedirectHandler("/debug/pprof",
    73  				http.StatusSeeOther)
    74  			http.Handle("/", profileRedirect)
    75  			btcdLog.Errorf("%v", http.ListenAndServe(listenAddr, nil))
    76  		}()
    77  	}
    78  
    79  	// Write cpu profile if requested.
    80  	if cfg.CPUProfile != "" {
    81  		f, err := os.Create(cfg.CPUProfile)
    82  		if err != nil {
    83  			btcdLog.Errorf("Unable to create cpu profile: %v", err)
    84  			return err
    85  		}
    86  		pprof.StartCPUProfile(f)
    87  		defer f.Close()
    88  		defer pprof.StopCPUProfile()
    89  	}
    90  
    91  	// Write mem profile if requested.
    92  	if cfg.MemoryProfile != "" {
    93  		f, err := os.Create(cfg.MemoryProfile)
    94  		if err != nil {
    95  			btcdLog.Errorf("Unable to create memory profile: %v", err)
    96  			return err
    97  		}
    98  		defer f.Close()
    99  		defer pprof.WriteHeapProfile(f)
   100  		defer runtime.GC()
   101  	}
   102  
   103  	// Perform upgrades to btcd as new versions require it.
   104  	if err := doUpgrades(); err != nil {
   105  		btcdLog.Errorf("%v", err)
   106  		return err
   107  	}
   108  
   109  	// Return now if an interrupt signal was triggered.
   110  	if interruptRequested(interrupt) {
   111  		return nil
   112  	}
   113  
   114  	// Load the block database.
   115  	db, err := loadBlockDB()
   116  	if err != nil {
   117  		btcdLog.Errorf("%v", err)
   118  		return err
   119  	}
   120  	defer func() {
   121  		// Ensure the database is sync'd and closed on shutdown.
   122  		btcdLog.Infof("Gracefully shutting down the database...")
   123  		db.Close()
   124  	}()
   125  
   126  	// Return now if an interrupt signal was triggered.
   127  	if interruptRequested(interrupt) {
   128  		return nil
   129  	}
   130  
   131  	// Drop indexes and exit if requested.
   132  	//
   133  	// NOTE: The order is important here because dropping the tx index also
   134  	// drops the address index since it relies on it.
   135  	if cfg.DropAddrIndex {
   136  		if err := indexers.DropAddrIndex(db, interrupt); err != nil {
   137  			btcdLog.Errorf("%v", err)
   138  			return err
   139  		}
   140  
   141  		return nil
   142  	}
   143  	if cfg.DropTxIndex {
   144  		if err := indexers.DropTxIndex(db, interrupt); err != nil {
   145  			btcdLog.Errorf("%v", err)
   146  			return err
   147  		}
   148  
   149  		return nil
   150  	}
   151  	if cfg.DropCfIndex {
   152  		if err := indexers.DropCfIndex(db, interrupt); err != nil {
   153  			btcdLog.Errorf("%v", err)
   154  			return err
   155  		}
   156  
   157  		return nil
   158  	}
   159  
   160  	// Check if the database had previously been pruned.  If it had been, it's
   161  	// not possible to newly generate the tx index and addr index.
   162  	var beenPruned bool
   163  	db.View(func(dbTx database.Tx) error {
   164  		beenPruned, err = dbTx.BeenPruned()
   165  		return err
   166  	})
   167  	if err != nil {
   168  		btcdLog.Errorf("%v", err)
   169  		return err
   170  	}
   171  	if beenPruned && cfg.Prune == 0 {
   172  		err = fmt.Errorf("--prune cannot be disabled as the node has been "+
   173  			"previously pruned. You must delete the files in the datadir: \"%s\" "+
   174  			"and sync from the beginning to disable pruning", cfg.DataDir)
   175  		btcdLog.Errorf("%v", err)
   176  		return err
   177  	}
   178  	if beenPruned && cfg.TxIndex {
   179  		err = fmt.Errorf("--txindex cannot be enabled as the node has been "+
   180  			"previously pruned. You must delete the files in the datadir: \"%s\" "+
   181  			"and sync from the beginning to enable the desired index", cfg.DataDir)
   182  		btcdLog.Errorf("%v", err)
   183  		return err
   184  	}
   185  	if beenPruned && cfg.AddrIndex {
   186  		err = fmt.Errorf("--addrindex cannot be enabled as the node has been "+
   187  			"previously pruned. You must delete the files in the datadir: \"%s\" "+
   188  			"and sync from the beginning to enable the desired index", cfg.DataDir)
   189  		btcdLog.Errorf("%v", err)
   190  		return err
   191  	}
   192  	// If we've previously been pruned and the cfindex isn't present, it means that the
   193  	// user wants to enable the cfindex after the node has already synced up and been
   194  	// pruned.
   195  	if beenPruned && !indexers.CfIndexInitialized(db) && !cfg.NoCFilters {
   196  		err = fmt.Errorf("compact filters cannot be enabled as the node has been "+
   197  			"previously pruned. You must delete the files in the datadir: \"%s\" "+
   198  			"and sync from the beginning to enable the desired index. You may "+
   199  			"use the --nocfilters flag to start the node up without the compact "+
   200  			"filters", cfg.DataDir)
   201  		btcdLog.Errorf("%v", err)
   202  		return err
   203  	}
   204  	// If the user wants to disable the cfindex and is pruned or has enabled pruning, force
   205  	// the user to either drop the cfindex manually or restart the node without the --nocfilters
   206  	// flag.
   207  	if (beenPruned || cfg.Prune != 0) && indexers.CfIndexInitialized(db) && cfg.NoCFilters {
   208  		err = fmt.Errorf("--nocfilters flag was given but the compact filters have " +
   209  			"previously been enabled on this node and the index data currently " +
   210  			"exists in the database. The node has also been previously pruned and " +
   211  			"the database would be left in an inconsistent state if the compact " +
   212  			"filters don't get indexed now. To disable compact filters, please drop the " +
   213  			"index completely with the --dropcfindex flag and restart the node. " +
   214  			"To keep the compact filters, restart the node without the --nocfilters " +
   215  			"flag")
   216  		btcdLog.Errorf("%v", err)
   217  		return err
   218  	}
   219  
   220  	// Enforce removal of txindex and addrindex if user requested pruning.
   221  	// This is to require explicit action from the user before removing
   222  	// indexes that won't be useful when block files are pruned.
   223  	//
   224  	// NOTE: The order is important here because dropping the tx index also
   225  	// drops the address index since it relies on it.  We explicitly make the
   226  	// user drop both indexes if --addrindex was enabled previously.
   227  	if cfg.Prune != 0 && indexers.AddrIndexInitialized(db) {
   228  		err = fmt.Errorf("--prune flag may not be given when the address index " +
   229  			"has been initialized. Please drop the address index with the " +
   230  			"--dropaddrindex flag before enabling pruning")
   231  		btcdLog.Errorf("%v", err)
   232  		return err
   233  	}
   234  	if cfg.Prune != 0 && indexers.TxIndexInitialized(db) {
   235  		err = fmt.Errorf("--prune flag may not be given when the transaction index " +
   236  			"has been initialized. Please drop the transaction index with the " +
   237  			"--droptxindex flag before enabling pruning")
   238  		btcdLog.Errorf("%v", err)
   239  		return err
   240  	}
   241  
   242  	// The config file is already created if it did not exist and the log
   243  	// file has already been opened by now so we only need to allow
   244  	// creating rpc cert and key files if they don't exist.
   245  	unveilx(cfg.RPCKey, "rwc")
   246  	unveilx(cfg.RPCCert, "rwc")
   247  	unveilx(cfg.DataDir, "rwc")
   248  
   249  	// drop unveil and tty
   250  	pledgex("stdio rpath wpath cpath flock dns inet")
   251  
   252  	// Create server and start it.
   253  	server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
   254  		cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
   255  	if err != nil {
   256  		// TODO: this logging could do with some beautifying.
   257  		btcdLog.Errorf("Unable to start server on %v: %v",
   258  			cfg.Listeners, err)
   259  		return err
   260  	}
   261  	defer func() {
   262  		btcdLog.Infof("Gracefully shutting down the server...")
   263  		server.Stop()
   264  		server.WaitForShutdown()
   265  		srvrLog.Infof("Server shutdown complete")
   266  	}()
   267  	server.Start()
   268  	if serverChan != nil {
   269  		serverChan <- server
   270  	}
   271  
   272  	// Wait until the interrupt signal is received from an OS signal or
   273  	// shutdown is requested through one of the subsystems such as the RPC
   274  	// server.
   275  	<-interrupt
   276  	return nil
   277  }
   278  
   279  // removeRegressionDB removes the existing regression test database if running
   280  // in regression test mode and it already exists.
   281  func removeRegressionDB(dbPath string) error {
   282  	// Don't do anything if not in regression test mode.
   283  	if !cfg.RegressionTest {
   284  		return nil
   285  	}
   286  
   287  	// Remove the old regression test database if it already exists.
   288  	fi, err := os.Stat(dbPath)
   289  	if err == nil {
   290  		btcdLog.Infof("Removing regression test database from '%s'", dbPath)
   291  		if fi.IsDir() {
   292  			err := os.RemoveAll(dbPath)
   293  			if err != nil {
   294  				return err
   295  			}
   296  		} else {
   297  			err := os.Remove(dbPath)
   298  			if err != nil {
   299  				return err
   300  			}
   301  		}
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  // dbPath returns the path to the block database given a database type.
   308  func blockDbPath(dbType string) string {
   309  	// The database name is based on the database type.
   310  	dbName := blockDbNamePrefix + "_" + dbType
   311  	if dbType == "sqlite" {
   312  		dbName = dbName + ".db"
   313  	}
   314  	dbPath := filepath.Join(cfg.DataDir, dbName)
   315  	return dbPath
   316  }
   317  
   318  // warnMultipleDBs shows a warning if multiple block database types are detected.
   319  // This is not a situation most users want.  It is handy for development however
   320  // to support multiple side-by-side databases.
   321  func warnMultipleDBs() {
   322  	// This is intentionally not using the known db types which depend
   323  	// on the database types compiled into the binary since we want to
   324  	// detect legacy db types as well.
   325  	dbTypes := []string{"ffldb", "leveldb", "sqlite"}
   326  	duplicateDbPaths := make([]string, 0, len(dbTypes)-1)
   327  	for _, dbType := range dbTypes {
   328  		if dbType == cfg.DbType {
   329  			continue
   330  		}
   331  
   332  		// Store db path as a duplicate db if it exists.
   333  		dbPath := blockDbPath(dbType)
   334  		if fileExists(dbPath) {
   335  			duplicateDbPaths = append(duplicateDbPaths, dbPath)
   336  		}
   337  	}
   338  
   339  	// Warn if there are extra databases.
   340  	if len(duplicateDbPaths) > 0 {
   341  		selectedDbPath := blockDbPath(cfg.DbType)
   342  		btcdLog.Warnf("WARNING: There are multiple block chain databases "+
   343  			"using different database types.\nYou probably don't "+
   344  			"want to waste disk space by having more than one.\n"+
   345  			"Your current database is located at [%v].\nThe "+
   346  			"additional database is located at %v", selectedDbPath,
   347  			duplicateDbPaths)
   348  	}
   349  }
   350  
   351  // loadBlockDB loads (or creates when needed) the block database taking into
   352  // account the selected database backend and returns a handle to it.  It also
   353  // contains additional logic such warning the user if there are multiple
   354  // databases which consume space on the file system and ensuring the regression
   355  // test database is clean when in regression test mode.
   356  func loadBlockDB() (database.DB, error) {
   357  	// The memdb backend does not have a file path associated with it, so
   358  	// handle it uniquely.  We also don't want to worry about the multiple
   359  	// database type warnings when running with the memory database.
   360  	if cfg.DbType == "memdb" {
   361  		btcdLog.Infof("Creating block database in memory.")
   362  		db, err := database.Create(cfg.DbType)
   363  		if err != nil {
   364  			return nil, err
   365  		}
   366  		return db, nil
   367  	}
   368  
   369  	warnMultipleDBs()
   370  
   371  	// The database name is based on the database type.
   372  	dbPath := blockDbPath(cfg.DbType)
   373  
   374  	// The regression test is special in that it needs a clean database for
   375  	// each run, so remove it now if it already exists.
   376  	removeRegressionDB(dbPath)
   377  
   378  	btcdLog.Infof("Loading block database from '%s'", dbPath)
   379  	db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
   380  	if err != nil {
   381  		// Return the error if it's not because the database doesn't
   382  		// exist.
   383  		if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
   384  			database.ErrDbDoesNotExist {
   385  
   386  			return nil, err
   387  		}
   388  
   389  		// Create the db if it does not exist.
   390  		err = os.MkdirAll(cfg.DataDir, 0700)
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  		db, err = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  	}
   399  
   400  	btcdLog.Info("Block database loaded")
   401  	return db, nil
   402  }
   403  
   404  func unveilx(path string, perms string) {
   405  	err := ossec.Unveil(path, perms)
   406  	if err != nil {
   407  		fmt.Fprintf(os.Stderr, "unveil failed: %v\n", err)
   408  		os.Exit(1)
   409  	}
   410  }
   411  
   412  func pledgex(promises string) {
   413  	err := ossec.PledgePromises(promises)
   414  	if err != nil {
   415  		fmt.Fprintf(os.Stderr, "pledge failed: %v\n", err)
   416  		os.Exit(1)
   417  	}
   418  }
   419  
   420  func init() {
   421  	pledgex("unveil stdio id rpath wpath cpath flock dns inet tty")
   422  }
   423  
   424  func main() {
   425  	// If GOGC is not explicitly set, override GC percent.
   426  	if os.Getenv("GOGC") == "" {
   427  		// Block and transaction processing can cause bursty allocations.  This
   428  		// limits the garbage collector from excessively overallocating during
   429  		// bursts.  This value was arrived at with the help of profiling live
   430  		// usage.
   431  		debug.SetGCPercent(10)
   432  	}
   433  
   434  	// Up some limits.
   435  	if err := limits.SetLimits(); err != nil {
   436  		fmt.Fprintf(os.Stderr, "failed to set limits: %v\n", err)
   437  		os.Exit(1)
   438  	}
   439  
   440  	// Call serviceMain on Windows to handle running as a service.  When
   441  	// the return isService flag is true, exit now since we ran as a
   442  	// service.  Otherwise, just fall through to normal operation.
   443  	if runtime.GOOS == "windows" {
   444  		isService, err := winServiceMain()
   445  		if err != nil {
   446  			fmt.Println(err)
   447  			os.Exit(1)
   448  		}
   449  		if isService {
   450  			os.Exit(0)
   451  		}
   452  	}
   453  
   454  	// Work around defer not working after os.Exit()
   455  	if err := btcdMain(nil); err != nil {
   456  		os.Exit(1)
   457  	}
   458  }