noded.go raw

   1  /*Package node is a full-node Parallelcoin implementation written in Go.
   2  
   3  The default options are sane for most users. This means pod will work 'out of the box' for most users. However, there
   4  are also a wide variety of flags that can be used to control it.
   5  
   6  The following section provides a usage overview which enumerates the flags. An interesting point to note is that the
   7  long form of all of these options ( except -C/--configfile and -D --datadir) can be specified in a configuration file
   8  that is automatically parsed when pod starts up. By default, the configuration file is located at ~/.pod/pod. conf on
   9  POSIX-style operating systems and %LOCALAPPDATA%\pod\pod. conf on Windows. The -D (--datadir) flag, can be used to
  10  override this location.
  11  
  12  NAME:
  13     pod node - start parallelcoin full node
  14  
  15  USAGE:
  16     pod node [global options] command [command options] [arguments...]
  17  
  18  VERSION:
  19     v0.0.1
  20  
  21  COMMANDS:
  22       dropaddrindex  drop the address search index
  23       droptxindex    drop the address search index
  24       dropcfindex    drop the address search index
  25  
  26  GLOBAL OPTIONS:
  27     --help, -h  show help
  28  */
  29  package node
  30  
  31  import (
  32  	"io"
  33  	"net"
  34  	"net/http"
  35  	"os"
  36  	"path/filepath"
  37  	"runtime/pprof"
  38  
  39  	"github.com/p9c/p9/pkg/qu"
  40  
  41  	"github.com/p9c/p9/pkg/interrupt"
  42  
  43  	"github.com/p9c/p9/pkg/apputil"
  44  	"github.com/p9c/p9/pkg/chainrpc"
  45  	"github.com/p9c/p9/pkg/constant"
  46  	"github.com/p9c/p9/pkg/ctrl"
  47  	"github.com/p9c/p9/pkg/database"
  48  	"github.com/p9c/p9/pkg/database/blockdb"
  49  	"github.com/p9c/p9/pkg/indexers"
  50  	"github.com/p9c/p9/pkg/log"
  51  	"github.com/p9c/p9/pod/state"
  52  )
  53  
  54  // // This enables pprof
  55  // _ "net/http/pprof"
  56  
  57  // winServiceMain is only invoked on Windows. It detects when pod is running as a service and reacts accordingly.
  58  var winServiceMain func() (bool, error)
  59  
  60  // NodeMain is the real main function for pod.
  61  //
  62  // The optional serverChan parameter is mainly used by the service code to be notified with the server once it is setup
  63  // so it can gracefully stop it when requested from the service control manager.
  64  func NodeMain(cx *state.State) (e error) {
  65  	T.Ln("starting up node main")
  66  	// cx.WaitGroup.Add(1)
  67  	cx.WaitAdd()
  68  	// enable http profiling server if requested
  69  	if cx.Config.Profile.V() != "" {
  70  		D.Ln("profiling requested")
  71  		go func() {
  72  			listenAddr := net.JoinHostPort("", cx.Config.Profile.V())
  73  			I.Ln("profile server listening on", listenAddr)
  74  			profileRedirect := http.RedirectHandler("/debug/pprof", http.StatusSeeOther)
  75  			http.Handle("/", profileRedirect)
  76  			D.Ln("profile server", http.ListenAndServe(listenAddr, nil))
  77  		}()
  78  	}
  79  	// write cpu profile if requested
  80  	if cx.Config.CPUProfile.V() != "" && os.Getenv("POD_TRACE") != "on" {
  81  		D.Ln("cpu profiling enabled")
  82  		var f *os.File
  83  		f, e = os.Create(cx.Config.CPUProfile.V())
  84  		if e != nil {
  85  			E.Ln("unable to create cpu profile:", e)
  86  			return
  87  		}
  88  		e = pprof.StartCPUProfile(f)
  89  		if e != nil {
  90  			D.Ln("failed to start up cpu profiler:", e)
  91  		} else {
  92  			defer func() {
  93  				if e = f.Close(); E.Chk(e) {
  94  				}
  95  			}()
  96  			defer pprof.StopCPUProfile()
  97  			interrupt.AddHandler(
  98  				func() {
  99  					D.Ln("stopping CPU profiler")
 100  					e = f.Close()
 101  					if e != nil {
 102  					}
 103  					pprof.StopCPUProfile()
 104  					D.Ln("finished cpu profiling", *cx.Config.CPUProfile)
 105  				},
 106  			)
 107  		}
 108  	}
 109  	// perform upgrades to pod as new versions require it
 110  	if e = doUpgrades(cx); E.Chk(e) {
 111  		return
 112  	}
 113  	// return now if an interrupt signal was triggered
 114  	if interrupt.Requested() {
 115  		return nil
 116  	}
 117  	// load the block database
 118  	var db database.DB
 119  	db, e = loadBlockDB(cx)
 120  	if e != nil {
 121  		return
 122  	}
 123  	closeDb := func() {
 124  		// ensure the database is synced and closed on shutdown
 125  		T.Ln("gracefully shutting down the database")
 126  		func() {
 127  			if e = db.Close(); E.Chk(e) {
 128  			}
 129  		}()
 130  	}
 131  	defer closeDb()
 132  	interrupt.AddHandler(closeDb)
 133  	// return now if an interrupt signal was triggered
 134  	if interrupt.Requested() {
 135  		return nil
 136  	}
 137  	// drop indexes and exit if requested.
 138  	//
 139  	// NOTE: The order is important here because dropping the tx index also drops the address index since it relies on
 140  	// it
 141  	if cx.StateCfg.DropAddrIndex {
 142  		W.Ln("dropping address index")
 143  		if e = indexers.DropAddrIndex(db, interrupt.ShutdownRequestChan); E.Chk(e) {
 144  			return
 145  		}
 146  	}
 147  	if cx.StateCfg.DropTxIndex {
 148  		W.Ln("dropping transaction index")
 149  		if e = indexers.DropTxIndex(db, interrupt.ShutdownRequestChan); E.Chk(e) {
 150  			return
 151  		}
 152  	}
 153  	if cx.StateCfg.DropCfIndex {
 154  		W.Ln("dropping cfilter index")
 155  		if e = indexers.DropCfIndex(db, interrupt.ShutdownRequestChan); E.Chk(e) {
 156  			return
 157  		}
 158  	}
 159  	// return now if an interrupt signal was triggered
 160  	if interrupt.Requested() {
 161  		return nil
 162  	}
 163  	mempoolUpdateChan := qu.Ts(1)
 164  	mempoolUpdateHook := func() {
 165  		mempoolUpdateChan.Signal()
 166  	}
 167  	// create server and start it
 168  	var server *chainrpc.Node
 169  	server, e = chainrpc.NewNode(
 170  		cx.Config.P2PListeners.S(),
 171  		db,
 172  		interrupt.ShutdownRequestChan,
 173  		state.GetContext(cx),
 174  		mempoolUpdateHook,
 175  	)
 176  	if e != nil {
 177  		E.F("unable to start server on %v: %v", cx.Config.P2PListeners.S(), e)
 178  		return e
 179  	}
 180  	server.Start()
 181  	cx.RealNode = server
 182  	// if len(server.RPCServers) > 0 && *cx.Config.CAPI {
 183  	// 	D.Ln("starting cAPI.....")
 184  	// 	// chainrpc.RunAPI(server.RPCServers[0], cx.NodeKill)
 185  	// 	// D.Ln("propagating rpc server handle (node has started)")
 186  	// }
 187  	// I.S(server.RPCServers)
 188  	if len(server.RPCServers) > 0 {
 189  		cx.RPCServer = server.RPCServers[0]
 190  		D.Ln("sending back node")
 191  		cx.NodeChan <- cx.RPCServer
 192  	}
 193  	D.Ln("starting controller")
 194  	cx.Controller, e = ctrl.New(
 195  		cx.Syncing,
 196  		cx.Config,
 197  		cx.StateCfg,
 198  		cx.RealNode,
 199  		cx.RPCServer.Cfg.ConnMgr,
 200  		mempoolUpdateChan,
 201  		uint64(cx.Config.UUID.V()),
 202  		cx.KillAll,
 203  		cx.RealNode.StartController, cx.RealNode.StopController,
 204  	)
 205  	go cx.Controller.Run()
 206  	cx.Controller.Start()
 207  	D.Ln("controller started")
 208  	once := true
 209  	gracefulShutdown := func() {
 210  		if !once {
 211  			return
 212  		}
 213  		if once {
 214  			once = false
 215  		}
 216  		D.Ln("gracefully shutting down the server...")
 217  		D.Ln("stopping controller")
 218  		cx.Controller.Shutdown()
 219  		D.Ln("stopping server")
 220  		e := server.Stop()
 221  		if e != nil {
 222  			W.Ln("failed to stop server", e)
 223  		}
 224  		server.WaitForShutdown()
 225  		I.Ln("server shutdown complete")
 226  		log.LogChanDisabled.Store(true)
 227  		cx.WaitDone()
 228  		cx.KillAll.Q()
 229  		cx.NodeKill.Q()
 230  	}
 231  	D.Ln("adding interrupt handler for node")
 232  	interrupt.AddHandler(gracefulShutdown)
 233  	// Wait until the interrupt signal is received from an OS signal or shutdown is requested through one of the
 234  	// subsystems such as the RPC server.
 235  	select {
 236  	case <-cx.NodeKill.Wait():
 237  		D.Ln("NodeKill")
 238  		if !interrupt.Requested() {
 239  			interrupt.Request()
 240  		}
 241  		break
 242  	case <-cx.KillAll.Wait():
 243  		D.Ln("KillAll")
 244  		if !interrupt.Requested() {
 245  			interrupt.Request()
 246  		}
 247  		break
 248  	}
 249  	gracefulShutdown()
 250  	return nil
 251  }
 252  
 253  // loadBlockDB loads (or creates when needed) the block database taking into account the selected database backend and
 254  // returns a handle to it. It also additional logic such warning the user if there are multiple databases which consume
 255  // space on the file system and ensuring the regression test database is clean when in regression test mode.
 256  func loadBlockDB(cx *state.State) (db database.DB, e error) {
 257  	// The memdb backend does not have a file path associated with it, so handle it uniquely. We also don't want to
 258  	// worry about the multiple database type warnings when running with the memory database.
 259  	if cx.Config.DbType.V() == "memdb" {
 260  		I.Ln("creating block database in memory")
 261  		if db, e = database.Create(cx.Config.DbType.V()); state.E.Chk(e) {
 262  			return nil, e
 263  		}
 264  		return db, nil
 265  	}
 266  	warnMultipleDBs(cx)
 267  	// The database name is based on the database type.
 268  	dbPath := state.BlockDb(cx, cx.Config.DbType.V(), blockdb.NamePrefix)
 269  	// The regression test is special in that it needs a clean database for each
 270  	// run, so remove it now if it already exists.
 271  	e = removeRegressionDB(cx, dbPath)
 272  	if e != nil {
 273  		D.Ln("failed to remove regression db:", e)
 274  	}
 275  	I.F("loading block database from '%s'", dbPath)
 276  	I.Ln(database.SupportedDrivers())
 277  	if db, e = database.Open(cx.Config.DbType.V(), dbPath, cx.ActiveNet.Net); E.Chk(e) {
 278  		T.Ln(e) // return the error if it's not because the database doesn't exist
 279  		if dbErr, ok := e.(database.DBError); !ok || dbErr.ErrorCode !=
 280  			database.ErrDbDoesNotExist {
 281  			return nil, e
 282  		}
 283  		// create the db if it does not exist
 284  		e = os.MkdirAll(cx.Config.DataDir.V(), 0700)
 285  		if e != nil {
 286  			return nil, e
 287  		}
 288  		db, e = database.Create(cx.Config.DbType.V(), dbPath, cx.ActiveNet.Net)
 289  		if e != nil {
 290  			return nil, e
 291  		}
 292  	}
 293  	T.Ln("block database loaded")
 294  	return db, nil
 295  }
 296  
 297  // removeRegressionDB removes the existing regression test database if running
 298  // in regression test mode and it already exists.
 299  func removeRegressionDB(cx *state.State, dbPath string) (e error) {
 300  	// don't do anything if not in regression test mode
 301  	if !((cx.Config.Network.V())[0] == 'r') {
 302  		return nil
 303  	}
 304  	// remove the old regression test database if it already exists
 305  	fi, e := os.Stat(dbPath)
 306  	if e == nil {
 307  		I.F("removing regression test database from '%s' %s", dbPath)
 308  		if fi.IsDir() {
 309  			if e = os.RemoveAll(dbPath); E.Chk(e) {
 310  				return e
 311  			}
 312  		} else {
 313  			if e = os.Remove(dbPath); E.Chk(e) {
 314  				return e
 315  			}
 316  		}
 317  	}
 318  	return nil
 319  }
 320  
 321  // warnMultipleDBs shows a warning if multiple block database types are
 322  // detected. This is not a situation most users want. It is handy for
 323  // development however to support multiple side-by-side databases.
 324  func warnMultipleDBs(cx *state.State) {
 325  	// This is intentionally not using the known db types which depend on the
 326  	// database types compiled into the binary since we want to detect legacy db
 327  	// types as well.
 328  	dbTypes := []string{"ffldb", "leveldb", "sqlite"}
 329  	duplicateDbPaths := make([]string, 0, len(dbTypes)-1)
 330  	for _, dbType := range dbTypes {
 331  		if dbType == cx.Config.DbType.V() {
 332  			continue
 333  		}
 334  		// store db path as a duplicate db if it exists
 335  		dbPath := state.BlockDb(cx, dbType, blockdb.NamePrefix)
 336  		if apputil.FileExists(dbPath) {
 337  			duplicateDbPaths = append(duplicateDbPaths, dbPath)
 338  		}
 339  	}
 340  	// warn if there are extra databases
 341  	if len(duplicateDbPaths) > 0 {
 342  		selectedDbPath := state.BlockDb(cx, cx.Config.DbType.V(), blockdb.NamePrefix)
 343  		W.F(
 344  			"\nThere are multiple block chain databases using different"+
 345  				" database types.\nYou probably don't want to waste disk"+
 346  				" space by having more than one."+
 347  				"\nYour current database is located at [%v]."+
 348  				"\nThe additional database is located at %v",
 349  			selectedDbPath,
 350  			duplicateDbPaths,
 351  		)
 352  	}
 353  }
 354  
 355  // dirEmpty returns whether or not the specified directory path is empty
 356  func dirEmpty(dirPath string) (bool, error) {
 357  	f, e := os.Open(dirPath)
 358  	if e != nil {
 359  		return false, e
 360  	}
 361  	defer func() {
 362  		if e = f.Close(); E.Chk(e) {
 363  		}
 364  	}()
 365  	// Read the names of a max of one entry from the directory. When the directory is empty, an io.EOF error will be
 366  	// returned, so allow it.
 367  	names, e := f.Readdirnames(1)
 368  	if e != nil && e != io.EOF {
 369  		return false, e
 370  	}
 371  	return len(names) == 0, nil
 372  }
 373  
 374  // doUpgrades performs upgrades to pod as new versions require it
 375  func doUpgrades(cx *state.State) (e error) {
 376  	e = upgradeDBPaths(cx)
 377  	if e != nil {
 378  		return e
 379  	}
 380  	return upgradeDataPaths()
 381  }
 382  
 383  // oldPodHomeDir returns the OS specific home directory pod used prior to version 0.3.3. This has since been replaced
 384  // with util.AppDataDir but this function is still provided for the automatic upgrade path.
 385  func oldPodHomeDir() string {
 386  	// Search for Windows APPDATA first. This won't exist on POSIX OSes
 387  	appData := os.Getenv("APPDATA")
 388  	if appData != "" {
 389  		return filepath.Join(appData, "pod")
 390  	}
 391  	// Fall back to standard HOME directory that works for most POSIX OSes
 392  	home := os.Getenv("HOME")
 393  	if home != "" {
 394  		return filepath.Join(home, ".pod")
 395  	}
 396  	// In the worst case, use the current directory
 397  	return "."
 398  }
 399  
 400  // upgradeDBPathNet moves the database for a specific network from its location prior to pod version 0.2.0 and uses
 401  // heuristics to ascertain the old database type to rename to the new format.
 402  func upgradeDBPathNet(cx *state.State, oldDbPath, netName string) (e error) {
 403  	// Prior to version 0.2.0, the database was named the same thing for both sqlite and leveldb. Use heuristics to
 404  	// figure out the type of the database and move it to the new path and name introduced with version 0.2.0
 405  	// accordingly.
 406  	fi, e := os.Stat(oldDbPath)
 407  	if e == nil {
 408  		oldDbType := "sqlite"
 409  		if fi.IsDir() {
 410  			oldDbType = "leveldb"
 411  		}
 412  		// The new database name is based on the database type and resides in a directory named after the network type.
 413  		newDbRoot := filepath.Join(filepath.Dir(cx.Config.DataDir.V()), netName)
 414  		newDbName := blockdb.NamePrefix + "_" + oldDbType
 415  		if oldDbType == "sqlite" {
 416  			newDbName = newDbName + ".db"
 417  		}
 418  		newDbPath := filepath.Join(newDbRoot, newDbName)
 419  		// Create the new path if needed
 420  		//
 421  		e = os.MkdirAll(newDbRoot, 0700)
 422  		if e != nil {
 423  			return e
 424  		}
 425  		// Move and rename the old database
 426  		//
 427  		e := os.Rename(oldDbPath, newDbPath)
 428  		if e != nil {
 429  			return e
 430  		}
 431  	}
 432  	return nil
 433  }
 434  
 435  // upgradeDBPaths moves the databases from their locations prior to pod version 0.2.0 to their new locations
 436  func upgradeDBPaths(cx *state.State) (e error) {
 437  	// Prior to version 0.2.0 the databases were in the "db" directory and their names were suffixed by "testnet" and
 438  	// "regtest" for their respective networks. Chk for the old database and update it to the new path introduced with
 439  	// version 0.2.0 accordingly.
 440  	oldDbRoot := filepath.Join(oldPodHomeDir(), "db")
 441  	e = upgradeDBPathNet(cx, filepath.Join(oldDbRoot, "pod.db"), "mainnet")
 442  	if e != nil {
 443  		D.Ln(e)
 444  	}
 445  	e = upgradeDBPathNet(
 446  		cx, filepath.Join(oldDbRoot, "pod_testnet.db"),
 447  		"testnet",
 448  	)
 449  	if e != nil {
 450  		D.Ln(e)
 451  	}
 452  	e = upgradeDBPathNet(
 453  		cx, filepath.Join(oldDbRoot, "pod_regtest.db"),
 454  		"regtest",
 455  	)
 456  	if e != nil {
 457  		D.Ln(e)
 458  	}
 459  	// Remove the old db directory
 460  	return os.RemoveAll(oldDbRoot)
 461  }
 462  
 463  // upgradeDataPaths moves the application data from its location prior to pod version 0.3.3 to its new location.
 464  func upgradeDataPaths() (e error) {
 465  	// No need to migrate if the old and new home paths are the same.
 466  	oldHomePath := oldPodHomeDir()
 467  	newHomePath := constant.DefaultHomeDir
 468  	if oldHomePath == newHomePath {
 469  		return nil
 470  	}
 471  	// Only migrate if the old path exists and the new one doesn't
 472  	if apputil.FileExists(oldHomePath) && !apputil.FileExists(newHomePath) {
 473  		// Create the new path
 474  		I.F(
 475  			"migrating application home path from '%s' to '%s'",
 476  			oldHomePath, newHomePath,
 477  		)
 478  		e := os.MkdirAll(newHomePath, 0700)
 479  		if e != nil {
 480  			return e
 481  		}
 482  		// Move old pod.conf into new location if needed
 483  		oldConfPath := filepath.Join(oldHomePath, constant.DefaultConfigFilename)
 484  		newConfPath := filepath.Join(newHomePath, constant.DefaultConfigFilename)
 485  		if apputil.FileExists(oldConfPath) && !apputil.FileExists(newConfPath) {
 486  			e = os.Rename(oldConfPath, newConfPath)
 487  			if e != nil {
 488  				return e
 489  			}
 490  		}
 491  		// Move old data directory into new location if needed
 492  		oldDataPath := filepath.Join(oldHomePath, constant.DefaultDataDirname)
 493  		newDataPath := filepath.Join(newHomePath, constant.DefaultDataDirname)
 494  		if apputil.FileExists(oldDataPath) && !apputil.FileExists(newDataPath) {
 495  			e = os.Rename(oldDataPath, newDataPath)
 496  			if e != nil {
 497  				return e
 498  			}
 499  		}
 500  		// Remove the old home if it is empty or show a warning if not
 501  		ohpEmpty, e := dirEmpty(oldHomePath)
 502  		if e != nil {
 503  			return e
 504  		}
 505  		if ohpEmpty {
 506  			e := os.Remove(oldHomePath)
 507  			if e != nil {
 508  				return e
 509  			}
 510  		} else {
 511  			W.F(
 512  				"not removing '%s' since it contains files not created by"+
 513  					" this application you may want to manually move them or"+
 514  					" delete them.", oldHomePath,
 515  			)
 516  		}
 517  	}
 518  	return nil
 519  }
 520