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  }