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