github.com/lbryio/lbcd@v0.22.119/lbcd.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/lbryio/lbcd/blockchain/indexers"
    19  	"github.com/lbryio/lbcd/claimtrie/param"
    20  	"github.com/lbryio/lbcd/database"
    21  	"github.com/lbryio/lbcd/limits"
    22  	"github.com/lbryio/lbcd/version"
    23  
    24  	"github.com/felixge/fgprof"
    25  )
    26  
    27  const (
    28  	// blockDbNamePrefix is the prefix for the block database name.  The
    29  	// database type is appended to this value to form the full block
    30  	// database name.
    31  	blockDbNamePrefix = "blocks"
    32  )
    33  
    34  var (
    35  	cfg *config
    36  )
    37  
    38  // winServiceMain is only invoked on Windows.  It detects when btcd is running
    39  // as a service and reacts accordingly.
    40  var winServiceMain func() (bool, error)
    41  
    42  // btcdMain is the real main function for btcd.  It is necessary to work around
    43  // the fact that deferred functions do not run when os.Exit() is called.  The
    44  // optional serverChan parameter is mainly used by the service code to be
    45  // notified with the server once it is setup so it can gracefully stop it when
    46  // requested from the service control manager.
    47  func btcdMain(serverChan chan<- *server) error {
    48  	// Load configuration and parse command line.  This function also
    49  	// initializes logging and configures it accordingly.
    50  	tcfg, _, err := loadConfig()
    51  	if err != nil {
    52  		return err
    53  	}
    54  	cfg = tcfg
    55  	defer func() {
    56  		if logRotator != nil {
    57  			logRotator.Close()
    58  		}
    59  	}()
    60  
    61  	// Get a channel that will be closed when a shutdown signal has been
    62  	// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
    63  	// another subsystem such as the RPC server.
    64  	interrupt := interruptListener()
    65  	defer btcdLog.Info("Shutdown complete")
    66  
    67  	// Show version at startup.
    68  	btcdLog.Infof("Version %s", version.Full())
    69  
    70  	// Enable http profiling server if requested.
    71  	if cfg.Profile != "" {
    72  		http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
    73  		go func() {
    74  			listenAddr := net.JoinHostPort("", cfg.Profile)
    75  			btcdLog.Infof("Profile server listening on %s", listenAddr)
    76  			profileRedirect := http.RedirectHandler("/debug/pprof",
    77  				http.StatusSeeOther)
    78  			http.Handle("/", profileRedirect)
    79  			btcdLog.Errorf("%v", http.ListenAndServe(listenAddr, nil))
    80  		}()
    81  	}
    82  
    83  	// Write cpu profile if requested.
    84  	if cfg.CPUProfile != "" {
    85  		f, err := os.Create(cfg.CPUProfile)
    86  		if err != nil {
    87  			btcdLog.Errorf("Unable to create cpu profile: %v", err)
    88  			return err
    89  		}
    90  		pprof.StartCPUProfile(f)
    91  		defer f.Close()
    92  		defer pprof.StopCPUProfile()
    93  	}
    94  
    95  	// Write memory profile if requested.
    96  	if cfg.MemProfile != "" {
    97  		f, err := os.Create(cfg.MemProfile + ".heap")
    98  		if err != nil {
    99  			btcdLog.Errorf("Unable to create mem profile: %v", err)
   100  			return err
   101  		}
   102  		defer f.Close()
   103  		defer pprof.Lookup("heap").WriteTo(f, 0)
   104  
   105  		f, err = os.Create(cfg.MemProfile + ".allocs")
   106  		if err != nil {
   107  			btcdLog.Errorf("Unable to create mem profile: %v", err)
   108  			return err
   109  		}
   110  		defer f.Close()
   111  		defer pprof.Lookup("allocs").WriteTo(f, 0)
   112  	}
   113  
   114  	// Perform upgrades to btcd as new versions require it.
   115  	if err := doUpgrades(); err != nil {
   116  		btcdLog.Errorf("%v", err)
   117  		return err
   118  	}
   119  
   120  	// Return now if an interrupt signal was triggered.
   121  	if interruptRequested(interrupt) {
   122  		return nil
   123  	}
   124  
   125  	// Load the block database.
   126  	db, err := loadBlockDB()
   127  	if err != nil {
   128  		btcdLog.Errorf("%v", err)
   129  		return err
   130  	}
   131  	defer func() {
   132  		// Ensure the database is sync'd and closed on shutdown.
   133  		btcdLog.Infof("Gracefully shutting down the database...")
   134  		db.Close()
   135  	}()
   136  
   137  	// Return now if an interrupt signal was triggered.
   138  	if interruptRequested(interrupt) {
   139  		return nil
   140  	}
   141  
   142  	// Drop indexes and exit if requested.
   143  	//
   144  	// NOTE: The order is important here because dropping the tx index also
   145  	// drops the address index since it relies on it.
   146  	if cfg.DropAddrIndex {
   147  		if err := indexers.DropAddrIndex(db, interrupt); err != nil {
   148  			btcdLog.Errorf("%v", err)
   149  			return err
   150  		}
   151  
   152  		return nil
   153  	}
   154  	if cfg.DropTxIndex {
   155  		if err := indexers.DropTxIndex(db, interrupt); err != nil {
   156  			btcdLog.Errorf("%v", err)
   157  			return err
   158  		}
   159  
   160  		return nil
   161  	}
   162  	if cfg.DropCfIndex {
   163  		if err := indexers.DropCfIndex(db, interrupt); err != nil {
   164  			btcdLog.Errorf("%v", err)
   165  			return err
   166  		}
   167  
   168  		return nil
   169  	}
   170  
   171  	param.SetNetwork(activeNetParams.Params.Net) // prep the claimtrie params
   172  
   173  	go logMemoryUsage()
   174  
   175  	// Create server and start it.
   176  	server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
   177  		cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
   178  	if err != nil {
   179  		// TODO: this logging could do with some beautifying.
   180  		btcdLog.Errorf("Unable to start server on %v: %v",
   181  			cfg.Listeners, err)
   182  		return err
   183  	}
   184  	defer func() {
   185  		btcdLog.Infof("Gracefully shutting down the server...")
   186  		server.Stop()
   187  		server.WaitForShutdown()
   188  		srvrLog.Infof("Server shutdown complete")
   189  		// TODO: tie into the sync manager for shutdown instead
   190  		if ct := server.chain.ClaimTrie(); ct != nil {
   191  			ct.Close()
   192  		}
   193  	}()
   194  	server.Start()
   195  	if serverChan != nil {
   196  		serverChan <- server
   197  	}
   198  
   199  	// Wait until the interrupt signal is received from an OS signal or
   200  	// shutdown is requested through one of the subsystems such as the RPC
   201  	// server.
   202  	<-interrupt
   203  	return nil
   204  }
   205  
   206  // dbPath returns the path to the block database given a database type.
   207  func blockDbPath(dbType string) string {
   208  	// The database name is based on the database type.
   209  	dbName := blockDbNamePrefix + "_" + dbType
   210  	if dbType == "sqlite" {
   211  		dbName = dbName + ".db"
   212  	}
   213  	dbPath := filepath.Join(cfg.DataDir, dbName)
   214  	return dbPath
   215  }
   216  
   217  // warnMultipleDBs shows a warning if multiple block database types are detected.
   218  // This is not a situation most users want.  It is handy for development however
   219  // to support multiple side-by-side databases.
   220  func warnMultipleDBs() {
   221  	// This is intentionally not using the known db types which depend
   222  	// on the database types compiled into the binary since we want to
   223  	// detect legacy db types as well.
   224  	dbTypes := []string{"ffldb", "leveldb", "sqlite"}
   225  	duplicateDbPaths := make([]string, 0, len(dbTypes)-1)
   226  	for _, dbType := range dbTypes {
   227  		if dbType == cfg.DbType {
   228  			continue
   229  		}
   230  
   231  		// Store db path as a duplicate db if it exists.
   232  		dbPath := blockDbPath(dbType)
   233  		if fileExists(dbPath) {
   234  			duplicateDbPaths = append(duplicateDbPaths, dbPath)
   235  		}
   236  	}
   237  
   238  	// Warn if there are extra databases.
   239  	if len(duplicateDbPaths) > 0 {
   240  		selectedDbPath := blockDbPath(cfg.DbType)
   241  		btcdLog.Warnf("WARNING: There are multiple block chain databases "+
   242  			"using different database types.\nYou probably don't "+
   243  			"want to waste disk space by having more than one.\n"+
   244  			"Your current database is located at [%v].\nThe "+
   245  			"additional database is located at %v", selectedDbPath,
   246  			duplicateDbPaths)
   247  	}
   248  }
   249  
   250  // loadBlockDB loads (or creates when needed) the block database taking into
   251  // account the selected database backend and returns a handle to it.  It also
   252  // contains additional logic such warning the user if there are multiple
   253  // databases which consume space on the file system and ensuring the regression
   254  // test database is clean when in regression test mode.
   255  func loadBlockDB() (database.DB, error) {
   256  	// The memdb backend does not have a file path associated with it, so
   257  	// handle it uniquely.  We also don't want to worry about the multiple
   258  	// database type warnings when running with the memory database.
   259  	if cfg.DbType == "memdb" {
   260  		btcdLog.Infof("Creating block database in memory.")
   261  		db, err := database.Create(cfg.DbType)
   262  		if err != nil {
   263  			return nil, err
   264  		}
   265  		return db, nil
   266  	}
   267  
   268  	warnMultipleDBs()
   269  
   270  	// The database name is based on the database type.
   271  	dbPath := blockDbPath(cfg.DbType)
   272  	btcdLog.Infof("Loading block database from '%s'", dbPath)
   273  	db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
   274  	if err != nil {
   275  		// Return the error if it's not because the database doesn't
   276  		// exist.
   277  		if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
   278  			database.ErrDbDoesNotExist {
   279  
   280  			return nil, err
   281  		}
   282  
   283  		// Create the db if it does not exist.
   284  		err = os.MkdirAll(cfg.DataDir, 0700)
   285  		if err != nil {
   286  			return nil, err
   287  		}
   288  		db, err = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
   289  		if err != nil {
   290  			return nil, err
   291  		}
   292  	}
   293  
   294  	btcdLog.Info("Block database loaded")
   295  	return db, nil
   296  }
   297  
   298  func main() {
   299  	// Block and transaction processing can cause bursty allocations.  This
   300  	// limits the garbage collector from excessively overallocating during
   301  	// bursts.  This value was arrived at with the help of profiling live
   302  	// usage.
   303  	if _, ok := os.LookupEnv("GOGC"); !ok {
   304  		debug.SetGCPercent(10)
   305  	}
   306  
   307  	// Up some limits.
   308  	if err := limits.SetLimits(); err != nil {
   309  		fmt.Fprintf(os.Stderr, "failed to set limits: %v\n", err)
   310  		os.Exit(1)
   311  	}
   312  
   313  	// Call serviceMain on Windows to handle running as a service.  When
   314  	// the return isService flag is true, exit now since we ran as a
   315  	// service.  Otherwise, just fall through to normal operation.
   316  	if runtime.GOOS == "windows" {
   317  		isService, err := winServiceMain()
   318  		if err != nil {
   319  			fmt.Println(err)
   320  			os.Exit(1)
   321  		}
   322  		if isService {
   323  			os.Exit(0)
   324  		}
   325  	}
   326  
   327  	// Work around defer not working after os.Exit()
   328  	if err := btcdMain(nil); err != nil {
   329  		os.Exit(1)
   330  	}
   331  }