123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- // Copyrgith © 2021 Jeffrey H. Johnson <trnsz@pobox.com>
- // Copyright © 2013-2016 The btcsuite developers
- //
- // Use of this source code is governed by an ISC
- // license that can be found in the LICENSE file.
- package main
- import (
- "fmt"
- "net"
- "net/http"
- _ "net/http/pprof"
- "os"
- "path/filepath"
- "runtime"
- "runtime/debug"
- "runtime/pprof"
- "time"
- "github.com/arl/statsviz"
- "github.com/pkt-cash/pktd/blockchain/indexers"
- "github.com/pkt-cash/pktd/btcutil/er"
- "github.com/pkt-cash/pktd/database"
- "github.com/pkt-cash/pktd/limits"
- "github.com/pkt-cash/pktd/pktconfig/version"
- "github.com/pkt-cash/pktd/pktlog/log"
- pktdLegal "go4.org/legal"
- )
- const (
- // blockDbNamePrefix is the prefix for the block database name. The
- // database type is appended to this value to form the full block
- // database name.
- blockDbNamePrefix = "blocks"
- )
- var cfg *config
- // winServiceMain is only invoked on Windows. It detects when pktd is running
- // as a service and reacts accordingly.
- var winServiceMain func() (bool, er.R)
- // pktdMain is the real main function for pktd. It is necessary to work around
- // the fact that deferred functions do not run when os.Exit() is called. The
- // optional serverChan parameter is mainly used by the service code to be
- // notified with the server once it is setup so it can gracefully stop it when
- // requested from the service control manager.
- func pktdMain(serverChan chan<- *server) er.R {
- // Unconditionally, show version and system information at early startup.
- log.Infof(
- "Version %s\nBuilt with %v %v for %v/%v\n%v logical CPUs, %v goroutines available",
- version.Version(),
- runtime.Compiler,
- runtime.Version(),
- runtime.GOOS,
- runtime.GOARCH,
- runtime.NumCPU(),
- runtime.GOMAXPROCS(-1),
- )
- // Load configuration and parse command line. This function also
- // initializes logging and configures it accordingly.
- tcfg, _, err := loadConfig()
- if err != nil {
- return err
- }
- cfg = tcfg
- // Warn if running a pre-released pktd
- log.WarnIfPrerelease()
- // Warn if not running on a 64-bit architecture
- is64bit := uint64(^uintptr(0)) == ^uint64(0)
- if !is64bit {
- log.Warnf(
- "UNSUPPORTED ARCHITECTURE: ONLY 64-BIT ARCHITECTURES ARE SUPPORTED",
- )
- }
- //
- // Get a channel that will be closed when a shutdown signal has been
- // triggered either from an OS signal such as SIGINT (Ctrl+C) or from
- // another subsystem such as the RPC server.
- interrupt := interruptListener()
- defer log.Info("Shutdown complete")
- // Enable http profiling server if requested.
- if cfg.Profile != "" {
- go func() {
- listenAddr := net.JoinHostPort("", cfg.Profile)
- log.Infof("Profile server listening on %s", listenAddr)
- profileRedirect := http.RedirectHandler("/debug/pprof",
- http.StatusSeeOther)
- http.Handle("/", profileRedirect)
- log.Errorf("%v", http.ListenAndServe(listenAddr, nil))
- }()
- }
- // Write cpu profile if requested.
- if cfg.CPUProfile != "" {
- f, errr := os.Create(cfg.CPUProfile)
- if errr != nil {
- log.Errorf("Unable to create cpu profile: %v", err)
- return er.E(errr)
- }
- if errp := pprof.StartCPUProfile(f); errp != nil {
- log.Errorf("could not start CPU profile: ", errp)
- return er.E(errp)
- }
- defer f.Close()
- defer pprof.StopCPUProfile()
- }
- // Enable StatsViz server if requested.
- if cfg.StatsViz != "" {
- statsvizAddr := net.JoinHostPort("", cfg.StatsViz)
- log.Infof("StatsViz server listening on %s", statsvizAddr)
- smux := http.NewServeMux()
- statsvizRedirect := http.RedirectHandler("/debug/statsviz", http.StatusSeeOther)
- smux.Handle("/", statsvizRedirect)
- if err := statsviz.Register(smux, statsviz.Root("/debug/statsviz")); err != nil {
- log.Errorf("%v", err)
- }
- go func() {
- log.Errorf("%v", http.ListenAndServe(statsvizAddr, smux))
- }()
- }
- // Perform upgrades to pktd as new versions require it.
- runtime.LockOSThread()
- if err := doUpgrades(); err != nil {
- log.Errorf("%v", err)
- runtime.UnlockOSThread()
- return err
- }
- // Return now if an interrupt signal was triggered.
- runtime.UnlockOSThread()
- if interruptRequested(interrupt) {
- runtime.LockOSThread()
- return nil
- }
- // Load the block database.
- runtime.UnlockOSThread()
- db, err := loadBlockDB()
- if err != nil {
- log.Errorf("%v", err)
- return err
- }
- defer func() {
- // Ensure the database is sync'd and closed on shutdown.
- log.Infof("Gracefully shutting down the database...")
- runtime.UnlockOSThread()
- runtime.GC()
- runtime.Gosched()
- db.Close()
- runtime.GC()
- debug.FreeOSMemory()
- }()
- // Return now if an interrupt signal was triggered.
- if interruptRequested(interrupt) {
- runtime.GC()
- debug.FreeOSMemory()
- return nil
- }
- // Drop indexes and exit if requested.
- //
- // NOTE: The order is important here because dropping the tx index also
- // drops the address index since it relies on it.
- if cfg.DropAddrIndex {
- if err := indexers.DropAddrIndex(db, interrupt); err != nil {
- log.Errorf("%v", err)
- return err
- }
- return nil
- }
- if cfg.DropTxIndex {
- if err := indexers.DropTxIndex(db, interrupt); err != nil {
- log.Errorf("%v", err)
- return err
- }
- return nil
- }
- if cfg.DropCfIndex {
- if err := indexers.DropCfIndex(db, interrupt); err != nil {
- log.Errorf("%v", err)
- return err
- }
- return nil
- }
- // Create server and start it.
- server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
- cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
- if err != nil {
- // TODO: this logging could do with some beautifying.
- log.Errorf("Unable to start server on %v: %v",
- cfg.Listeners, err)
- return err
- }
- defer func() {
- // Shut down in 5 minutes, or just pull the plug.
- const shutdownTimeout = 5 * time.Minute
- log.Infof("Preparing for shutdown...")
- runtime.Gosched()
- runtime.GC()
- debug.FreeOSMemory()
- log.Infof("Attempting graceful shutdown (%s timeout, %v active goroutines)...", shutdownTimeout, runtime.NumGoroutine())
- runtime.Gosched()
- server.Stop()
- shutdownDone := make(chan struct{})
- go func() {
- server.WaitForShutdown()
- shutdownDone <- struct{}{}
- }()
- select {
- case <-shutdownDone:
- case <-time.Tick(shutdownTimeout):
- log.Errorf("Graceful shutdown in %s failed - forcefully terminating process in 5s...", shutdownTimeout)
- time.Sleep(3 * time.Second)
- panic("Forcefully terminating the server process...")
- time.Sleep(1 * time.Second)
- runtime.Goexit()
- time.Sleep(1 * time.Second)
- panic("\nCowards die many times before their deaths\nThe valiant never taste of death but once.\n")
- }
- log.Infof("Server shutdown complete")
- }()
- server.Start()
- if serverChan != nil {
- serverChan <- server
- }
- // Wait until the interrupt signal is received from an OS signal or
- // shutdown is requested through one of the subsystems such as the RPC
- // server.
- <-interrupt
- return nil
- }
- // removeRegressionDB removes the existing regression test database if running
- // in regression test mode and it already exists.
- func removeRegressionDB(dbPath string) er.R {
- // Don't do anything if not in regression test mode.
- if !cfg.RegressionTest {
- return nil
- }
- // Remove the old regression test database if it already exists.
- fi, err := os.Stat(dbPath)
- if err == nil {
- log.Infof("Removing regression test database from '%s'", dbPath)
- if fi.IsDir() {
- errr := os.RemoveAll(dbPath)
- if errr != nil {
- return er.E(errr)
- }
- } else {
- errr := os.Remove(dbPath)
- if errr != nil {
- return er.E(errr)
- }
- }
- }
- return nil
- }
- // dbPath returns the path to the block database given a database type.
- func blockDbPath(dbType string) string {
- // The database name is based on the database type.
- dbName := blockDbNamePrefix + "_" + dbType
- if dbType == "sqlite" {
- dbName = dbName + ".db"
- }
- dbPath := filepath.Join(cfg.DataDir, dbName)
- return dbPath
- }
- // warnMultipleDBs shows a warning if multiple block database types are detected.
- // This is not a situation most users want. It is handy for development however
- // to support multiple side-by-side databases.
- func warnMultipleDBs() {
- // This is intentionally not using the known db types which depend
- // on the database types compiled into the binary since we want to
- // detect legacy db types as well.
- dbTypes := []string{"ffldb", "leveldb", "sqlite"}
- duplicateDbPaths := make([]string, 0, len(dbTypes)-1)
- for _, dbType := range dbTypes {
- if dbType == cfg.DbType {
- continue
- }
- // Store db path as a duplicate db if it exists.
- dbPath := blockDbPath(dbType)
- if fileExists(dbPath) {
- duplicateDbPaths = append(duplicateDbPaths, dbPath)
- }
- }
- // Warn if there are extra databases.
- if len(duplicateDbPaths) > 0 {
- selectedDbPath := blockDbPath(cfg.DbType)
- log.Warnf("WARNING: There are multiple block chain databases "+
- "using different database types.\nYou probably don't "+
- "want to waste disk space by having more than one.\n"+
- "Your current database is located at [%v].\nThe "+
- "additional database is located at %v", selectedDbPath,
- duplicateDbPaths)
- }
- }
- // loadBlockDB loads (or creates when needed) the block database taking into
- // account the selected database backend and returns a handle to it. It also
- // contains additional logic such warning the user if there are multiple
- // databases which consume space on the file system and ensuring the regression
- // test database is clean when in regression test mode.
- func loadBlockDB() (database.DB, er.R) {
- // The memdb backend does not have a file path associated with it, so
- // handle it uniquely. We also don't want to worry about the multiple
- // database type warnings when running with the memory database.
- if cfg.DbType == "memdb" {
- log.Infof("Creating block database in memory.")
- db, err := database.Create(cfg.DbType)
- if err != nil {
- return nil, err
- }
- return db, nil
- }
- warnMultipleDBs()
- // The database name is based on the database type.
- dbPath := blockDbPath(cfg.DbType)
- // The regression test is special in that it needs a clean database for
- // each run, so remove it now if it already exists.
- removeRegressionDB(dbPath)
- log.Infof("Loading block database from '%s'", dbPath)
- db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
- if err != nil {
- // Return the error if it's not because the database doesn't
- // exist.
- if !database.ErrDbDoesNotExist.Is(err) {
- return nil, err
- }
- // Create the db if it does not exist.
- errr := os.MkdirAll(cfg.DataDir, 0o700)
- if errr != nil {
- return nil, er.E(errr)
- }
- db, err = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
- if err != nil {
- return nil, err
- }
- }
- log.Info("Block database loaded")
- return db, nil
- }
- func main() {
- version.SetUserAgentName("pktd")
- runtime.GOMAXPROCS(runtime.NumCPU() * 6)
- // Block and transaction processing can cause bursty allocations. This
- // limits the garbage collector from excessively over-allocating during
- // bursts. This value was arrived at with the help of profiling live
- // usage.
- debug.SetGCPercent(10)
- // Up some limits.
- if err := limits.SetLimits(); err != nil {
- fmt.Fprintf(os.Stderr, "failed to set limits: %v\n", err)
- os.Exit(1)
- }
- // Call serviceMain on Windows to handle running as a service. When
- // the return isService flag is true, exit now since we ran as a
- // service. Otherwise, just fall through to normal operation.
- if runtime.GOOS == "windows" {
- isService, err := winServiceMain()
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- if isService {
- os.Exit(0)
- }
- }
- // Work around defer not working after os.Exit()
- if err := pktdMain(nil); err != nil {
- os.Exit(1)
- panic("time to die...")
- }
- }
- func init() {
- // Clean slate
- debug.FreeOSMemory()
- debug.SetPanicOnFault(false)
- debug.SetTraceback("all")
- // Register licensing
- pktdLegal.RegisterLicense(
- "\nISC License\n\nCopyright © 2021 Anode LLC.\nCopyright © 2019-2021 Caleb James DeLisle.\nCopyright © 2021 Gridfinity, LLC.\nCopyright © 2021 Jeffrey H. Johnson. <trnsz@pobox.com>\nCopyright © 2021 Filippo Valsorda.\nCopyright © 2020 Frank Denis <j at pureftpd dot org>.\nCopyright © 2020 The Go Authors.\nCopyright © 2015-2021 Lightning Labs and The Lightning Network Developers.\nCopyright © 2015-2018 Lightning Labs.\nCopyright © 2013-2017 The btcsuite developers.\nCopyright © 2016-2017 The Lightning Network Developers.\nCopyright © 2015-2016 The Decred developers.\nCopyright © 2015 Google, Inc.\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n",
- )
- }
|