github.com/core-coin/go-core/v2@v2.1.9/node/node.go (about) 1 // Copyright 2015 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 crand "crypto/rand" 21 "errors" 22 "fmt" 23 "net/http" 24 "os" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "sync" 29 30 "github.com/prometheus/tsdb/fileutil" 31 32 "github.com/core-coin/go-core/v2/common" 33 "github.com/core-coin/go-core/v2/common/hexutil" 34 "github.com/core-coin/go-core/v2/core/led" 35 "github.com/core-coin/go-core/v2/xcbdb" 36 37 "github.com/core-coin/go-core/v2/accounts" 38 "github.com/core-coin/go-core/v2/core/rawdb" 39 "github.com/core-coin/go-core/v2/event" 40 "github.com/core-coin/go-core/v2/log" 41 "github.com/core-coin/go-core/v2/p2p" 42 "github.com/core-coin/go-core/v2/rpc" 43 ) 44 45 // Node is a container on which services can be registered. 46 type Node struct { 47 eventmux *event.TypeMux 48 config *Config 49 accman *accounts.Manager 50 log log.Logger 51 ephemKeystore string // if non-empty, the key directory that will be removed by Stop 52 dirLock fileutil.Releaser // prevents concurrent use of instance directory 53 stop chan struct{} // Channel to wait for termination notifications 54 server *p2p.Server // Currently running P2P networking layer 55 startStopLock sync.Mutex // Start/Stop are protected by an additional lock 56 state int // Tracks state of node lifecycle 57 58 lock sync.Mutex 59 lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle 60 rpcAPIs []rpc.API // List of APIs currently provided by the node 61 http *httpServer // 62 ws *httpServer // 63 httpAuth *httpServer // 64 wsAuth *httpServer // 65 ipc *ipcServer // Stores information about the ipc http server 66 inprocHandler *rpc.Server // In-process RPC request handler to process the API requests 67 68 databases map[*closeTrackingDB]struct{} // All open databases 69 } 70 71 const ( 72 initializingState = iota 73 runningState 74 closedState 75 ) 76 77 // New creates a new P2P node, ready for protocol registration. 78 func New(conf *Config) (*Node, error) { 79 // Copy config and resolve the datadir so future changes to the current 80 // working directory don't affect the node. 81 confCopy := *conf 82 conf = &confCopy 83 if conf.DataDir != "" { 84 absdatadir, err := filepath.Abs(conf.DataDir) 85 if err != nil { 86 return nil, err 87 } 88 conf.DataDir = absdatadir 89 } 90 if conf.Logger == nil { 91 conf.Logger = log.New() 92 } 93 94 // Ensure that the instance name doesn't cause weird conflicts with 95 // other files in the data directory. 96 if strings.ContainsAny(conf.Name, `/\`) { 97 return nil, errors.New(`Config.Name must not contain '/' or '\'`) 98 } 99 if conf.Name == datadirDefaultKeyStore { 100 return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) 101 } 102 if strings.HasSuffix(conf.Name, ".ipc") { 103 return nil, errors.New(`Config.Name cannot end in ".ipc"`) 104 } 105 106 node := &Node{ 107 config: conf, 108 inprocHandler: rpc.NewServer(), 109 eventmux: new(event.TypeMux), 110 log: conf.Logger, 111 stop: make(chan struct{}), 112 server: &p2p.Server{Config: conf.P2P}, 113 databases: make(map[*closeTrackingDB]struct{}), 114 } 115 116 // Register built-in APIs. 117 node.rpcAPIs = append(node.rpcAPIs, node.apis()...) 118 119 // Acquire the instance directory lock. 120 if err := node.openDataDir(); err != nil { 121 return nil, err 122 } 123 // Ensure that the AccountManager method works before the node has started. We rely on 124 // this in cmd/gocore. 125 am, ephemeralKeystore, err := makeAccountManager(conf) 126 if err != nil { 127 return nil, err 128 } 129 node.accman = am 130 node.ephemKeystore = ephemeralKeystore 131 132 // Initialize the p2p server. This creates the node key and discovery databases. 133 node.server.Config.PrivateKey = node.config.NodeKey() 134 node.server.Config.Name = node.config.NodeName() 135 node.server.Config.Logger = node.log 136 if node.server.Config.StaticNodes == nil { 137 node.server.Config.StaticNodes = node.config.StaticNodes() 138 } 139 if node.server.Config.TrustedNodes == nil { 140 node.server.Config.TrustedNodes = node.config.TrustedNodes() 141 } 142 if node.server.Config.NodeDatabase == "" { 143 node.server.Config.NodeDatabase = node.config.NodeDB() 144 } 145 146 // Check HTTP/WS prefixes are valid. 147 if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil { 148 return nil, err 149 } 150 if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil { 151 return nil, err 152 } 153 154 // Configure RPC servers. 155 node.http = newHTTPServer(node.log, conf.HTTPTimeouts) 156 node.httpAuth = newHTTPServer(node.log, conf.HTTPTimeouts) 157 node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) 158 node.wsAuth = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) 159 node.ipc = newIPCServer(node.log, conf.IPCEndpoint()) 160 161 return node, nil 162 } 163 164 // Start starts all registered lifecycles, RPC services and p2p networking. 165 // Node can only be started once. 166 func (n *Node) Start() error { 167 n.startStopLock.Lock() 168 defer n.startStopLock.Unlock() 169 170 n.lock.Lock() 171 switch n.state { 172 case runningState: 173 n.lock.Unlock() 174 return ErrNodeRunning 175 case closedState: 176 n.lock.Unlock() 177 return ErrNodeStopped 178 } 179 n.state = runningState 180 err := n.startNetworking() 181 lifecycles := make([]Lifecycle, len(n.lifecycles)) 182 copy(lifecycles, n.lifecycles) 183 n.lock.Unlock() 184 185 // Check if networking startup failed. 186 if err != nil { 187 n.doClose(nil) 188 return err 189 } 190 // Start all registered lifecycles. 191 var started []Lifecycle 192 for _, lifecycle := range lifecycles { 193 if err = lifecycle.Start(); err != nil { 194 break 195 } 196 started = append(started, lifecycle) 197 } 198 // Check if any lifecycle failed to start. 199 if err != nil { 200 n.stopServices(started) 201 n.doClose(nil) 202 } 203 return err 204 } 205 206 // Close stops the Node and releases resources acquired in 207 // Node constructor New. 208 func (n *Node) Close() error { 209 n.startStopLock.Lock() 210 defer n.startStopLock.Unlock() 211 212 n.lock.Lock() 213 state := n.state 214 n.lock.Unlock() 215 switch state { 216 case initializingState: 217 // The node was never started. 218 return n.doClose(nil) 219 case runningState: 220 // The node was started, release resources acquired by Start(). 221 var errs []error 222 if err := n.stopServices(n.lifecycles); err != nil { 223 errs = append(errs, err) 224 } 225 return n.doClose(errs) 226 case closedState: 227 return ErrNodeStopped 228 default: 229 panic(fmt.Sprintf("node is in unknown state %d", state)) 230 } 231 } 232 233 // doClose releases resources acquired by New(), collecting errors. 234 func (n *Node) doClose(errs []error) error { 235 // Close databases. This needs the lock because it needs to 236 // synchronize with OpenDatabase*. 237 n.lock.Lock() 238 n.state = closedState 239 errs = append(errs, n.closeDatabases()...) 240 n.lock.Unlock() 241 242 if err := n.accman.Close(); err != nil { 243 errs = append(errs, err) 244 } 245 if n.ephemKeystore != "" { 246 if err := os.RemoveAll(n.ephemKeystore); err != nil { 247 errs = append(errs, err) 248 } 249 } 250 251 // Release instance directory lock. 252 n.closeDataDir() 253 254 // Unblock n.Wait. 255 close(n.stop) 256 257 // Disable Led 258 if n.config.Led { 259 if err := led.DisableLed(n.config.LedGPIOPort); err != nil { 260 return err 261 } 262 } 263 264 // Report any errors that might have occurred. 265 switch len(errs) { 266 case 0: 267 return nil 268 case 1: 269 return errs[0] 270 default: 271 return fmt.Errorf("%v", errs) 272 } 273 } 274 275 // startNetworking starts all network endpoints. 276 func (n *Node) startNetworking() error { 277 n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) 278 if err := n.server.Start(); err != nil { 279 return convertFileLockError(err) 280 } 281 err := n.startRPC() 282 if err != nil { 283 n.stopRPC() 284 n.server.Stop() 285 } 286 return err 287 } 288 289 // containsLifecycle checks if 'lfs' contains 'l'. 290 func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { 291 for _, obj := range lfs { 292 if obj == l { 293 return true 294 } 295 } 296 return false 297 } 298 299 // stopServices terminates running services, RPC and p2p networking. 300 // It is the inverse of Start. 301 func (n *Node) stopServices(running []Lifecycle) error { 302 n.stopRPC() 303 304 // Stop running lifecycles in reverse order. 305 failure := &StopError{Services: make(map[reflect.Type]error)} 306 for i := len(running) - 1; i >= 0; i-- { 307 if err := running[i].Stop(); err != nil { 308 failure.Services[reflect.TypeOf(running[i])] = err 309 } 310 } 311 312 // Stop p2p networking. 313 n.server.Stop() 314 315 if len(failure.Services) > 0 { 316 return failure 317 } 318 return nil 319 } 320 321 func (n *Node) openDataDir() error { 322 if n.config.DataDir == "" { 323 return nil // ephemeral 324 } 325 326 instdir := filepath.Join(n.config.DataDir, n.config.name()) 327 if err := os.MkdirAll(instdir, 0700); err != nil { 328 return err 329 } 330 // Lock the instance directory to prevent concurrent use by another instance as well as 331 // accidental use of the instance directory as a database. 332 release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) 333 if err != nil { 334 return convertFileLockError(err) 335 } 336 n.dirLock = release 337 return nil 338 } 339 340 func (n *Node) closeDataDir() { 341 // Release instance directory lock. 342 if n.dirLock != nil { 343 if err := n.dirLock.Release(); err != nil { 344 n.log.Error("Can't release datadir lock", "err", err) 345 } 346 n.dirLock = nil 347 } 348 } 349 350 // obtainJWTSecret loads the jwt-secret, either from the provided config, 351 // or from the default location. If neither of those are present, it generates 352 // a new secret and stores to the default location. 353 func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) { 354 var fileName string 355 if len(cliParam) > 0 { 356 // If a plaintext secret was provided via cli flags, use that 357 jwtSecret := common.FromHex(cliParam) 358 if len(jwtSecret) == 32 && strings.HasPrefix(cliParam, "0x") { 359 log.Warn("Plaintext JWT secret provided, please consider passing via file") 360 return jwtSecret, nil 361 } 362 // path provided 363 fileName = cliParam 364 } else { 365 // no path provided, use default 366 fileName = n.ResolvePath(datadirJWTKey) 367 } 368 // try reading from file 369 log.Debug("Reading JWT secret", "path", fileName) 370 if data, err := os.ReadFile(fileName); err == nil { 371 372 jwtSecret := common.FromHex(strings.TrimSpace(string(data))) 373 if len(jwtSecret) == 32 { 374 return jwtSecret, nil 375 } 376 log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret)) 377 return nil, errors.New("invalid JWT secret") 378 } 379 // Need to generate one 380 jwtSecret := make([]byte, 32) 381 crand.Read(jwtSecret) 382 // if we're in --dev mode, don't bother saving, just show it 383 if fileName == "" { 384 log.Info("Generated ephemeral JWT secret", "secret", hexutil.Encode(jwtSecret)) 385 return jwtSecret, nil 386 } 387 if err := os.WriteFile(fileName, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil { 388 return nil, err 389 } 390 log.Info("Generated JWT secret", "path", fileName) 391 return jwtSecret, nil 392 } 393 394 // startRPC is a helper method to configure all the various RPC endpoints during node 395 // configureRPC is a helper method to configure all the various RPC endpoints during node 396 // startup. It's not meant to be called at any time afterwards as it makes certain 397 // assumptions about the state of the node. 398 func (n *Node) startRPC() error { 399 if err := n.startInProc(); err != nil { 400 return err 401 } 402 403 // Configure IPC. 404 if n.ipc.endpoint != "" { 405 if err := n.ipc.start(n.rpcAPIs); err != nil { 406 return err 407 } 408 } 409 410 var ( 411 servers []*httpServer 412 open, all = n.GetAPIs() 413 ) 414 415 initHttp := func(server *httpServer, apis []rpc.API, port int) error { 416 if err := server.setListenAddr(n.config.HTTPHost, port); err != nil { 417 return err 418 } 419 if err := server.enableRPC(apis, httpConfig{ 420 CorsAllowedOrigins: n.config.HTTPCors, 421 Vhosts: n.config.HTTPVirtualHosts, 422 Modules: n.config.HTTPModules, 423 prefix: n.config.HTTPPathPrefix, 424 }); err != nil { 425 return err 426 } 427 servers = append(servers, server) 428 return nil 429 } 430 431 initWS := func(apis []rpc.API, port int) error { 432 server := n.wsServerForPort(port, false) 433 if err := server.setListenAddr(n.config.WSHost, port); err != nil { 434 return err 435 } 436 if err := server.enableWS(n.rpcAPIs, wsConfig{ 437 Modules: n.config.WSModules, 438 Origins: n.config.WSOrigins, 439 prefix: n.config.WSPathPrefix, 440 }); err != nil { 441 return err 442 } 443 servers = append(servers, server) 444 return nil 445 } 446 447 initAuth := func(apis []rpc.API, port int, secret []byte) error { 448 // Enable auth via HTTP 449 server := n.httpAuth 450 if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { 451 return err 452 } 453 if err := server.enableRPC(apis, httpConfig{ 454 CorsAllowedOrigins: DefaultAuthCors, 455 Vhosts: n.config.AuthVirtualHosts, 456 Modules: DefaultAuthModules, 457 prefix: DefaultAuthPrefix, 458 jwtSecret: secret, 459 }); err != nil { 460 return err 461 } 462 servers = append(servers, server) 463 // Enable auth via WS 464 server = n.wsServerForPort(port, true) 465 if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { 466 return err 467 } 468 469 if err := server.enableWS(apis, wsConfig{ 470 Modules: DefaultAuthModules, 471 Origins: DefaultAuthOrigins, 472 prefix: DefaultAuthPrefix, 473 jwtSecret: secret, 474 }); err != nil { 475 return err 476 } 477 servers = append(servers, server) 478 return nil 479 } 480 // Set up HTTP. 481 if n.config.HTTPHost != "" { 482 // Configure legacy unauthenticated HTTP. 483 if err := initHttp(n.http, open, n.config.HTTPPort); err != nil { 484 return err 485 } 486 } 487 // Configure WebSocket. 488 if n.config.WSHost != "" { 489 // legacy unauthenticated 490 if err := initWS(open, n.config.WSPort); err != nil { 491 return err 492 } 493 } 494 // Configure authenticated API 495 if len(open) != len(all) { 496 jwtSecret, err := n.obtainJWTSecret(n.config.JWTSecret) 497 if err != nil { 498 return err 499 } 500 if err := initAuth(all, n.config.AuthPort, jwtSecret); err != nil { 501 return err 502 } 503 } 504 // Start the servers 505 for _, server := range servers { 506 if err := server.start(); err != nil { 507 return err 508 } 509 } 510 return nil 511 } 512 513 func (n *Node) wsServerForPort(port int, authenticated bool) *httpServer { 514 httpServer, wsServer := n.http, n.ws 515 if authenticated { 516 httpServer, wsServer = n.httpAuth, n.wsAuth 517 } 518 if n.config.HTTPHost == "" || httpServer.port == port { 519 return httpServer 520 } 521 return wsServer 522 } 523 524 func (n *Node) stopRPC() { 525 n.http.stop() 526 n.ws.stop() 527 n.httpAuth.stop() 528 n.wsAuth.stop() 529 n.ipc.stop() 530 n.stopInProc() 531 } 532 533 // startInProc registers all RPC APIs on the inproc server. 534 func (n *Node) startInProc() error { 535 for _, api := range n.rpcAPIs { 536 if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil { 537 return err 538 } 539 } 540 return nil 541 } 542 543 // stopInProc terminates the in-process RPC endpoint. 544 func (n *Node) stopInProc() { 545 n.inprocHandler.Stop() 546 } 547 548 // Wait blocks until the node is closed. 549 func (n *Node) Wait() { 550 <-n.stop 551 } 552 553 // RegisterLifecycle registers the given Lifecycle on the node. 554 func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { 555 n.lock.Lock() 556 defer n.lock.Unlock() 557 558 if n.state != initializingState { 559 panic("can't register lifecycle on running/stopped node") 560 } 561 if containsLifecycle(n.lifecycles, lifecycle) { 562 panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) 563 } 564 n.lifecycles = append(n.lifecycles, lifecycle) 565 } 566 567 // RegisterProtocols adds backend's protocols to the node's p2p server. 568 func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { 569 n.lock.Lock() 570 defer n.lock.Unlock() 571 572 if n.state != initializingState { 573 panic("can't register protocols on running/stopped node") 574 } 575 n.server.Protocols = append(n.server.Protocols, protocols...) 576 } 577 578 // RegisterAPIs registers the APIs a service provides on the node. 579 func (n *Node) RegisterAPIs(apis []rpc.API) { 580 n.lock.Lock() 581 defer n.lock.Unlock() 582 583 if n.state != initializingState { 584 panic("can't register APIs on running/stopped node") 585 } 586 n.rpcAPIs = append(n.rpcAPIs, apis...) 587 } 588 589 // GetAPIs return two sets of APIs, both the ones that do not require 590 // authentication, and the complete set 591 func (n *Node) GetAPIs() (unauthenticated, all []rpc.API) { 592 for _, api := range n.rpcAPIs { 593 if !api.Authenticated { 594 unauthenticated = append(unauthenticated, api) 595 } 596 } 597 return unauthenticated, n.rpcAPIs 598 } 599 600 // RegisterHandler mounts a handler on the given path on the canonical HTTP server. 601 // 602 // The name of the handler is shown in a log message when the HTTP server starts 603 // and should be a descriptive term for the service provided by the handler. 604 func (n *Node) RegisterHandler(name, path string, handler http.Handler) { 605 n.lock.Lock() 606 defer n.lock.Unlock() 607 608 if n.state != initializingState { 609 panic("can't register HTTP handler on running/stopped node") 610 } 611 n.http.mux.Handle(path, handler) 612 n.http.handlerNames[path] = name 613 } 614 615 // Attach creates an RPC client attached to an in-process API handler. 616 func (n *Node) Attach() (*rpc.Client, error) { 617 return rpc.DialInProc(n.inprocHandler), nil 618 } 619 620 // RPCHandler returns the in-process RPC request handler. 621 func (n *Node) RPCHandler() (*rpc.Server, error) { 622 n.lock.Lock() 623 defer n.lock.Unlock() 624 625 if n.state == closedState { 626 return nil, ErrNodeStopped 627 } 628 return n.inprocHandler, nil 629 } 630 631 // Config returns the configuration of node. 632 func (n *Node) Config() *Config { 633 return n.config 634 } 635 636 // Server retrieves the currently running P2P network layer. This method is meant 637 // only to inspect fields of the currently running server. Callers should not 638 // start or stop the returned server. 639 func (n *Node) Server() *p2p.Server { 640 n.lock.Lock() 641 defer n.lock.Unlock() 642 643 return n.server 644 } 645 646 // DataDir retrieves the current datadir used by the protocol stack. 647 // Deprecated: No files should be stored in this directory, use InstanceDir instead. 648 func (n *Node) DataDir() string { 649 return n.config.DataDir 650 } 651 652 // InstanceDir retrieves the instance directory used by the protocol stack. 653 func (n *Node) InstanceDir() string { 654 return n.config.instanceDir() 655 } 656 657 // AccountManager retrieves the account manager used by the protocol stack. 658 func (n *Node) AccountManager() *accounts.Manager { 659 return n.accman 660 } 661 662 // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. 663 func (n *Node) IPCEndpoint() string { 664 return n.ipc.endpoint 665 } 666 667 // HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not 668 // contain the JSON-RPC path prefix set by HTTPPathPrefix. 669 func (n *Node) HTTPEndpoint() string { 670 return "http://" + n.http.listenAddr() 671 } 672 673 // WSEndpoint returns the current JSON-RPC over WebSocket endpoint. 674 func (n *Node) WSEndpoint() string { 675 if n.http.wsAllowed() { 676 return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix 677 } 678 return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix 679 } 680 681 // HTTPAuthEndpoint returns the URL of the authenticated HTTP server. 682 func (n *Node) HTTPAuthEndpoint() string { 683 return "http://" + n.httpAuth.listenAddr() 684 } 685 686 // WSAuthEndpoint returns the current authenticated JSON-RPC over WebSocket endpoint. 687 func (n *Node) WSAuthEndpoint() string { 688 if n.httpAuth.wsAllowed() { 689 return "ws://" + n.httpAuth.listenAddr() + n.httpAuth.wsConfig.prefix 690 } 691 return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix 692 } 693 694 // EventMux retrieves the event multiplexer used by all the network services in 695 // the current protocol stack. 696 func (n *Node) EventMux() *event.TypeMux { 697 return n.eventmux 698 } 699 700 // OpenDatabase opens an existing database with the given name (or creates one if no 701 // previous can be found) from within the node's instance directory. If the node is 702 // ephemeral, a memory database is returned. 703 func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (xcbdb.Database, error) { 704 n.lock.Lock() 705 defer n.lock.Unlock() 706 if n.state == closedState { 707 return nil, ErrNodeStopped 708 } 709 710 var db xcbdb.Database 711 var err error 712 if n.config.DataDir == "" { 713 db = rawdb.NewMemoryDatabase() 714 } else { 715 db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) 716 } 717 718 if err == nil { 719 db = n.wrapDatabase(db) 720 } 721 return db, err 722 } 723 724 // OpenDatabaseWithFreezer opens an existing database with the given name (or 725 // creates one if no previous can be found) from within the node's data directory, 726 // also attaching a chain freezer to it that moves ancient chain data from the 727 // database to immutable append-only files. If the node is an ephemeral one, a 728 // memory database is returned. 729 func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (xcbdb.Database, error) { 730 n.lock.Lock() 731 defer n.lock.Unlock() 732 if n.state == closedState { 733 return nil, ErrNodeStopped 734 } 735 736 var db xcbdb.Database 737 var err error 738 if n.config.DataDir == "" { 739 db = rawdb.NewMemoryDatabase() 740 } else { 741 root := n.ResolvePath(name) 742 switch { 743 case freezer == "": 744 freezer = filepath.Join(root, "ancient") 745 case !filepath.IsAbs(freezer): 746 freezer = n.ResolvePath(freezer) 747 } 748 db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) 749 } 750 751 if err == nil { 752 db = n.wrapDatabase(db) 753 } 754 return db, err 755 } 756 757 // ResolvePath returns the absolute path of a resource in the instance directory. 758 func (n *Node) ResolvePath(x string) string { 759 return n.config.ResolvePath(x) 760 } 761 762 // closeTrackingDB wraps the Close method of a database. When the database is closed by the 763 // service, the wrapper removes it from the node's database map. This ensures that Node 764 // won't auto-close the database if it is closed by the service that opened it. 765 type closeTrackingDB struct { 766 xcbdb.Database 767 n *Node 768 } 769 770 func (db *closeTrackingDB) Close() error { 771 db.n.lock.Lock() 772 delete(db.n.databases, db) 773 db.n.lock.Unlock() 774 return db.Database.Close() 775 } 776 777 // wrapDatabase ensures the database will be auto-closed when Node is closed. 778 func (n *Node) wrapDatabase(db xcbdb.Database) xcbdb.Database { 779 wrapper := &closeTrackingDB{db, n} 780 n.databases[wrapper] = struct{}{} 781 return wrapper 782 } 783 784 // closeDatabases closes all open databases. 785 func (n *Node) closeDatabases() (errors []error) { 786 for db := range n.databases { 787 delete(n.databases, db) 788 if err := db.Database.Close(); err != nil { 789 errors = append(errors, err) 790 } 791 } 792 return errors 793 }