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