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 }