github.com/dominant-strategies/go-quai@v0.28.2/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 "errors" 21 "fmt" 22 "net/http" 23 "os" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "sync" 28 29 "github.com/dominant-strategies/go-quai/core/rawdb" 30 "github.com/dominant-strategies/go-quai/ethdb" 31 "github.com/dominant-strategies/go-quai/event" 32 "github.com/dominant-strategies/go-quai/log" 33 "github.com/dominant-strategies/go-quai/p2p" 34 "github.com/dominant-strategies/go-quai/rpc" 35 "github.com/prometheus/tsdb/fileutil" 36 ) 37 38 // Node is a container on which services can be registered. 39 type Node struct { 40 eventmux *event.TypeMux 41 config *Config 42 log log.Logger 43 dirLock fileutil.Releaser // prevents concurrent use of instance directory 44 stop chan struct{} // Channel to wait for termination notifications 45 server *p2p.Server // Currently running P2P networking layer 46 startStopLock sync.Mutex // Start/Stop are protected by an additional lock 47 state int // Tracks state of node lifecycle 48 49 lock sync.Mutex 50 lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle 51 rpcAPIs []rpc.API // List of APIs currently provided by the node 52 http *httpServer // 53 ws *httpServer // 54 inprocHandler *rpc.Server // In-process RPC request handler to process the API requests 55 56 databases map[*closeTrackingDB]struct{} // All open databases 57 } 58 59 const ( 60 initializingState = iota 61 runningState 62 closedState 63 ) 64 65 // New creates a new P2P node, ready for protocol registration. 66 func New(conf *Config) (*Node, error) { 67 // Copy config and resolve the datadir so future changes to the current 68 // working directory don't affect the node. 69 confCopy := *conf 70 conf = &confCopy 71 if conf.DataDir != "" { 72 absdatadir, err := filepath.Abs(conf.DataDir) 73 if err != nil { 74 return nil, err 75 } 76 conf.DataDir = absdatadir 77 } 78 if conf.Logger == nil { 79 conf.Logger = &log.Log 80 } 81 82 // Ensure that the instance name doesn't cause weird conflicts with 83 // other files in the data directory. 84 if strings.ContainsAny(conf.Name, `/\`) { 85 return nil, errors.New(`Config.Name must not contain '/' or '\'`) 86 } 87 if conf.Name == datadirDefaultKeyStore { 88 return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) 89 } 90 91 node := &Node{ 92 config: conf, 93 inprocHandler: rpc.NewServer(), 94 eventmux: new(event.TypeMux), 95 log: *conf.Logger, 96 stop: make(chan struct{}), 97 server: &p2p.Server{Config: conf.P2P}, 98 databases: make(map[*closeTrackingDB]struct{}), 99 } 100 101 // Register built-in APIs. 102 node.rpcAPIs = append(node.rpcAPIs, node.apis()...) 103 104 // Acquire the instance directory lock. 105 if err := node.openDataDir(); err != nil { 106 return nil, err 107 } 108 109 // Initialize the p2p server. This creates the node key and discovery databases. 110 node.server.Config.PrivateKey = node.config.NodeKey() 111 node.server.Config.Name = node.config.NodeName() 112 node.server.Config.Logger = &node.log 113 if node.server.Config.StaticNodes == nil { 114 node.server.Config.StaticNodes = node.config.StaticNodes() 115 } 116 if node.server.Config.TrustedNodes == nil { 117 node.server.Config.TrustedNodes = node.config.TrustedNodes() 118 } 119 if node.server.Config.NodeDatabase == "" { 120 node.server.Config.NodeDatabase = node.config.NodeDB() 121 } 122 123 // Check HTTP/WS prefixes are valid. 124 if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil { 125 return nil, err 126 } 127 if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil { 128 return nil, err 129 } 130 131 // Configure RPC servers. 132 node.http = newHTTPServer(node.log, conf.HTTPTimeouts) 133 node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) 134 135 return node, nil 136 } 137 138 // Start starts all registered lifecycles, RPC services and p2p networking. 139 // Node can only be started once. 140 func (n *Node) Start() error { 141 n.startStopLock.Lock() 142 defer n.startStopLock.Unlock() 143 144 n.lock.Lock() 145 switch n.state { 146 case runningState: 147 n.lock.Unlock() 148 return ErrNodeRunning 149 case closedState: 150 n.lock.Unlock() 151 return ErrNodeStopped 152 } 153 n.state = runningState 154 // open networking and RPC endpoints 155 err := n.openEndpoints() 156 lifecycles := make([]Lifecycle, len(n.lifecycles)) 157 copy(lifecycles, n.lifecycles) 158 n.lock.Unlock() 159 160 // Check if endpoint startup failed. 161 if err != nil { 162 n.doClose(nil) 163 return err 164 } 165 // Start all registered lifecycles. 166 var started []Lifecycle 167 for _, lifecycle := range lifecycles { 168 if err = lifecycle.Start(); err != nil { 169 break 170 } 171 started = append(started, lifecycle) 172 } 173 // Check if any lifecycle failed to start. 174 if err != nil { 175 n.stopServices(started) 176 n.doClose(nil) 177 } 178 return err 179 } 180 181 // Close stops the Node and releases resources acquired in 182 // Node constructor New. 183 func (n *Node) Close() error { 184 n.startStopLock.Lock() 185 defer n.startStopLock.Unlock() 186 187 n.lock.Lock() 188 state := n.state 189 n.lock.Unlock() 190 switch state { 191 case initializingState: 192 // The node was never started. 193 return n.doClose(nil) 194 case runningState: 195 // The node was started, release resources acquired by Start(). 196 var errs []error 197 if err := n.stopServices(n.lifecycles); err != nil { 198 errs = append(errs, err) 199 } 200 return n.doClose(errs) 201 case closedState: 202 return ErrNodeStopped 203 default: 204 panic(fmt.Sprintf("node is in unknown state %d", state)) 205 } 206 } 207 208 // doClose releases resources acquired by New(), collecting errors. 209 func (n *Node) doClose(errs []error) error { 210 // Close databases. This needs the lock because it needs to 211 // synchronize with OpenDatabase*. 212 n.lock.Lock() 213 n.state = closedState 214 errs = append(errs, n.closeDatabases()...) 215 n.lock.Unlock() 216 217 // Release instance directory lock. 218 n.closeDataDir() 219 220 // Unblock n.Wait. 221 close(n.stop) 222 223 // Report any errors that might have occurred. 224 switch len(errs) { 225 case 0: 226 return nil 227 case 1: 228 return errs[0] 229 default: 230 return fmt.Errorf("%v", errs) 231 } 232 } 233 234 // openEndpoints starts all network and RPC endpoints. 235 func (n *Node) openEndpoints() error { 236 // start networking endpoints 237 n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) 238 if err := n.server.Start(); err != nil { 239 return convertFileLockError(err) 240 } 241 // start RPC endpoints 242 err := n.startRPC() 243 if err != nil { 244 n.stopRPC() 245 n.server.Stop() 246 } 247 return err 248 } 249 250 // containsLifecycle checks if 'lfs' contains 'l'. 251 func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { 252 for _, obj := range lfs { 253 if obj == l { 254 return true 255 } 256 } 257 return false 258 } 259 260 // stopServices terminates running services, RPC and p2p networking. 261 // It is the inverse of Start. 262 func (n *Node) stopServices(running []Lifecycle) error { 263 n.stopRPC() 264 265 // Stop running lifecycles in reverse order. 266 failure := &StopError{Services: make(map[reflect.Type]error)} 267 for i := len(running) - 1; i >= 0; i-- { 268 if err := running[i].Stop(); err != nil { 269 failure.Services[reflect.TypeOf(running[i])] = err 270 } 271 } 272 273 // Stop p2p networking. 274 n.server.Stop() 275 276 if len(failure.Services) > 0 { 277 return failure 278 } 279 return nil 280 } 281 282 func (n *Node) openDataDir() error { 283 if n.config.DataDir == "" { 284 return nil // ephemeral 285 } 286 287 instdir := filepath.Join(n.config.DataDir, n.config.name()) 288 if err := os.MkdirAll(instdir, 0700); err != nil { 289 return err 290 } 291 // Lock the instance directory to prevent concurrent use by another instance as well as 292 // accidental use of the instance directory as a database. 293 release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) 294 if err != nil { 295 return convertFileLockError(err) 296 } 297 n.dirLock = release 298 return nil 299 } 300 301 func (n *Node) closeDataDir() { 302 // Release instance directory lock. 303 if n.dirLock != nil { 304 if err := n.dirLock.Release(); err != nil { 305 n.log.Error("Can't release datadir lock", "err", err) 306 } 307 n.dirLock = nil 308 } 309 } 310 311 // configureRPC is a helper method to configure all the various RPC endpoints during node 312 // startup. It's not meant to be called at any time afterwards as it makes certain 313 // assumptions about the state of the node. 314 func (n *Node) startRPC() error { 315 if err := n.startInProc(); err != nil { 316 return err 317 } 318 319 // Configure HTTP. 320 if n.config.HTTPHost != "" { 321 config := httpConfig{ 322 CorsAllowedOrigins: n.config.HTTPCors, 323 Vhosts: n.config.HTTPVirtualHosts, 324 Modules: n.config.HTTPModules, 325 prefix: n.config.HTTPPathPrefix, 326 } 327 if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil { 328 return err 329 } 330 if err := n.http.enableRPC(n.rpcAPIs, config); err != nil { 331 return err 332 } 333 } 334 335 // Configure WebSocket. 336 if n.config.WSHost != "" { 337 server := n.wsServerForPort(n.config.WSPort) 338 config := wsConfig{ 339 Modules: n.config.WSModules, 340 Origins: n.config.WSOrigins, 341 prefix: n.config.WSPathPrefix, 342 } 343 if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil { 344 return err 345 } 346 if err := server.enableWS(n.rpcAPIs, config); err != nil { 347 return err 348 } 349 } 350 351 if err := n.http.start(); err != nil { 352 return err 353 } 354 return n.ws.start() 355 } 356 357 func (n *Node) wsServerForPort(port int) *httpServer { 358 if n.config.HTTPHost == "" || n.http.port == port { 359 return n.http 360 } 361 return n.ws 362 } 363 364 func (n *Node) stopRPC() { 365 n.http.stop() 366 n.ws.stop() 367 n.stopInProc() 368 } 369 370 // startInProc registers all RPC APIs on the inproc server. 371 func (n *Node) startInProc() error { 372 for _, api := range n.rpcAPIs { 373 if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil { 374 return err 375 } 376 } 377 return nil 378 } 379 380 // stopInProc terminates the in-process RPC endpoint. 381 func (n *Node) stopInProc() { 382 n.inprocHandler.Stop() 383 } 384 385 // Wait blocks until the node is closed. 386 func (n *Node) Wait() { 387 <-n.stop 388 } 389 390 // RegisterLifecycle registers the given Lifecycle on the node. 391 func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { 392 n.lock.Lock() 393 defer n.lock.Unlock() 394 395 if n.state != initializingState { 396 panic("can't register lifecycle on running/stopped node") 397 } 398 if containsLifecycle(n.lifecycles, lifecycle) { 399 panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) 400 } 401 n.lifecycles = append(n.lifecycles, lifecycle) 402 } 403 404 // RegisterProtocols adds backend's protocols to the node's p2p server. 405 func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { 406 n.lock.Lock() 407 defer n.lock.Unlock() 408 409 if n.state != initializingState { 410 panic("can't register protocols on running/stopped node") 411 } 412 n.server.Protocols = append(n.server.Protocols, protocols...) 413 } 414 415 // RegisterAPIs registers the APIs a service provides on the node. 416 func (n *Node) RegisterAPIs(apis []rpc.API) { 417 n.lock.Lock() 418 defer n.lock.Unlock() 419 420 if n.state != initializingState { 421 panic("can't register APIs on running/stopped node") 422 } 423 n.rpcAPIs = append(n.rpcAPIs, apis...) 424 } 425 426 // RegisterHandler mounts a handler on the given path on the canonical HTTP server. 427 // 428 // The name of the handler is shown in a log message when the HTTP server starts 429 // and should be a descriptive term for the service provided by the handler. 430 func (n *Node) RegisterHandler(name, path string, handler http.Handler) { 431 n.lock.Lock() 432 defer n.lock.Unlock() 433 434 if n.state != initializingState { 435 panic("can't register HTTP handler on running/stopped node") 436 } 437 438 n.http.mux.Handle(path, handler) 439 n.http.handlerNames[path] = name 440 } 441 442 // Attach creates an RPC client attached to an in-process API handler. 443 func (n *Node) Attach() (*rpc.Client, error) { 444 return rpc.DialInProc(n.inprocHandler), nil 445 } 446 447 // RPCHandler returns the in-process RPC request handler. 448 func (n *Node) RPCHandler() (*rpc.Server, error) { 449 n.lock.Lock() 450 defer n.lock.Unlock() 451 452 if n.state == closedState { 453 return nil, ErrNodeStopped 454 } 455 return n.inprocHandler, nil 456 } 457 458 // Config returns the configuration of node. 459 func (n *Node) Config() *Config { 460 return n.config 461 } 462 463 // Server retrieves the currently running P2P network layer. This method is meant 464 // only to inspect fields of the currently running server. Callers should not 465 // start or stop the returned server. 466 func (n *Node) Server() *p2p.Server { 467 n.lock.Lock() 468 defer n.lock.Unlock() 469 470 return n.server 471 } 472 473 // DataDir retrieves the current datadir used by the protocol stack. 474 // Deprecated: No files should be stored in this directory, use InstanceDir instead. 475 func (n *Node) DataDir() string { 476 return n.config.DataDir 477 } 478 479 // InstanceDir retrieves the instance directory used by the protocol stack. 480 func (n *Node) InstanceDir() string { 481 return n.config.instanceDir() 482 } 483 484 // HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not 485 // contain the JSON-RPC path prefix set by HTTPPathPrefix. 486 func (n *Node) HTTPEndpoint() string { 487 return "http://" + n.http.listenAddr() 488 } 489 490 // WSEndpoint returns the current JSON-RPC over WebSocket endpoint. 491 func (n *Node) WSEndpoint() string { 492 if n.http.wsAllowed() { 493 return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix 494 } 495 return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix 496 } 497 498 // EventMux retrieves the event multiplexer used by all the network services in 499 // the current protocol stack. 500 func (n *Node) EventMux() *event.TypeMux { 501 return n.eventmux 502 } 503 504 // OpenDatabase opens an existing database with the given name (or creates one if no 505 // previous can be found) from within the node's instance directory. If the node is 506 // ephemeral, a memory database is returned. 507 func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) { 508 n.lock.Lock() 509 defer n.lock.Unlock() 510 if n.state == closedState { 511 return nil, ErrNodeStopped 512 } 513 514 var db ethdb.Database 515 var err error 516 if n.config.DataDir == "" { 517 db = rawdb.NewMemoryDatabase() 518 } else { 519 db, err = rawdb.Open(rawdb.OpenOptions{ 520 Type: n.config.DBEngine, 521 Directory: n.ResolvePath(name), 522 Namespace: namespace, 523 Cache: cache, 524 Handles: handles, 525 ReadOnly: readonly, 526 }) 527 } 528 529 if err == nil { 530 db = n.wrapDatabase(db) 531 } 532 return db, err 533 } 534 535 // OpenDatabaseWithFreezer opens an existing database with the given name (or 536 // creates one if no previous can be found) from within the node's data directory, 537 // also attaching a chain freezer to it that moves ancient chain data from the 538 // database to immutable append-only files. If the node is an ephemeral one, a 539 // memory database is returned. 540 func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient, namespace string, readonly bool) (ethdb.Database, error) { 541 n.lock.Lock() 542 defer n.lock.Unlock() 543 if n.state == closedState { 544 return nil, ErrNodeStopped 545 } 546 var db ethdb.Database 547 var err error 548 if n.config.DataDir == "" { 549 db = rawdb.NewMemoryDatabase() 550 } else { 551 db, err = rawdb.Open(rawdb.OpenOptions{ 552 Type: n.config.DBEngine, 553 Directory: n.ResolvePath(name), 554 AncientsDirectory: n.ResolveAncient(name, ancient), 555 Namespace: namespace, 556 Cache: cache, 557 Handles: handles, 558 ReadOnly: readonly, 559 }) 560 } 561 562 if err == nil { 563 db = n.wrapDatabase(db) 564 } 565 return db, err 566 } 567 568 // ResolvePath returns the absolute path of a resource in the instance directory. 569 func (n *Node) ResolvePath(x string) string { 570 return n.config.ResolvePath(x) 571 } 572 573 // ResolveAncient returns the absolute path of the root ancient directory. 574 func (n *Node) ResolveAncient(name string, ancient string) string { 575 switch { 576 case ancient == "": 577 ancient = filepath.Join(n.ResolvePath(name), "ancient") 578 case !filepath.IsAbs(ancient): 579 ancient = n.ResolvePath(ancient) 580 } 581 return ancient 582 } 583 584 // closeTrackingDB wraps the Close method of a database. When the database is closed by the 585 // service, the wrapper removes it from the node's database map. This ensures that Node 586 // won't auto-close the database if it is closed by the service that opened it. 587 type closeTrackingDB struct { 588 ethdb.Database 589 n *Node 590 } 591 592 func (db *closeTrackingDB) Close() error { 593 db.n.lock.Lock() 594 delete(db.n.databases, db) 595 db.n.lock.Unlock() 596 return db.Database.Close() 597 } 598 599 // wrapDatabase ensures the database will be auto-closed when Node is closed. 600 func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { 601 wrapper := &closeTrackingDB{db, n} 602 n.databases[wrapper] = struct{}{} 603 return wrapper 604 } 605 606 // closeDatabases closes all open databases. 607 func (n *Node) closeDatabases() (errors []error) { 608 for db := range n.databases { 609 delete(n.databases, db) 610 if err := db.Database.Close(); err != nil { 611 errors = append(errors, err) 612 } 613 } 614 return errors 615 }