From 3b2c9275fda8acbc2daada2c25baf1cb634a0843 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Apr 2020 18:20:07 +0200 Subject: [PATCH 001/160] created a new package node_2 where i am experimenting w/ interfaces and starting node --- node_2/api.go | 317 +++++++++++++++++++ node_2/config.go | 545 ++++++++++++++++++++++++++++++++ node_2/defaults.go | 113 +++++++ node_2/endpoints.go | 99 ++++++ node_2/errors.go | 63 ++++ node_2/node.go | 737 ++++++++++++++++++++++++++++++++++++++++++++ node_2/rpcstack.go | 185 +++++++++++ node_2/service.go | 133 ++++++++ 8 files changed, 2192 insertions(+) create mode 100644 node_2/api.go create mode 100644 node_2/config.go create mode 100644 node_2/defaults.go create mode 100644 node_2/endpoints.go create mode 100644 node_2/errors.go create mode 100644 node_2/node.go create mode 100644 node_2/rpcstack.go create mode 100644 node_2/service.go diff --git a/node_2/api.go b/node_2/api.go new file mode 100644 index 000000000000..53f4f2dbc9c1 --- /dev/null +++ b/node_2/api.go @@ -0,0 +1,317 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "context" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rpc" +) + +// PrivateAdminAPI is the collection of administrative API methods exposed only +// over a secure RPC channel. +type PrivateAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPrivateAdminAPI creates a new API definition for the private admin methods +// of the node itself. +func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { + return &PrivateAdminAPI{node: node} +} + +// AddPeer requests connecting to a remote node, and also maintaining the new +// connection at all times, even reconnecting if it is lost. +func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to add the url as a static peer and return + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.AddPeer(node) + return true, nil +} + +// RemovePeer disconnects from a remote node if the connection exists +func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to remove the url as a static peer and return + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.RemovePeer(node) + return true, nil +} + +// AddTrustedPeer allows a remote node to always connect, even if slots are full +func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.AddTrustedPeer(node) + return true, nil +} + +// RemoveTrustedPeer removes a remote node from the trusted peer set, but it +// does not disconnect it automatically. +func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.RemoveTrustedPeer(node) + return true, nil +} + +// PeerEvents creates an RPC subscription which receives peer events from the +// node's p2p.Server +func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + + // Create the subscription + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return nil, rpc.ErrNotificationsUnsupported + } + rpcSub := notifier.CreateSubscription() + + go func() { + events := make(chan *p2p.PeerEvent) + sub := server.SubscribeEvents(events) + defer sub.Unsubscribe() + + for { + select { + case event := <-events: + notifier.Notify(rpcSub.ID, event) + case <-sub.Err(): + return + case <-rpcSub.Err(): + return + case <-notifier.Closed(): + return + } + } + }() + + return rpcSub, nil +} + +// StartRPC starts the HTTP RPC API server. +func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { + api.node.lock.Lock() + defer api.node.lock.Unlock() + + if api.node.httpHandler != nil { + return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint) + } + + if host == nil { + h := DefaultHTTPHost + if api.node.config.HTTPHost != "" { + h = api.node.config.HTTPHost + } + host = &h + } + if port == nil { + port = &api.node.config.HTTPPort + } + + allowedOrigins := api.node.config.HTTPCors + if cors != nil { + allowedOrigins = nil + for _, origin := range strings.Split(*cors, ",") { + allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) + } + } + + allowedVHosts := api.node.config.HTTPVirtualHosts + if vhosts != nil { + allowedVHosts = nil + for _, vhost := range strings.Split(*host, ",") { + allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) + } + } + + modules := api.node.httpWhitelist + if apis != nil { + modules = nil + for _, m := range strings.Split(*apis, ",") { + modules = append(modules, strings.TrimSpace(m)) + } + } + + if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { + return false, err + } + return true, nil +} + +// StopRPC terminates an already running HTTP RPC API endpoint. +func (api *PrivateAdminAPI) StopRPC() (bool, error) { + api.node.lock.Lock() + defer api.node.lock.Unlock() + + if api.node.httpHandler == nil { + return false, fmt.Errorf("HTTP RPC not running") + } + api.node.stopHTTP() + return true, nil +} + +// StartWS starts the websocket RPC API server. +func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { + api.node.lock.Lock() + defer api.node.lock.Unlock() + + if api.node.wsHandler != nil { + return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint) + } + + if host == nil { + h := DefaultWSHost + if api.node.config.WSHost != "" { + h = api.node.config.WSHost + } + host = &h + } + if port == nil { + port = &api.node.config.WSPort + } + + origins := api.node.config.WSOrigins + if allowedOrigins != nil { + origins = nil + for _, origin := range strings.Split(*allowedOrigins, ",") { + origins = append(origins, strings.TrimSpace(origin)) + } + } + + modules := api.node.config.WSModules + if apis != nil { + modules = nil + for _, m := range strings.Split(*apis, ",") { + modules = append(modules, strings.TrimSpace(m)) + } + } + + if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil { + return false, err + } + return true, nil +} + +// StopWS terminates an already running websocket RPC API endpoint. +func (api *PrivateAdminAPI) StopWS() (bool, error) { + api.node.lock.Lock() + defer api.node.lock.Unlock() + + if api.node.wsHandler == nil { + return false, fmt.Errorf("WebSocket RPC not running") + } + api.node.stopWS() + return true, nil +} + +// PublicAdminAPI is the collection of administrative API methods exposed over +// both secure and unsecure RPC channels. +type PublicAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPublicAdminAPI creates a new API definition for the public admin methods +// of the node itself. +func NewPublicAdminAPI(node *Node) *PublicAdminAPI { + return &PublicAdminAPI{node: node} +} + +// Peers retrieves all the information we know about each individual peer at the +// protocol granularity. +func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.PeersInfo(), nil +} + +// NodeInfo retrieves all the information we know about the host node at the +// protocol granularity. +func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.NodeInfo(), nil +} + +// Datadir retrieves the current data directory the node is using. +func (api *PublicAdminAPI) Datadir() string { + return api.node.DataDir() +} + +// PublicWeb3API offers helper utils +type PublicWeb3API struct { + stack *Node +} + +// NewPublicWeb3API creates a new Web3Service instance +func NewPublicWeb3API(stack *Node) *PublicWeb3API { + return &PublicWeb3API{stack} +} + +// ClientVersion returns the node name +func (s *PublicWeb3API) ClientVersion() string { + return s.stack.Server().Name +} + +// Sha3 applies the ethereum sha3 implementation on the input. +// It assumes the input is hex encoded. +func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { + return crypto.Keccak256(input) +} diff --git a/node_2/config.go b/node_2/config.go new file mode 100644 index 000000000000..4a7814adeb70 --- /dev/null +++ b/node_2/config.go @@ -0,0 +1,545 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "crypto/ecdsa" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/external" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/scwallet" + "github.com/ethereum/go-ethereum/accounts/usbwallet" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rpc" +) + +const ( + datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key + datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore + datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list + datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list + datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos +) + +// Config represents a small collection of configuration values to fine tune the +// P2P network layer of a protocol stack. These values can be further extended by +// all registered services. +type Config struct { + // Name sets the instance name of the node. It must not contain the / character and is + // used in the devp2p node identifier. The instance name of geth is "geth". If no + // value is specified, the basename of the current executable is used. + Name string `toml:"-"` + + // UserIdent, if set, is used as an additional component in the devp2p node identifier. + UserIdent string `toml:",omitempty"` + + // Version should be set to the version number of the program. It is used + // in the devp2p node identifier. + Version string `toml:"-"` + + // DataDir is the file system folder the node should use for any data storage + // requirements. The configured data directory will not be directly shared with + // registered services, instead those can use utility methods to create/access + // databases or flat files. This enables ephemeral nodes which can fully reside + // in memory. + DataDir string + + // Configuration of peer-to-peer networking. + P2P p2p.Config + + // KeyStoreDir is the file system folder that contains private keys. The directory can + // be specified as a relative path, in which case it is resolved relative to the + // current directory. + // + // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of + // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory + // is created by New and destroyed when the node is stopped. + KeyStoreDir string `toml:",omitempty"` + + // ExternalSigner specifies an external URI for a clef-type signer + ExternalSigner string `toml:"omitempty"` + + // UseLightweightKDF lowers the memory and CPU requirements of the key store + // scrypt KDF at the expense of security. + UseLightweightKDF bool `toml:",omitempty"` + + // InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment. + InsecureUnlockAllowed bool `toml:",omitempty"` + + // NoUSB disables hardware wallet monitoring and connectivity. + NoUSB bool `toml:",omitempty"` + + // SmartCardDaemonPath is the path to the smartcard daemon's socket + SmartCardDaemonPath string `toml:",omitempty"` + + // IPCPath is the requested location to place the IPC endpoint. If the path is + // a simple file name, it is placed inside the data directory (or on the root + // pipe path on Windows), whereas if it's a resolvable path name (absolute or + // relative), then that specific path is enforced. An empty path disables IPC. + IPCPath string `toml:",omitempty"` + + // HTTPHost is the host interface on which to start the HTTP RPC server. If this + // field is empty, no HTTP API endpoint will be started. + HTTPHost string `toml:",omitempty"` + + // HTTPPort is the TCP port number on which to start the HTTP RPC server. The + // default zero value is/ valid and will pick a port number randomly (useful + // for ephemeral nodes). + HTTPPort int `toml:",omitempty"` + + // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting + // clients. Please be aware that CORS is a browser enforced security, it's fully + // useless for custom HTTP clients. + HTTPCors []string `toml:",omitempty"` + + // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. + // This is by default {'localhost'}. Using this prevents attacks like + // DNS rebinding, which bypasses SOP by simply masquerading as being within the same + // origin. These attacks do not utilize CORS, since they are not cross-domain. + // By explicitly checking the Host-header, the server will not allow requests + // made against the server with a malicious host domain. + // Requests using ip address directly are not affected + HTTPVirtualHosts []string `toml:",omitempty"` + + // HTTPModules is a list of API modules to expose via the HTTP RPC interface. + // If the module list is empty, all RPC API endpoints designated public will be + // exposed. + HTTPModules []string `toml:",omitempty"` + + // HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC + // interface. + HTTPTimeouts rpc.HTTPTimeouts + + // WSHost is the host interface on which to start the websocket RPC server. If + // this field is empty, no websocket API endpoint will be started. + WSHost string `toml:",omitempty"` + + // WSPort is the TCP port number on which to start the websocket RPC server. The + // default zero value is/ valid and will pick a port number randomly (useful for + // ephemeral nodes). + WSPort int `toml:",omitempty"` + + // WSOrigins is the list of domain to accept websocket requests from. Please be + // aware that the server can only act upon the HTTP request the client sends and + // cannot verify the validity of the request header. + WSOrigins []string `toml:",omitempty"` + + // WSModules is a list of API modules to expose via the websocket RPC interface. + // If the module list is empty, all RPC API endpoints designated public will be + // exposed. + WSModules []string `toml:",omitempty"` + + // WSExposeAll exposes all API modules via the WebSocket RPC interface rather + // than just the public ones. + // + // *WARNING* Only set this if the node is running in a trusted network, exposing + // private APIs to untrusted users is a major security risk. + WSExposeAll bool `toml:",omitempty"` + + // GraphQLHost is the host interface on which to start the GraphQL server. If this + // field is empty, no GraphQL API endpoint will be started. + GraphQLHost string `toml:",omitempty"` + + // GraphQLPort is the TCP port number on which to start the GraphQL server. The + // default zero value is/ valid and will pick a port number randomly (useful + // for ephemeral nodes). + GraphQLPort int `toml:",omitempty"` + + // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting + // clients. Please be aware that CORS is a browser enforced security, it's fully + // useless for custom HTTP clients. + GraphQLCors []string `toml:",omitempty"` + + // GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. + // This is by default {'localhost'}. Using this prevents attacks like + // DNS rebinding, which bypasses SOP by simply masquerading as being within the same + // origin. These attacks do not utilize CORS, since they are not cross-domain. + // By explicitly checking the Host-header, the server will not allow requests + // made against the server with a malicious host domain. + // Requests using ip address directly are not affected + GraphQLVirtualHosts []string `toml:",omitempty"` + + // Logger is a custom logger to use with the p2p.Server. + Logger log.Logger `toml:",omitempty"` + + staticNodesWarning bool + trustedNodesWarning bool + oldGethResourceWarning bool +} + +// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into +// account the set data folders as well as the designated platform we're currently +// running on. +func (c *Config) IPCEndpoint() string { + // Short circuit if IPC has not been enabled + if c.IPCPath == "" { + return "" + } + // On windows we can only use plain top-level pipes + if runtime.GOOS == "windows" { + if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { + return c.IPCPath + } + return `\\.\pipe\` + c.IPCPath + } + // Resolve names into the data directory full paths otherwise + if filepath.Base(c.IPCPath) == c.IPCPath { + if c.DataDir == "" { + return filepath.Join(os.TempDir(), c.IPCPath) + } + return filepath.Join(c.DataDir, c.IPCPath) + } + return c.IPCPath +} + +// NodeDB returns the path to the discovery node database. +func (c *Config) NodeDB() string { + if c.DataDir == "" { + return "" // ephemeral + } + return c.ResolvePath(datadirNodeDatabase) +} + +// DefaultIPCEndpoint returns the IPC path used by default. +func DefaultIPCEndpoint(clientIdentifier string) string { + if clientIdentifier == "" { + clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + if clientIdentifier == "" { + panic("empty executable name") + } + } + config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} + return config.IPCEndpoint() +} + +// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface +// and port parameters. +func (c *Config) HTTPEndpoint() string { + if c.HTTPHost == "" { + return "" + } + return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) +} + +// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface +// and port parameters. +func (c *Config) GraphQLEndpoint() string { + if c.GraphQLHost == "" { + return "" + } + return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort) +} + +// DefaultHTTPEndpoint returns the HTTP endpoint used by default. +func DefaultHTTPEndpoint() string { + config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} + return config.HTTPEndpoint() +} + +// WSEndpoint resolves a websocket endpoint based on the configured host interface +// and port parameters. +func (c *Config) WSEndpoint() string { + if c.WSHost == "" { + return "" + } + return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) +} + +// DefaultWSEndpoint returns the websocket endpoint used by default. +func DefaultWSEndpoint() string { + config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} + return config.WSEndpoint() +} + +// ExtRPCEnabled returns the indicator whether node enables the external +// RPC(http, ws or graphql). +func (c *Config) ExtRPCEnabled() bool { + return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != "" +} + +// NodeName returns the devp2p node identifier. +func (c *Config) NodeName() string { + name := c.name() + // Backwards compatibility: previous versions used title-cased "Geth", keep that. + if name == "geth" || name == "geth-testnet" { + name = "Geth" + } + if c.UserIdent != "" { + name += "/" + c.UserIdent + } + if c.Version != "" { + name += "/v" + c.Version + } + name += "/" + runtime.GOOS + "-" + runtime.GOARCH + name += "/" + runtime.Version() + return name +} + +func (c *Config) name() string { + if c.Name == "" { + progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") + if progname == "" { + panic("empty executable name, set Config.Name") + } + return progname + } + return c.Name +} + +// These resources are resolved differently for "geth" instances. +var isOldGethResource = map[string]bool{ + "chaindata": true, + "nodes": true, + "nodekey": true, + "static-nodes.json": false, // no warning for these because they have their + "trusted-nodes.json": false, // own separate warning. +} + +// ResolvePath resolves path in the instance directory. +func (c *Config) ResolvePath(path string) string { + if filepath.IsAbs(path) { + return path + } + if c.DataDir == "" { + return "" + } + // Backwards-compatibility: ensure that data directory files created + // by geth 1.4 are used if they exist. + if warn, isOld := isOldGethResource[path]; isOld { + oldpath := "" + if c.name() == "geth" { + oldpath = filepath.Join(c.DataDir, path) + } + if oldpath != "" && common.FileExist(oldpath) { + if warn { + c.warnOnce(&c.oldGethResourceWarning, "Using deprecated resource file %s, please move this file to the 'geth' subdirectory of datadir.", oldpath) + } + return oldpath + } + } + return filepath.Join(c.instanceDir(), path) +} + +func (c *Config) instanceDir() string { + if c.DataDir == "" { + return "" + } + return filepath.Join(c.DataDir, c.name()) +} + +// NodeKey retrieves the currently configured private key of the node, checking +// first any manually set key, falling back to the one found in the configured +// data folder. If no key can be found, a new one is generated. +func (c *Config) NodeKey() *ecdsa.PrivateKey { + // Use any specifically configured key. + if c.P2P.PrivateKey != nil { + return c.P2P.PrivateKey + } + // Generate ephemeral key if no datadir is being used. + if c.DataDir == "" { + key, err := crypto.GenerateKey() + if err != nil { + log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err)) + } + return key + } + + keyfile := c.ResolvePath(datadirPrivateKey) + if key, err := crypto.LoadECDSA(keyfile); err == nil { + return key + } + // No persistent key found, generate and store a new one. + key, err := crypto.GenerateKey() + if err != nil { + log.Crit(fmt.Sprintf("Failed to generate node key: %v", err)) + } + instanceDir := filepath.Join(c.DataDir, c.name()) + if err := os.MkdirAll(instanceDir, 0700); err != nil { + log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) + return key + } + keyfile = filepath.Join(instanceDir, datadirPrivateKey) + if err := crypto.SaveECDSA(keyfile, key); err != nil { + log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) + } + return key +} + +// StaticNodes returns a list of node enode URLs configured as static nodes. +func (c *Config) StaticNodes() []*enode.Node { + return c.parsePersistentNodes(&c.staticNodesWarning, c.ResolvePath(datadirStaticNodes)) +} + +// TrustedNodes returns a list of node enode URLs configured as trusted nodes. +func (c *Config) TrustedNodes() []*enode.Node { + return c.parsePersistentNodes(&c.trustedNodesWarning, c.ResolvePath(datadirTrustedNodes)) +} + +// parsePersistentNodes parses a list of discovery node URLs loaded from a .json +// file from within the data directory. +func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { + // Short circuit if no node config is present + if c.DataDir == "" { + return nil + } + if _, err := os.Stat(path); err != nil { + return nil + } + c.warnOnce(w, "Found deprecated node list file %s, please use the TOML config file instead.", path) + + // Load the nodes from the config file. + var nodelist []string + if err := common.LoadJSON(path, &nodelist); err != nil { + log.Error(fmt.Sprintf("Can't load node list file: %v", err)) + return nil + } + // Interpret the list as a discovery node array + var nodes []*enode.Node + for _, url := range nodelist { + if url == "" { + continue + } + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) + continue + } + nodes = append(nodes, node) + } + return nodes +} + +// AccountConfig determines the settings for scrypt and keydirectory +func (c *Config) AccountConfig() (int, int, string, error) { + scryptN := keystore.StandardScryptN + scryptP := keystore.StandardScryptP + if c.UseLightweightKDF { + scryptN = keystore.LightScryptN + scryptP = keystore.LightScryptP + } + + var ( + keydir string + err error + ) + switch { + case filepath.IsAbs(c.KeyStoreDir): + keydir = c.KeyStoreDir + case c.DataDir != "": + if c.KeyStoreDir == "" { + keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore) + } else { + keydir, err = filepath.Abs(c.KeyStoreDir) + } + case c.KeyStoreDir != "": + keydir, err = filepath.Abs(c.KeyStoreDir) + } + return scryptN, scryptP, keydir, err +} + +func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { + scryptN, scryptP, keydir, err := conf.AccountConfig() + var ephemeral string + if keydir == "" { + // There is no datadir. + keydir, err = ioutil.TempDir("", "go-ethereum-keystore") + ephemeral = keydir + } + + if err != nil { + return nil, "", err + } + if err := os.MkdirAll(keydir, 0700); err != nil { + return nil, "", err + } + // Assemble the account manager and supported backends + var backends []accounts.Backend + if len(conf.ExternalSigner) > 0 { + log.Info("Using external signer", "url", conf.ExternalSigner) + if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil { + backends = append(backends, extapi) + } else { + return nil, "", fmt.Errorf("error connecting to external signer: %v", err) + } + } + if len(backends) == 0 { + // For now, we're using EITHER external signer OR local signers. + // If/when we implement some form of lockfile for USB and keystore wallets, + // we can have both, but it's very confusing for the user to see the same + // accounts in both externally and locally, plus very racey. + backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP)) + if !conf.NoUSB { + // Start a USB hub for Ledger hardware wallets + if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) + } else { + backends = append(backends, ledgerhub) + } + // Start a USB hub for Trezor hardware wallets (HID version) + if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil { + log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) + } else { + backends = append(backends, trezorhub) + } + // Start a USB hub for Trezor hardware wallets (WebUSB version) + if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil { + log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) + } else { + backends = append(backends, trezorhub) + } + } + if len(conf.SmartCardDaemonPath) > 0 { + // Start a smart card hub + if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil { + log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) + } else { + backends = append(backends, schub) + } + } + } + + return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}, backends...), ephemeral, nil +} + +var warnLock sync.Mutex + +func (c *Config) warnOnce(w *bool, format string, args ...interface{}) { + warnLock.Lock() + defer warnLock.Unlock() + + if *w { + return + } + l := c.Logger + if l == nil { + l = log.Root() + } + l.Warn(fmt.Sprintf(format, args...)) + *w = true +} diff --git a/node_2/defaults.go b/node_2/defaults.go new file mode 100644 index 000000000000..9951aaa0265e --- /dev/null +++ b/node_2/defaults.go @@ -0,0 +1,113 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "os" + "os/user" + "path/filepath" + "runtime" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rpc" +) + +const ( + DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server + DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server + DefaultWSHost = "localhost" // Default host interface for the websocket RPC server + DefaultWSPort = 8546 // Default TCP port for the websocket RPC server + DefaultGraphQLHost = "localhost" // Default host interface for the GraphQL server + DefaultGraphQLPort = 8547 // Default TCP port for the GraphQL server +) + +// DefaultConfig contains reasonable default settings. +var DefaultConfig = Config{ + DataDir: DefaultDataDir(), + HTTPPort: DefaultHTTPPort, + HTTPModules: []string{"net", "web3"}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: DefaultWSPort, + WSModules: []string{"net", "web3"}, + GraphQLPort: DefaultGraphQLPort, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: ":30303", + MaxPeers: 50, + NAT: nat.Any(), + }, +} + +// DefaultDataDir is the default data directory to use for the databases and other +// persistence requirements. +func DefaultDataDir() string { + // Try to place the data folder in the user's home dir + home := homeDir() + if home != "" { + switch runtime.GOOS { + case "darwin": + return filepath.Join(home, "Library", "Ethereum") + case "windows": + // We used to put everything in %HOME%\AppData\Roaming, but this caused + // problems with non-typical setups. If this fallback location exists and + // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%. + fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum") + appdata := windowsAppData() + if appdata == "" || isNonEmptyDir(fallback) { + return fallback + } + return filepath.Join(appdata, "Ethereum") + default: + return filepath.Join(home, ".ethereum") + } + } + // As we cannot guess a stable location, return empty and handle later + return "" +} + +func windowsAppData() string { + v := os.Getenv("LOCALAPPDATA") + if v == "" { + // Windows XP and below don't have LocalAppData. Crash here because + // we don't support Windows XP and undefining the variable will cause + // other issues. + panic("environment variable LocalAppData is undefined") + } + return v +} + +func isNonEmptyDir(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return false + } + names, _ := f.Readdir(1) + f.Close() + return len(names) > 0 +} + +func homeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home + } + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" +} diff --git a/node_2/endpoints.go b/node_2/endpoints.go new file mode 100644 index 000000000000..0dce7b5acfe3 --- /dev/null +++ b/node_2/endpoints.go @@ -0,0 +1,99 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "net" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// StartHTTPEndpoint starts the HTTP RPC endpoint. +func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { + // start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, err + } + // make sure timeout values are meaningful + CheckTimeouts(&timeouts) + // Bundle and start the HTTP server + httpSrv := &http.Server{ + Handler: handler, + ReadTimeout: timeouts.ReadTimeout, + WriteTimeout: timeouts.WriteTimeout, + IdleTimeout: timeouts.IdleTimeout, + } + go httpSrv.Serve(listener) + return listener, err +} + +// startWSEndpoint starts a websocket endpoint. +func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { + // start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, err + } + wsSrv := &http.Server{Handler: handler} + go wsSrv.Serve(listener) + return listener, err +} + +// checkModuleAvailability checks that all names given in modules are actually +// available API services. It assumes that the MetadataApi module ("rpc") is always available; +// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. +func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available []string) { + availableSet := make(map[string]struct{}) + for _, api := range apis { + if _, ok := availableSet[api.Namespace]; !ok { + availableSet[api.Namespace] = struct{}{} + available = append(available, api.Namespace) + } + } + for _, name := range modules { + if _, ok := availableSet[name]; !ok && name != rpc.MetadataApi { + bad = append(bad, name) + } + } + return bad, available +} + +// CheckTimeouts ensures that timeout values are meaningful +func CheckTimeouts(timeouts *rpc.HTTPTimeouts) { + if timeouts.ReadTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout) + timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout + } + if timeouts.WriteTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout) + timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout + } + if timeouts.IdleTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", rpc.DefaultHTTPTimeouts.IdleTimeout) + timeouts.IdleTimeout = rpc.DefaultHTTPTimeouts.IdleTimeout + } +} diff --git a/node_2/errors.go b/node_2/errors.go new file mode 100644 index 000000000000..7ae4440208cc --- /dev/null +++ b/node_2/errors.go @@ -0,0 +1,63 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "errors" + "fmt" + "reflect" + "syscall" +) + +var ( + ErrDatadirUsed = errors.New("datadir already used by another process") + ErrNodeStopped = errors.New("node not started") + ErrNodeRunning = errors.New("node already running") + ErrServiceUnknown = errors.New("unknown service") + + datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} +) + +func convertFileLockError(err error) error { + if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { + return ErrDatadirUsed + } + return err +} + +// DuplicateServiceError is returned during Node startup if a registered service +// constructor returns a service of the same type that was already started. +type DuplicateServiceError struct { + Kind reflect.Type +} + +// Error generates a textual representation of the duplicate service error. +func (e *DuplicateServiceError) Error() string { + return fmt.Sprintf("duplicate service: %v", e.Kind) +} + +// StopError is returned if a Node fails to stop either any of its registered +// services or itself. +type StopError struct { + Server error + Services map[reflect.Type]error +} + +// Error generates a textual representation of the stop error. +func (e *StopError) Error() string { + return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services) +} diff --git a/node_2/node.go b/node_2/node.go new file mode 100644 index 000000000000..555868ecdcfb --- /dev/null +++ b/node_2/node.go @@ -0,0 +1,737 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/prometheus/tsdb/fileutil" +) + +// Node is a container on which services can be registered. +type Node struct { + eventmux *event.TypeMux // Event multiplexer used between the services of a stack + config *Config + accman *accounts.Manager + + ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop + instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory + + server *p2p.Server // Currently running P2P networking layer + + backends map[reflect.Type]Backend // TODO + services map[reflect.Type]Service // Currently running services + auxServices map[reflect.Type]AuxiliaryService // TODO + + rpcAPIs []rpc.API // List of APIs currently provided by the node + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + + ipcHandler *httpHandler // TODO + httpHandler *httpHandler // TODO + wsHandler *httpHandler // TODO + + stop chan struct{} // Channel to wait for termination notifications + lock sync.RWMutex + + log log.Logger +} + +// New creates a new P2P node, ready for protocol registration. +func New(conf *Config) (*Node, error) { + // Copy config and resolve the datadir so future changes to the current + // working directory don't affect the node. + confCopy := *conf + conf = &confCopy + if conf.DataDir != "" { + absdatadir, err := filepath.Abs(conf.DataDir) + if err != nil { + return nil, err + } + conf.DataDir = absdatadir + } + // Ensure that the instance name doesn't cause weird conflicts with + // other files in the data directory. + if strings.ContainsAny(conf.Name, `/\`) { + return nil, errors.New(`Config.Name must not contain '/' or '\'`) + } + if conf.Name == datadirDefaultKeyStore { + return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) + } + if strings.HasSuffix(conf.Name, ".ipc") { + return nil, errors.New(`Config.Name cannot end in ".ipc"`) + } + // Ensure that the AccountManager method works before the node has started. + // We rely on this in cmd/geth. + am, ephemeralKeystore, err := makeAccountManager(conf) + if err != nil { + return nil, err + } + if conf.Logger == nil { + conf.Logger = log.New() + } + // Note: any interaction with Config that would create/touch files + // in the data directory or instance directory is delayed until Start. + return &Node{ + accman: am, + ephemeralKeystore: ephemeralKeystore, + config: conf, + ipcHandler: &httpHandler{ + endpoint: endpoint{ + endpoint: conf.IPCEndpoint(), + }, + }, + httpHandler: &httpHandler{ + endpoint: endpoint{ + endpoint: conf.HTTPEndpoint(), + host: conf.HTTPHost, + port: conf.HTTPPort, + }, + CorsAllowedOrigins: conf.HTTPCors, + Vhosts: conf.HTTPVirtualHosts, + Timeouts: conf.HTTPTimeouts, + WsOrigins: conf.WSOrigins, // TODO do i already add this? + RPCAllowed: true, + + }, + wsHandler: &httpHandler{ + endpoint: endpoint{ + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, + }, + WsOrigins: conf.WSOrigins, + WSAllowed: true, + }, + eventmux: new(event.TypeMux), + log: conf.Logger, + }, nil +} + +// Close stops the Node and releases resources acquired in +// Node constructor New. +func (n *Node) Close() error { + var errs []error + + // Terminate all subsystems and collect any errors + if err := n.Stop(); err != nil && err != ErrNodeStopped { + errs = append(errs, err) + } + if err := n.accman.Close(); err != nil { + errs = append(errs, err) + } + // Report any errors that might have occurred + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return fmt.Errorf("%v", errs) + } +} + +// Register injects a new service into the node's stack. The service created by +// the passed constructor must be unique in its type with regard to sibling ones. +func (n *Node) Register(constructor ServiceConstructor) error { + n.lock.Lock() + defer n.lock.Unlock() + + // TODO this should create the service and add it to the maps already + + //if n.server != nil { + // return ErrNodeRunning + //} + //n.serviceFuncs = append(n.serviceFuncs, constructor) + return nil +} + +// Start creates a live P2P node and starts running it. +func (n *Node) Start() error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node's already running + if n.server != nil { + return ErrNodeRunning + } + if err := n.openDataDir(); err != nil { + return err + } + + // Initialize the p2p server. This creates the node key and + // discovery databases. + running := &p2p.Server{} + + running.PrivateKey = n.config.NodeKey() + running.Name = n.config.NodeName() + running.Logger = n.log + if running.StaticNodes == nil { + running.StaticNodes = n.config.StaticNodes() + } + if running.TrustedNodes == nil { + running.TrustedNodes = n.config.TrustedNodes() + } + if running.NodeDatabase == "" { + running.NodeDatabase = n.config.NodeDB() + } + n.log.Info("Starting peer-to-peer node", "instance", running.Name) + + // TODO document + backends := make(map[reflect.Type]Backend) + services := make(map[reflect.Type]Service) + auxServices := make(map[reflect.Type]AuxiliaryService) + + // Gather the protocols and start the freshly assembled P2P server + for _, backend := range backends { + running.Protocols = append(running.Protocols, backend.Protocols()...) + } + if err := running.Start(); err != nil { + return convertFileLockError(err) + } + + //TODO instead of constructor, make it actually construct the backends, services and aux services? + + // TODO this should just check for duplicates, and then start the backends, services, and auxServices + + var started []reflect.Type + + // Start each of the backends + for kind, backend := range backends { + if err := backend.Start(); err != nil { + for _, kind := range started { + services[kind].Stop() + } + running.Stop() + + return err + } + // Mark the service started for potential cleanup + started = append(started, kind) + } + + // Start each of the services + for kind, service := range services { + // Start the next service, stopping all previous upon failure + if err := service.Start(); err != nil { + for _, kind := range started { + services[kind].Stop() + } + running.Stop() + + return err + } + // Mark the service started for potential cleanup + started = append(started, kind) + } + + // Gather all the possible APIs to surface + apis := n.apis() + for _, backend := range backends { + apis = append(apis, backend.APIs()...) + } + for _, service := range services { + apis = append(apis, service.APIs()...) + } + + // Lastly, start the configured RPC interfaces + if err := n.startRPC(apis); err != nil { + for _, service := range services { + service.Stop() + } + running.Stop() + return err + } + + // Finish initializing the startup + n.backends = backends + n.services = services + n.auxServices = auxServices + n.server = running + n.stop = make(chan struct{}) + return nil +} + +// Config returns the configuration of node. +func (n *Node) Config() *Config { + return n.config +} + +func (n *Node) openDataDir() error { + if n.config.DataDir == "" { + return nil // ephemeral + } + + instdir := filepath.Join(n.config.DataDir, n.config.name()) + if err := os.MkdirAll(instdir, 0700); err != nil { + return err + } + // Lock the instance directory to prevent concurrent use by another instance as well as + // accidental use of the instance directory as a database. + release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) + if err != nil { + return convertFileLockError(err) + } + n.instanceDirLock = release + return nil +} + +// startRPC is a helper method to start all the various RPC endpoints during node +// startup. It's not meant to be called at any time afterwards as it makes certain +// assumptions about the state of the node. +func (n *Node) startRPC(apis []rpc.API) error { + // Start the various API endpoints, terminating all in case of errors + if err := n.startInProc(apis); err != nil { + return err + } + if err := n.startIPC(apis); err != nil { + n.stopInProc() + return err + } + if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { + n.stopIPC() + n.stopInProc() + return err + } + // if endpoints are not the same, start separate servers + if n.httpEndpoint != n.wsEndpoint { + if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { + n.stopHTTP() + n.stopIPC() + n.stopInProc() + return err + } + } + + // All API endpoints started successfully + n.rpcAPIs = apis + return nil +} + +// startInProc initializes an in-process RPC endpoint. +func (n *Node) startInProc(apis []rpc.API) error { + // Register all the APIs exposed by the services + handler := rpc.NewServer() + for _, api := range apis { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + n.log.Debug("InProc registered", "namespace", api.Namespace) + } + n.inprocHandler = handler + return nil +} + +// stopInProc terminates the in-process RPC endpoint. +func (n *Node) stopInProc() { + if n.inprocHandler != nil { + n.inprocHandler.Stop() + n.inprocHandler = nil + } +} + +// startIPC initializes and starts the IPC RPC endpoint. +func (n *Node) startIPC(apis []rpc.API) error { + if n.ipcEndpoint == "" { + return nil // IPC disabled. + } + listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) + if err != nil { + return err + } + n.ipcListener = listener + n.ipcHandler = handler + n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) + return nil +} + +// stopIPC terminates the IPC RPC endpoint. +func (n *Node) stopIPC() { + if n.ipcListener != nil { + n.ipcListener.Close() + n.ipcListener = nil + + n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint) + } + if n.ipcHandler != nil { + n.ipcHandler.Stop() + n.ipcHandler = nil + } +} + +// startHTTP initializes and starts the HTTP RPC endpoint. +func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { + // Short circuit if the HTTP endpoint isn't being exposed + if endpoint == "" { + return nil + } + // register apis and create handler stack + srv := rpc.NewServer() + err := RegisterApisFromWhitelist(apis, modules, srv, false) + if err != nil { + return err + } + handler := NewHTTPHandlerStack(srv, cors, vhosts) + // wrap handler in websocket handler only if websocket port is the same as http rpc + if n.httpEndpoint == n.wsEndpoint { + handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) + } + listener, err := StartHTTPEndpoint(endpoint, timeouts, handler) + if err != nil { + return err + } + n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), + "cors", strings.Join(cors, ","), + "vhosts", strings.Join(vhosts, ",")) + if n.httpEndpoint == n.wsEndpoint { + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) + } + // All listeners booted successfully + n.httpEndpoint = endpoint + n.httpListener = listener + n.httpHandler = srv + + return nil +} + +// stopHTTP terminates the HTTP RPC endpoint. +func (n *Node) stopHTTP() { + if n.httpListener != nil { + url := fmt.Sprintf("http://%v/", n.httpListener.Addr()) + n.httpListener.Close() + n.httpListener = nil + n.log.Info("HTTP endpoint closed", "url", url) + } + if n.httpHandler != nil { + n.httpHandler.Stop() + n.httpHandler = nil + } +} + +// startWS initializes and starts the websocket RPC endpoint. +func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error { + // Short circuit if the WS endpoint isn't being exposed + if endpoint == "" { + return nil + } + + srv := rpc.NewServer() + handler := srv.WebsocketHandler(wsOrigins) + err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) + if err != nil { + return err + } + listener, err := startWSEndpoint(endpoint, handler) + if err != nil { + return err + } + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) + // All listeners booted successfully + n.wsEndpoint = endpoint + n.wsListener = listener + n.wsHandler = srv + + return nil +} + +// stopWS terminates the websocket RPC endpoint. +func (n *Node) stopWS() { + if n.wsListener != nil { + n.wsListener.Close() + n.wsListener = nil + + n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsEndpoint)) + } + if n.wsHandler != nil { + n.wsHandler.Stop() + n.wsHandler = nil + } +} + +// Stop terminates a running node along with all it's services. In the node was +// not started, an error is returned. +func (n *Node) Stop() error { + n.lock.Lock() + defer n.lock.Unlock() + + // Short circuit if the node's not running + if n.server == nil { + return ErrNodeStopped + } + + // Terminate the API, services and the p2p server. + n.stopWS() + n.stopHTTP() + n.stopIPC() + n.rpcAPIs = nil + failure := &StopError{ + Services: make(map[reflect.Type]error), + } + for kind, service := range n.services { + if err := service.Stop(); err != nil { + failure.Services[kind] = err + } + } + n.server.Stop() + n.services = nil + n.server = nil + + // Release instance directory lock. + if n.instanceDirLock != nil { + if err := n.instanceDirLock.Release(); err != nil { + n.log.Error("Can't release datadir lock", "err", err) + } + n.instanceDirLock = nil + } + + // unblock n.Wait + close(n.stop) + + // Remove the keystore if it was created ephemerally. + var keystoreErr error + if n.ephemeralKeystore != "" { + keystoreErr = os.RemoveAll(n.ephemeralKeystore) + } + + if len(failure.Services) > 0 { + return failure + } + if keystoreErr != nil { + return keystoreErr + } + return nil +} + +// Wait blocks the thread until the node is stopped. If the node is not running +// at the time of invocation, the method immediately returns. +func (n *Node) Wait() { + n.lock.RLock() + if n.server == nil { + n.lock.RUnlock() + return + } + stop := n.stop + n.lock.RUnlock() + + <-stop +} + +// Restart terminates a running node and boots up a new one in its place. If the +// node isn't running, an error is returned. +func (n *Node) Restart() error { + if err := n.Stop(); err != nil { + return err + } + if err := n.Start(); err != nil { + return err + } + return nil +} + +// Attach creates an RPC client attached to an in-process API handler. +func (n *Node) Attach() (*rpc.Client, error) { + n.lock.RLock() + defer n.lock.RUnlock() + + if n.server == nil { + return nil, ErrNodeStopped + } + return rpc.DialInProc(n.inprocHandler), nil +} + +// RPCHandler returns the in-process RPC request handler. +func (n *Node) RPCHandler() (*rpc.Server, error) { + n.lock.RLock() + defer n.lock.RUnlock() + + if n.inprocHandler == nil { + return nil, ErrNodeStopped + } + return n.inprocHandler, nil +} + +// Server retrieves the currently running P2P network layer. This method is meant +// only to inspect fields of the currently running server, life cycle management +// should be left to this Node entity. +func (n *Node) Server() *p2p.Server { + n.lock.RLock() + defer n.lock.RUnlock() + + return n.server +} + +// Service retrieves a currently running service registered of a specific type. +func (n *Node) Service(service interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if n.server == nil { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(service).Elem() + if running, ok := n.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + +// DataDir retrieves the current datadir used by the protocol stack. +// Deprecated: No files should be stored in this directory, use InstanceDir instead. +func (n *Node) DataDir() string { + return n.config.DataDir +} + +// InstanceDir retrieves the instance directory used by the protocol stack. +func (n *Node) InstanceDir() string { + return n.config.instanceDir() +} + +// AccountManager retrieves the account manager used by the protocol stack. +func (n *Node) AccountManager() *accounts.Manager { + return n.accman +} + +// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. +func (n *Node) IPCEndpoint() string { + return n.ipcEndpoint +} + +// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. +func (n *Node) HTTPEndpoint() string { + n.lock.Lock() + defer n.lock.Unlock() + + if n.httpListener != nil { + return n.httpListener.Addr().String() + } + return n.httpEndpoint +} + +// WSEndpoint retrieves the current WS endpoint used by the protocol stack. +func (n *Node) WSEndpoint() string { + n.lock.Lock() + defer n.lock.Unlock() + + if n.wsListener != nil { + return n.wsListener.Addr().String() + } + return n.wsEndpoint +} + +// EventMux retrieves the event multiplexer used by all the network services in +// the current protocol stack. +func (n *Node) EventMux() *event.TypeMux { + return n.eventmux +} + +// OpenDatabase opens an existing database with the given name (or creates one if no +// previous can be found) from within the node's instance directory. If the node is +// ephemeral, a memory database is returned. +func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { + if n.config.DataDir == "" { + return rawdb.NewMemoryDatabase(), nil + } + return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace) +} + +// OpenDatabaseWithFreezer opens an existing database with the given name (or +// creates one if no previous can be found) from within the node's data directory, +// also attaching a chain freezer to it that moves ancient chain data from the +// database to immutable append-only files. If the node is an ephemeral one, a +// memory database is returned. +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { + if n.config.DataDir == "" { + return rawdb.NewMemoryDatabase(), nil + } + root := n.config.ResolvePath(name) + + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = n.config.ResolvePath(freezer) + } + return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) +} + +// ResolvePath returns the absolute path of a resource in the instance directory. +func (n *Node) ResolvePath(x string) string { + return n.config.ResolvePath(x) +} + +// apis returns the collection of RPC descriptors this node offers. +func (n *Node) apis() []rpc.API { + return []rpc.API{ + { + Namespace: "admin", + Version: "1.0", + Service: NewPrivateAdminAPI(n), + }, { + Namespace: "admin", + Version: "1.0", + Service: NewPublicAdminAPI(n), + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: debug.Handler, + }, { + Namespace: "web3", + Version: "1.0", + Service: NewPublicWeb3API(n), + Public: true, + }, + } +} + +// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, +// and then registers all of the APIs exposed by the services. +func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { + if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { + log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) + } + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := srv.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + } + return nil +} diff --git a/node_2/rpcstack.go b/node_2/rpcstack.go new file mode 100644 index 000000000000..cec762c0633a --- /dev/null +++ b/node_2/rpcstack.go @@ -0,0 +1,185 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "compress/gzip" + "github.com/ethereum/go-ethereum/rpc" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/log" + "github.com/rs/cors" +) + +type httpHandler struct { + handler http.Handler + Srv *rpc.Server + + endpoint endpoint + + Whitelist []string + + CorsAllowedOrigins []string + Vhosts []string + WsOrigins []string + Timeouts rpc.HTTPTimeouts + + Listener net.Listener + + RPCAllowed bool + WSAllowed bool +} + +type endpoint struct { + endpoint string + host string + port int +} + +// NewHTTPHandlerStack returns wrapped http-related handlers +func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { + // Wrap the CORS-handler within a host-handler + handler := newCorsHandler(srv, cors) + handler = newVHostHandler(vhosts, handler) + return newGzipHandler(handler) +} + +func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { + // disable CORS support if user has not specified a custom CORS configuration + if len(allowedOrigins) == 0 { + return srv + } + c := cors.New(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{http.MethodPost, http.MethodGet}, + MaxAge: 600, + AllowedHeaders: []string{"*"}, + }) + return c.Handler(srv) +} + +// virtualHostHandler is a handler which validates the Host-header of incoming requests. +// Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to +// the service ip address (but without CORS headers). By verifying the targeted virtual host, we can +// ensure that it's a destination that the node operator has defined. +type virtualHostHandler struct { + vhosts map[string]struct{} + next http.Handler +} + +func newVHostHandler(vhosts []string, next http.Handler) http.Handler { + vhostMap := make(map[string]struct{}) + for _, allowedHost := range vhosts { + vhostMap[strings.ToLower(allowedHost)] = struct{}{} + } + return &virtualHostHandler{vhostMap, next} +} + +// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler +func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // if r.Host is not set, we can continue serving since a browser would set the Host header + if r.Host == "" { + h.next.ServeHTTP(w, r) + return + } + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + // Either invalid (too many colons) or no port specified + host = r.Host + } + if ipAddr := net.ParseIP(host); ipAddr != nil { + // It's an IP address, we can serve that + h.next.ServeHTTP(w, r) + return + + } + // Not an IP address, but a hostname. Need to validate + if _, exist := h.vhosts["*"]; exist { + h.next.ServeHTTP(w, r) + return + } + if _, exist := h.vhosts[host]; exist { + h.next.ServeHTTP(w, r) + return + } + http.Error(w, "invalid host specified", http.StatusForbidden) +} + +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(ioutil.Discard) + return w + }, +} + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w *gzipResponseWriter) WriteHeader(status int) { + w.Header().Del("Content-Length") + w.ResponseWriter.WriteHeader(status) +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func newGzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Encoding", "gzip") + + gz := gzPool.Get().(*gzip.Writer) + defer gzPool.Put(gz) + + gz.Reset(w) + defer gz.Close() + + next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) + }) +} + +// NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade +// request to the websocket protocol. If not, serves the the request with the http handler. +func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isWebsocket(r) { + ws.ServeHTTP(w, r) + log.Debug("serving websocket request") + return + } + + h.ServeHTTP(w, r) + }) +} + +// isWebsocket checks the header of an http request for a websocket upgrade request. +func isWebsocket(r *http.Request) bool { + return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && + strings.ToLower(r.Header.Get("Connection")) == "upgrade" +} diff --git a/node_2/service.go b/node_2/service.go new file mode 100644 index 000000000000..aabafa0737b9 --- /dev/null +++ b/node_2/service.go @@ -0,0 +1,133 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node_2 + +import ( + "path/filepath" + "reflect" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// ServiceContext is a collection of service independent options inherited from +// the protocol stack, that is passed to all constructors to be optionally used; +// as well as utility methods to operate on the service environment. +type ServiceContext struct { + services map[reflect.Type]Service // Index of the already constructed services + Config Config + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + AccountManager *accounts.Manager // Account manager created by the node. +} + +// OpenDatabase opens an existing database with the given name (or creates one +// if no previous can be found) from within the node's data directory. If the +// node is an ephemeral one, a memory database is returned. +func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) { + if ctx.Config.DataDir == "" { + return rawdb.NewMemoryDatabase(), nil + } + return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace) +} + +// OpenDatabaseWithFreezer opens an existing database with the given name (or +// creates one if no previous can be found) from within the node's data directory, +// also attaching a chain freezer to it that moves ancient chain data from the +// database to immutable append-only files. If the node is an ephemeral one, a +// memory database is returned. +func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { + if ctx.Config.DataDir == "" { + return rawdb.NewMemoryDatabase(), nil + } + root := ctx.Config.ResolvePath(name) + + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = ctx.Config.ResolvePath(freezer) + } + return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) +} + +// ResolvePath resolves a user path into the data directory if that was relative +// and if the user actually uses persistent storage. It will return an empty string +// for emphemeral storage and the user's own input for absolute paths. +func (ctx *ServiceContext) ResolvePath(path string) string { + return ctx.Config.ResolvePath(path) +} + +// Service retrieves a currently running service registered of a specific type. +func (ctx *ServiceContext) Service(service interface{}) error { + element := reflect.ValueOf(service).Elem() + if running, ok := ctx.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + +// ExtRPCEnabled returns the indicator whether node enables the external +// RPC(http, ws or graphql). +func (ctx *ServiceContext) ExtRPCEnabled() bool { + return ctx.Config.ExtRPCEnabled() +} + +// ServiceConstructor is the function signature of the constructors needed to be +// registered for service instantiation. +type ServiceConstructor func(ctx *ServiceContext) (Service, error) + +// Backend is an individual protocol that can be registered into a node. +// +// Notes: +// +// • Backend service life-cycle management is delegated to the node. The backend service is allowed to +// initialize itself upon creation, but no goroutines should be spun up outside of the +// Start method. +type Backend interface { + // Protocols retrieves the P2P protocols the service wishes to start. + Protocols() []p2p.Protocol + + Service +} + +// TODO document +type Service interface { + // APIs retrieves the list of RPC descriptors the service provides + APIs() []rpc.API + + // Start is called after all services have been constructed and the networking + // layer was also initialized to spawn any goroutines required by the service. + Start() error + + // Stop terminates all goroutines belonging to the service, blocking until they + // are all terminated. + Stop() error +} + +// TODO document +type AuxiliaryService interface { + Start() error + + Stop() error +} + + From d44ac535ea88fb464c19d25ed9d1d88d6e5aa438 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 20 Apr 2020 16:59:42 +0200 Subject: [PATCH 002/160] httpHandler -> httpServer --- node_2/node.go | 12 ++++++------ node_2/rpcstack.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/node_2/node.go b/node_2/node.go index 555868ecdcfb..9668fee80f81 100644 --- a/node_2/node.go +++ b/node_2/node.go @@ -54,9 +54,9 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - ipcHandler *httpHandler // TODO - httpHandler *httpHandler // TODO - wsHandler *httpHandler // TODO + ipcHandler *httpServer // TODO + httpHandler *httpServer // TODO + wsHandler *httpServer // TODO stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -103,12 +103,12 @@ func New(conf *Config) (*Node, error) { accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, - ipcHandler: &httpHandler{ + ipcHandler: &httpServer{ endpoint: endpoint{ endpoint: conf.IPCEndpoint(), }, }, - httpHandler: &httpHandler{ + httpHandler: &httpServer{ endpoint: endpoint{ endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, @@ -121,7 +121,7 @@ func New(conf *Config) (*Node, error) { RPCAllowed: true, }, - wsHandler: &httpHandler{ + wsHandler: &httpServer{ endpoint: endpoint{ endpoint: conf.WSEndpoint(), host: conf.WSHost, diff --git a/node_2/rpcstack.go b/node_2/rpcstack.go index cec762c0633a..eb3928655782 100644 --- a/node_2/rpcstack.go +++ b/node_2/rpcstack.go @@ -30,7 +30,7 @@ import ( "github.com/rs/cors" ) -type httpHandler struct { +type httpServer struct { handler http.Handler Srv *rpc.Server From ec0c79155fd39a9750bfbaa1fe19eb4d8c691ce4 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Apr 2020 14:49:12 +0200 Subject: [PATCH 003/160] saving progress, working on ethstats impl --- cmd/faucet/faucet.go | 7 +- cmd/utils/flags.go | 4 +- ethstats/ethstats.go | 20 +- node/node.go | 132 +++++--- node/rpcstack.go | 25 ++ node/service.go | 36 ++- node_2/api.go | 317 ------------------- node_2/config.go | 545 -------------------------------- node_2/defaults.go | 113 ------- node_2/endpoints.go | 99 ------ node_2/errors.go | 63 ---- node_2/node.go | 737 ------------------------------------------- node_2/rpcstack.go | 185 ----------- node_2/service.go | 133 -------- 14 files changed, 158 insertions(+), 2258 deletions(-) delete mode 100644 node_2/api.go delete mode 100644 node_2/config.go delete mode 100644 node_2/defaults.go delete mode 100644 node_2/endpoints.go delete mode 100644 node_2/errors.go delete mode 100644 node_2/node.go delete mode 100644 node_2/rpcstack.go delete mode 100644 node_2/service.go diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 5a905c4f129a..475430e7f95a 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -236,7 +236,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u return nil, err } // Assemble the Ethereum light client protocol - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + if err := stack.RegisterBackendLifecycle(func(stack *node.Node) (node.Backend, error) { cfg := eth.DefaultConfig cfg.SyncMode = downloader.LightSync cfg.NetworkId = network @@ -247,10 +247,9 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u } // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { var serv *les.LightEthereum - ctx.Service(&serv) - return ethstats.New(stats, nil, serv) + return ethstats.New(stack, stats, nil, serv) }); err != nil { return nil, err } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 68c3449fe544..e179cb9669c9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1725,7 +1725,7 @@ func RegisterShhService(stack *node.Node, cfg *whisper.Config) { // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. func RegisterEthStatsService(stack *node.Node, url string) { - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + if err := stack.Register(func(stack *node.Node) (node.AuxiliaryService, error) { // Retrieve both eth and les services var ethServ *eth.Ethereum ctx.Service(ðServ) @@ -1742,7 +1742,7 @@ func RegisterEthStatsService(stack *node.Node, url string) { // RegisterGraphQLService is a utility function to construct a new service and register it against a node. func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { // Try to construct the GraphQL service backed by a full node var ethServ *eth.Ethereum if err := ctx.Service(ðServ); err == nil { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index b60ac56eabce..7d630f7b9cf7 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/node" "math/big" "net/http" "regexp" @@ -40,7 +41,6 @@ import ( "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" ) @@ -83,7 +83,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) { +func New(node *node.Node, url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (node.AuxiliaryService, error) { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) @@ -98,6 +98,7 @@ func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Servic engine = lesServ.Engine() } return &Service{ + server: node.Server(), eth: ethServ, les: lesServ, engine: engine, @@ -109,17 +110,8 @@ func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Servic }, nil } -// Protocols implements node.Service, returning the P2P network protocols used -// by the stats service (nil as it doesn't use the devp2p overlay network). -func (s *Service) Protocols() []p2p.Protocol { return nil } - -// APIs implements node.Service, returning the RPC API endpoints provided by the -// stats service (nil as it doesn't provide any user callable APIs). -func (s *Service) APIs() []rpc.API { return nil } - // Start implements node.Service, starting up the monitoring and reporting daemon. -func (s *Service) Start(server *p2p.Server) error { - s.server = server +func (s *Service) Start() error { go s.loop() log.Info("Stats daemon started") @@ -132,6 +124,10 @@ func (s *Service) Stop() error { return nil } +func (s *Service) Server() (*node.HttpServer, error) { + return nil, nil +} + // loop keeps trying to connect to the netstats server, reporting chain events // until termination. func (s *Service) loop() { diff --git a/node/node.go b/node/node.go index 329ff425b98a..14bc99b726bc 100644 --- a/node/node.go +++ b/node/node.go @@ -20,8 +20,6 @@ import ( "context" "errors" "fmt" - "net" - "net/http" "os" "path/filepath" "reflect" @@ -48,29 +46,23 @@ type Node struct { ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory - serverConfig p2p.Config + // TODO: removed p2pConfig b/c p2pServer already contains p2pConfig (is there a reason for it to be duplicated? server *p2p.Server // Currently running P2P networking layer - serviceFuncs []ServiceConstructor // Service constructors (in dependency order) - services map[reflect.Type]Service // Currently running services + serviceConstructors []ServiceConstructor // Service constructors (in dependency order) + auxServiceConstructors []AuxiliarServiceConstructor // AuxiliaryService constructors + + backend Backend // The registered Backend of the node + services map[reflect.Type]Service // Currently running services + auxServices map[reflect.Type]AuxiliaryService // Currently running auxiliary services rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - ipcEndpoint string // IPC endpoint to listen at (empty = IPC disabled) - ipcListener net.Listener // IPC RPC listener socket to serve API requests - ipcHandler *rpc.Server // IPC RPC request handler to process the API requests - - httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled) - httpWhitelist []string // HTTP RPC modules to allow through this endpoint - httpListenerAddr net.Addr // Address of HTTP RPC listener socket serving API requests - httpServer *http.Server // HTTP RPC HTTP server - httpHandler *rpc.Server // HTTP RPC request handler to process the API requests + ipcHandler *HttpServer // TODO + httpHandler *HttpServer // TODO + wsHandler *HttpServer // TODO - wsEndpoint string // WebSocket endpoint (interface + port) to listen at (empty = WebSocket disabled) - wsListenerAddr net.Addr // Address of WebSocket RPC listener socket serving API requests - wsHTTPServer *http.Server // WebSocket RPC HTTP server - wsHandler *rpc.Server // WebSocket RPC request handler to process the API requests stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -149,19 +141,73 @@ func (n *Node) Close() error { } } +// TODO document +func (n * Node) RegisterBackendLifecycle(constructor BackendConstructor) error { + n.lock.Lock() + defer n.lock.Unlock() + + if n.running() { + return ErrNodeRunning + } + + backend, err := constructor(n) + if err != nil { + return err + } + n.backend = backend + return nil +} + // Register injects a new service into the node's stack. The service created by // the passed constructor must be unique in its type with regard to sibling ones. -func (n *Node) Register(constructor ServiceConstructor) error { +func (n *Node) RegisterServiceLifecycle(constructor ServiceConstructor) error { n.lock.Lock() defer n.lock.Unlock() - if n.server != nil { + if n.running() { + return ErrNodeRunning + } + + service, err := constructor(n) + if err != nil { + return err + } + kind := reflect.TypeOf(service) + if _, exists := n.services[kind]; exists { + return &DuplicateServiceError{Kind: kind} + } + n.services[kind] = service + + return nil +} + +// TODO document +func (n *Node) RegisterAuxServiceLifecycle(constructor AuxiliaryServiceConstructor) error { + n.lock.Lock() + defer n.lock.Unlock() + + if n.running() { return ErrNodeRunning } - n.serviceFuncs = append(n.serviceFuncs, constructor) + + service, err := constructor(n) + if err != nil { + return err + } + kind := reflect.TypeOf(service) + if _, exists := n.auxServices[kind]; exists { + return &DuplicateServiceError{Kind: kind} + } + n.auxServices[kind] = service + return nil } +// running returns true if the node's p2p server is already running +func (n *Node) running() bool { + return n.server != nil +} + // Start creates a live P2P node and starts running it. func (n *Node) Start() error { n.lock.Lock() @@ -195,28 +241,28 @@ func (n *Node) Start() error { // Otherwise copy and specialize the P2P configuration services := make(map[reflect.Type]Service) - for _, constructor := range n.serviceFuncs { - // Create a new context for the particular service - ctx := &ServiceContext{ - Config: *n.config, - services: make(map[reflect.Type]Service), - EventMux: n.eventmux, - AccountManager: n.accman, - } - for kind, s := range services { // copy needed for threaded access - ctx.services[kind] = s - } - // Construct and save the service - service, err := constructor(ctx) - if err != nil { - return err - } - kind := reflect.TypeOf(service) - if _, exists := services[kind]; exists { - return &DuplicateServiceError{Kind: kind} - } - services[kind] = service - } + //for _, constructor := range n.serviceFuncs { + // // Create a new context for the particular service + // ctx := &ServiceContext{ + // Config: *n.config, + // services: make(map[reflect.Type]Service), + // EventMux: n.eventmux, + // AccountManager: n.accman, + // } + // for kind, s := range services { // copy needed for threaded access + // ctx.services[kind] = s + // } + // // Construct and save the service + // service, err := constructor(ctx) + // if err != nil { + // return err + // } + // kind := reflect.TypeOf(service) + // if _, exists := services[kind]; exists { + // return &DuplicateServiceError{Kind: kind} + // } + // services[kind] = service + //} // Gather the protocols and start the freshly assembled P2P server for _, service := range services { running.Protocols = append(running.Protocols, service.Protocols()...) diff --git a/node/rpcstack.go b/node/rpcstack.go index 70aa0d4555e6..86c7b6fd683e 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -18,6 +18,7 @@ package node import ( "compress/gzip" + "github.com/ethereum/go-ethereum/rpc" "io" "io/ioutil" "net" @@ -29,6 +30,30 @@ import ( "github.com/rs/cors" ) +type HttpServer struct { + handler http.Handler + Srv *rpc.Server + Listener net.Listener + + endpoint endpoint + + Whitelist []string + + CorsAllowedOrigins []string + Vhosts []string + WsOrigins []string + Timeouts rpc.HTTPTimeouts + + RPCAllowed bool + WSAllowed bool +} + +type endpoint struct { + endpoint string + host string + port int +} + // NewHTTPHandlerStack returns wrapped http-related handlers func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { // Wrap the CORS-handler within a host-handler diff --git a/node/service.go b/node/service.go index ef5b995e4b39..94bb012c699e 100644 --- a/node/service.go +++ b/node/service.go @@ -91,9 +91,23 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.Config.ExtRPCEnabled() } +// TODO document +type BackendConstructor func(node *Node) (Backend, error) + // ServiceConstructor is the function signature of the constructors needed to be // registered for service instantiation. -type ServiceConstructor func(ctx *ServiceContext) (Service, error) +type ServiceConstructor func(node *Node) (Service, error) + +//TODO document +type AuxiliaryServiceConstructor func(node *Node) (AuxiliaryService, error) + +type Backend interface { + // Protocols retrieves the P2P protocols the service wishes to start. + Protocols() []p2p.Protocol + + // Backend also implements the Service interface. + Service +} // Service is an individual protocol that can be registered into a node. // @@ -106,15 +120,27 @@ type ServiceConstructor func(ctx *ServiceContext) (Service, error) // • Restart logic is not required as the node will create a fresh instance // every time a service is started. type Service interface { - // Protocols retrieves the P2P protocols the service wishes to start. - Protocols() []p2p.Protocol - // APIs retrieves the list of RPC descriptors the service provides APIs() []rpc.API + // Service also implements Lifecycle + Lifecycle +} + +// TODO document +type AuxiliaryService interface { + // TODO document + Server() (*HttpServer, error) + + // AuxiliaryService also implements Lifecycle + Lifecycle +} + +// TODO, this might be overkill +type Lifecycle interface { // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. - Start(server *p2p.Server) error + Start() error // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. diff --git a/node_2/api.go b/node_2/api.go deleted file mode 100644 index 53f4f2dbc9c1..000000000000 --- a/node_2/api.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "context" - "fmt" - "strings" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rpc" -) - -// PrivateAdminAPI is the collection of administrative API methods exposed only -// over a secure RPC channel. -type PrivateAdminAPI struct { - node *Node // Node interfaced by this API -} - -// NewPrivateAdminAPI creates a new API definition for the private admin methods -// of the node itself. -func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { - return &PrivateAdminAPI{node: node} -} - -// AddPeer requests connecting to a remote node, and also maintaining the new -// connection at all times, even reconnecting if it is lost. -func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { - // Make sure the server is running, fail otherwise - server := api.node.Server() - if server == nil { - return false, ErrNodeStopped - } - // Try to add the url as a static peer and return - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - server.AddPeer(node) - return true, nil -} - -// RemovePeer disconnects from a remote node if the connection exists -func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { - // Make sure the server is running, fail otherwise - server := api.node.Server() - if server == nil { - return false, ErrNodeStopped - } - // Try to remove the url as a static peer and return - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - server.RemovePeer(node) - return true, nil -} - -// AddTrustedPeer allows a remote node to always connect, even if slots are full -func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) { - // Make sure the server is running, fail otherwise - server := api.node.Server() - if server == nil { - return false, ErrNodeStopped - } - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - server.AddTrustedPeer(node) - return true, nil -} - -// RemoveTrustedPeer removes a remote node from the trusted peer set, but it -// does not disconnect it automatically. -func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) { - // Make sure the server is running, fail otherwise - server := api.node.Server() - if server == nil { - return false, ErrNodeStopped - } - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - return false, fmt.Errorf("invalid enode: %v", err) - } - server.RemoveTrustedPeer(node) - return true, nil -} - -// PeerEvents creates an RPC subscription which receives peer events from the -// node's p2p.Server -func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { - // Make sure the server is running, fail otherwise - server := api.node.Server() - if server == nil { - return nil, ErrNodeStopped - } - - // Create the subscription - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return nil, rpc.ErrNotificationsUnsupported - } - rpcSub := notifier.CreateSubscription() - - go func() { - events := make(chan *p2p.PeerEvent) - sub := server.SubscribeEvents(events) - defer sub.Unsubscribe() - - for { - select { - case event := <-events: - notifier.Notify(rpcSub.ID, event) - case <-sub.Err(): - return - case <-rpcSub.Err(): - return - case <-notifier.Closed(): - return - } - } - }() - - return rpcSub, nil -} - -// StartRPC starts the HTTP RPC API server. -func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - if api.node.httpHandler != nil { - return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint) - } - - if host == nil { - h := DefaultHTTPHost - if api.node.config.HTTPHost != "" { - h = api.node.config.HTTPHost - } - host = &h - } - if port == nil { - port = &api.node.config.HTTPPort - } - - allowedOrigins := api.node.config.HTTPCors - if cors != nil { - allowedOrigins = nil - for _, origin := range strings.Split(*cors, ",") { - allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) - } - } - - allowedVHosts := api.node.config.HTTPVirtualHosts - if vhosts != nil { - allowedVHosts = nil - for _, vhost := range strings.Split(*host, ",") { - allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) - } - } - - modules := api.node.httpWhitelist - if apis != nil { - modules = nil - for _, m := range strings.Split(*apis, ",") { - modules = append(modules, strings.TrimSpace(m)) - } - } - - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { - return false, err - } - return true, nil -} - -// StopRPC terminates an already running HTTP RPC API endpoint. -func (api *PrivateAdminAPI) StopRPC() (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - if api.node.httpHandler == nil { - return false, fmt.Errorf("HTTP RPC not running") - } - api.node.stopHTTP() - return true, nil -} - -// StartWS starts the websocket RPC API server. -func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - if api.node.wsHandler != nil { - return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint) - } - - if host == nil { - h := DefaultWSHost - if api.node.config.WSHost != "" { - h = api.node.config.WSHost - } - host = &h - } - if port == nil { - port = &api.node.config.WSPort - } - - origins := api.node.config.WSOrigins - if allowedOrigins != nil { - origins = nil - for _, origin := range strings.Split(*allowedOrigins, ",") { - origins = append(origins, strings.TrimSpace(origin)) - } - } - - modules := api.node.config.WSModules - if apis != nil { - modules = nil - for _, m := range strings.Split(*apis, ",") { - modules = append(modules, strings.TrimSpace(m)) - } - } - - if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil { - return false, err - } - return true, nil -} - -// StopWS terminates an already running websocket RPC API endpoint. -func (api *PrivateAdminAPI) StopWS() (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - if api.node.wsHandler == nil { - return false, fmt.Errorf("WebSocket RPC not running") - } - api.node.stopWS() - return true, nil -} - -// PublicAdminAPI is the collection of administrative API methods exposed over -// both secure and unsecure RPC channels. -type PublicAdminAPI struct { - node *Node // Node interfaced by this API -} - -// NewPublicAdminAPI creates a new API definition for the public admin methods -// of the node itself. -func NewPublicAdminAPI(node *Node) *PublicAdminAPI { - return &PublicAdminAPI{node: node} -} - -// Peers retrieves all the information we know about each individual peer at the -// protocol granularity. -func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { - server := api.node.Server() - if server == nil { - return nil, ErrNodeStopped - } - return server.PeersInfo(), nil -} - -// NodeInfo retrieves all the information we know about the host node at the -// protocol granularity. -func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { - server := api.node.Server() - if server == nil { - return nil, ErrNodeStopped - } - return server.NodeInfo(), nil -} - -// Datadir retrieves the current data directory the node is using. -func (api *PublicAdminAPI) Datadir() string { - return api.node.DataDir() -} - -// PublicWeb3API offers helper utils -type PublicWeb3API struct { - stack *Node -} - -// NewPublicWeb3API creates a new Web3Service instance -func NewPublicWeb3API(stack *Node) *PublicWeb3API { - return &PublicWeb3API{stack} -} - -// ClientVersion returns the node name -func (s *PublicWeb3API) ClientVersion() string { - return s.stack.Server().Name -} - -// Sha3 applies the ethereum sha3 implementation on the input. -// It assumes the input is hex encoded. -func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { - return crypto.Keccak256(input) -} diff --git a/node_2/config.go b/node_2/config.go deleted file mode 100644 index 4a7814adeb70..000000000000 --- a/node_2/config.go +++ /dev/null @@ -1,545 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "crypto/ecdsa" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/external" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/accounts/scwallet" - "github.com/ethereum/go-ethereum/accounts/usbwallet" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rpc" -) - -const ( - datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key - datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore - datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list - datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list - datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos -) - -// Config represents a small collection of configuration values to fine tune the -// P2P network layer of a protocol stack. These values can be further extended by -// all registered services. -type Config struct { - // Name sets the instance name of the node. It must not contain the / character and is - // used in the devp2p node identifier. The instance name of geth is "geth". If no - // value is specified, the basename of the current executable is used. - Name string `toml:"-"` - - // UserIdent, if set, is used as an additional component in the devp2p node identifier. - UserIdent string `toml:",omitempty"` - - // Version should be set to the version number of the program. It is used - // in the devp2p node identifier. - Version string `toml:"-"` - - // DataDir is the file system folder the node should use for any data storage - // requirements. The configured data directory will not be directly shared with - // registered services, instead those can use utility methods to create/access - // databases or flat files. This enables ephemeral nodes which can fully reside - // in memory. - DataDir string - - // Configuration of peer-to-peer networking. - P2P p2p.Config - - // KeyStoreDir is the file system folder that contains private keys. The directory can - // be specified as a relative path, in which case it is resolved relative to the - // current directory. - // - // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of - // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory - // is created by New and destroyed when the node is stopped. - KeyStoreDir string `toml:",omitempty"` - - // ExternalSigner specifies an external URI for a clef-type signer - ExternalSigner string `toml:"omitempty"` - - // UseLightweightKDF lowers the memory and CPU requirements of the key store - // scrypt KDF at the expense of security. - UseLightweightKDF bool `toml:",omitempty"` - - // InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment. - InsecureUnlockAllowed bool `toml:",omitempty"` - - // NoUSB disables hardware wallet monitoring and connectivity. - NoUSB bool `toml:",omitempty"` - - // SmartCardDaemonPath is the path to the smartcard daemon's socket - SmartCardDaemonPath string `toml:",omitempty"` - - // IPCPath is the requested location to place the IPC endpoint. If the path is - // a simple file name, it is placed inside the data directory (or on the root - // pipe path on Windows), whereas if it's a resolvable path name (absolute or - // relative), then that specific path is enforced. An empty path disables IPC. - IPCPath string `toml:",omitempty"` - - // HTTPHost is the host interface on which to start the HTTP RPC server. If this - // field is empty, no HTTP API endpoint will be started. - HTTPHost string `toml:",omitempty"` - - // HTTPPort is the TCP port number on which to start the HTTP RPC server. The - // default zero value is/ valid and will pick a port number randomly (useful - // for ephemeral nodes). - HTTPPort int `toml:",omitempty"` - - // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting - // clients. Please be aware that CORS is a browser enforced security, it's fully - // useless for custom HTTP clients. - HTTPCors []string `toml:",omitempty"` - - // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. - // This is by default {'localhost'}. Using this prevents attacks like - // DNS rebinding, which bypasses SOP by simply masquerading as being within the same - // origin. These attacks do not utilize CORS, since they are not cross-domain. - // By explicitly checking the Host-header, the server will not allow requests - // made against the server with a malicious host domain. - // Requests using ip address directly are not affected - HTTPVirtualHosts []string `toml:",omitempty"` - - // HTTPModules is a list of API modules to expose via the HTTP RPC interface. - // If the module list is empty, all RPC API endpoints designated public will be - // exposed. - HTTPModules []string `toml:",omitempty"` - - // HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC - // interface. - HTTPTimeouts rpc.HTTPTimeouts - - // WSHost is the host interface on which to start the websocket RPC server. If - // this field is empty, no websocket API endpoint will be started. - WSHost string `toml:",omitempty"` - - // WSPort is the TCP port number on which to start the websocket RPC server. The - // default zero value is/ valid and will pick a port number randomly (useful for - // ephemeral nodes). - WSPort int `toml:",omitempty"` - - // WSOrigins is the list of domain to accept websocket requests from. Please be - // aware that the server can only act upon the HTTP request the client sends and - // cannot verify the validity of the request header. - WSOrigins []string `toml:",omitempty"` - - // WSModules is a list of API modules to expose via the websocket RPC interface. - // If the module list is empty, all RPC API endpoints designated public will be - // exposed. - WSModules []string `toml:",omitempty"` - - // WSExposeAll exposes all API modules via the WebSocket RPC interface rather - // than just the public ones. - // - // *WARNING* Only set this if the node is running in a trusted network, exposing - // private APIs to untrusted users is a major security risk. - WSExposeAll bool `toml:",omitempty"` - - // GraphQLHost is the host interface on which to start the GraphQL server. If this - // field is empty, no GraphQL API endpoint will be started. - GraphQLHost string `toml:",omitempty"` - - // GraphQLPort is the TCP port number on which to start the GraphQL server. The - // default zero value is/ valid and will pick a port number randomly (useful - // for ephemeral nodes). - GraphQLPort int `toml:",omitempty"` - - // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting - // clients. Please be aware that CORS is a browser enforced security, it's fully - // useless for custom HTTP clients. - GraphQLCors []string `toml:",omitempty"` - - // GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. - // This is by default {'localhost'}. Using this prevents attacks like - // DNS rebinding, which bypasses SOP by simply masquerading as being within the same - // origin. These attacks do not utilize CORS, since they are not cross-domain. - // By explicitly checking the Host-header, the server will not allow requests - // made against the server with a malicious host domain. - // Requests using ip address directly are not affected - GraphQLVirtualHosts []string `toml:",omitempty"` - - // Logger is a custom logger to use with the p2p.Server. - Logger log.Logger `toml:",omitempty"` - - staticNodesWarning bool - trustedNodesWarning bool - oldGethResourceWarning bool -} - -// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into -// account the set data folders as well as the designated platform we're currently -// running on. -func (c *Config) IPCEndpoint() string { - // Short circuit if IPC has not been enabled - if c.IPCPath == "" { - return "" - } - // On windows we can only use plain top-level pipes - if runtime.GOOS == "windows" { - if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { - return c.IPCPath - } - return `\\.\pipe\` + c.IPCPath - } - // Resolve names into the data directory full paths otherwise - if filepath.Base(c.IPCPath) == c.IPCPath { - if c.DataDir == "" { - return filepath.Join(os.TempDir(), c.IPCPath) - } - return filepath.Join(c.DataDir, c.IPCPath) - } - return c.IPCPath -} - -// NodeDB returns the path to the discovery node database. -func (c *Config) NodeDB() string { - if c.DataDir == "" { - return "" // ephemeral - } - return c.ResolvePath(datadirNodeDatabase) -} - -// DefaultIPCEndpoint returns the IPC path used by default. -func DefaultIPCEndpoint(clientIdentifier string) string { - if clientIdentifier == "" { - clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") - if clientIdentifier == "" { - panic("empty executable name") - } - } - config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} - return config.IPCEndpoint() -} - -// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface -// and port parameters. -func (c *Config) HTTPEndpoint() string { - if c.HTTPHost == "" { - return "" - } - return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) -} - -// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface -// and port parameters. -func (c *Config) GraphQLEndpoint() string { - if c.GraphQLHost == "" { - return "" - } - return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort) -} - -// DefaultHTTPEndpoint returns the HTTP endpoint used by default. -func DefaultHTTPEndpoint() string { - config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} - return config.HTTPEndpoint() -} - -// WSEndpoint resolves a websocket endpoint based on the configured host interface -// and port parameters. -func (c *Config) WSEndpoint() string { - if c.WSHost == "" { - return "" - } - return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) -} - -// DefaultWSEndpoint returns the websocket endpoint used by default. -func DefaultWSEndpoint() string { - config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} - return config.WSEndpoint() -} - -// ExtRPCEnabled returns the indicator whether node enables the external -// RPC(http, ws or graphql). -func (c *Config) ExtRPCEnabled() bool { - return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != "" -} - -// NodeName returns the devp2p node identifier. -func (c *Config) NodeName() string { - name := c.name() - // Backwards compatibility: previous versions used title-cased "Geth", keep that. - if name == "geth" || name == "geth-testnet" { - name = "Geth" - } - if c.UserIdent != "" { - name += "/" + c.UserIdent - } - if c.Version != "" { - name += "/v" + c.Version - } - name += "/" + runtime.GOOS + "-" + runtime.GOARCH - name += "/" + runtime.Version() - return name -} - -func (c *Config) name() string { - if c.Name == "" { - progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") - if progname == "" { - panic("empty executable name, set Config.Name") - } - return progname - } - return c.Name -} - -// These resources are resolved differently for "geth" instances. -var isOldGethResource = map[string]bool{ - "chaindata": true, - "nodes": true, - "nodekey": true, - "static-nodes.json": false, // no warning for these because they have their - "trusted-nodes.json": false, // own separate warning. -} - -// ResolvePath resolves path in the instance directory. -func (c *Config) ResolvePath(path string) string { - if filepath.IsAbs(path) { - return path - } - if c.DataDir == "" { - return "" - } - // Backwards-compatibility: ensure that data directory files created - // by geth 1.4 are used if they exist. - if warn, isOld := isOldGethResource[path]; isOld { - oldpath := "" - if c.name() == "geth" { - oldpath = filepath.Join(c.DataDir, path) - } - if oldpath != "" && common.FileExist(oldpath) { - if warn { - c.warnOnce(&c.oldGethResourceWarning, "Using deprecated resource file %s, please move this file to the 'geth' subdirectory of datadir.", oldpath) - } - return oldpath - } - } - return filepath.Join(c.instanceDir(), path) -} - -func (c *Config) instanceDir() string { - if c.DataDir == "" { - return "" - } - return filepath.Join(c.DataDir, c.name()) -} - -// NodeKey retrieves the currently configured private key of the node, checking -// first any manually set key, falling back to the one found in the configured -// data folder. If no key can be found, a new one is generated. -func (c *Config) NodeKey() *ecdsa.PrivateKey { - // Use any specifically configured key. - if c.P2P.PrivateKey != nil { - return c.P2P.PrivateKey - } - // Generate ephemeral key if no datadir is being used. - if c.DataDir == "" { - key, err := crypto.GenerateKey() - if err != nil { - log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err)) - } - return key - } - - keyfile := c.ResolvePath(datadirPrivateKey) - if key, err := crypto.LoadECDSA(keyfile); err == nil { - return key - } - // No persistent key found, generate and store a new one. - key, err := crypto.GenerateKey() - if err != nil { - log.Crit(fmt.Sprintf("Failed to generate node key: %v", err)) - } - instanceDir := filepath.Join(c.DataDir, c.name()) - if err := os.MkdirAll(instanceDir, 0700); err != nil { - log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) - return key - } - keyfile = filepath.Join(instanceDir, datadirPrivateKey) - if err := crypto.SaveECDSA(keyfile, key); err != nil { - log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) - } - return key -} - -// StaticNodes returns a list of node enode URLs configured as static nodes. -func (c *Config) StaticNodes() []*enode.Node { - return c.parsePersistentNodes(&c.staticNodesWarning, c.ResolvePath(datadirStaticNodes)) -} - -// TrustedNodes returns a list of node enode URLs configured as trusted nodes. -func (c *Config) TrustedNodes() []*enode.Node { - return c.parsePersistentNodes(&c.trustedNodesWarning, c.ResolvePath(datadirTrustedNodes)) -} - -// parsePersistentNodes parses a list of discovery node URLs loaded from a .json -// file from within the data directory. -func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node { - // Short circuit if no node config is present - if c.DataDir == "" { - return nil - } - if _, err := os.Stat(path); err != nil { - return nil - } - c.warnOnce(w, "Found deprecated node list file %s, please use the TOML config file instead.", path) - - // Load the nodes from the config file. - var nodelist []string - if err := common.LoadJSON(path, &nodelist); err != nil { - log.Error(fmt.Sprintf("Can't load node list file: %v", err)) - return nil - } - // Interpret the list as a discovery node array - var nodes []*enode.Node - for _, url := range nodelist { - if url == "" { - continue - } - node, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) - continue - } - nodes = append(nodes, node) - } - return nodes -} - -// AccountConfig determines the settings for scrypt and keydirectory -func (c *Config) AccountConfig() (int, int, string, error) { - scryptN := keystore.StandardScryptN - scryptP := keystore.StandardScryptP - if c.UseLightweightKDF { - scryptN = keystore.LightScryptN - scryptP = keystore.LightScryptP - } - - var ( - keydir string - err error - ) - switch { - case filepath.IsAbs(c.KeyStoreDir): - keydir = c.KeyStoreDir - case c.DataDir != "": - if c.KeyStoreDir == "" { - keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore) - } else { - keydir, err = filepath.Abs(c.KeyStoreDir) - } - case c.KeyStoreDir != "": - keydir, err = filepath.Abs(c.KeyStoreDir) - } - return scryptN, scryptP, keydir, err -} - -func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { - scryptN, scryptP, keydir, err := conf.AccountConfig() - var ephemeral string - if keydir == "" { - // There is no datadir. - keydir, err = ioutil.TempDir("", "go-ethereum-keystore") - ephemeral = keydir - } - - if err != nil { - return nil, "", err - } - if err := os.MkdirAll(keydir, 0700); err != nil { - return nil, "", err - } - // Assemble the account manager and supported backends - var backends []accounts.Backend - if len(conf.ExternalSigner) > 0 { - log.Info("Using external signer", "url", conf.ExternalSigner) - if extapi, err := external.NewExternalBackend(conf.ExternalSigner); err == nil { - backends = append(backends, extapi) - } else { - return nil, "", fmt.Errorf("error connecting to external signer: %v", err) - } - } - if len(backends) == 0 { - // For now, we're using EITHER external signer OR local signers. - // If/when we implement some form of lockfile for USB and keystore wallets, - // we can have both, but it's very confusing for the user to see the same - // accounts in both externally and locally, plus very racey. - backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP)) - if !conf.NoUSB { - // Start a USB hub for Ledger hardware wallets - if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { - log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) - } else { - backends = append(backends, ledgerhub) - } - // Start a USB hub for Trezor hardware wallets (HID version) - if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil { - log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) - } else { - backends = append(backends, trezorhub) - } - // Start a USB hub for Trezor hardware wallets (WebUSB version) - if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil { - log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) - } else { - backends = append(backends, trezorhub) - } - } - if len(conf.SmartCardDaemonPath) > 0 { - // Start a smart card hub - if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil { - log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) - } else { - backends = append(backends, schub) - } - } - } - - return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}, backends...), ephemeral, nil -} - -var warnLock sync.Mutex - -func (c *Config) warnOnce(w *bool, format string, args ...interface{}) { - warnLock.Lock() - defer warnLock.Unlock() - - if *w { - return - } - l := c.Logger - if l == nil { - l = log.Root() - } - l.Warn(fmt.Sprintf(format, args...)) - *w = true -} diff --git a/node_2/defaults.go b/node_2/defaults.go deleted file mode 100644 index 9951aaa0265e..000000000000 --- a/node_2/defaults.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "os" - "os/user" - "path/filepath" - "runtime" - - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rpc" -) - -const ( - DefaultHTTPHost = "localhost" // Default host interface for the HTTP RPC server - DefaultHTTPPort = 8545 // Default TCP port for the HTTP RPC server - DefaultWSHost = "localhost" // Default host interface for the websocket RPC server - DefaultWSPort = 8546 // Default TCP port for the websocket RPC server - DefaultGraphQLHost = "localhost" // Default host interface for the GraphQL server - DefaultGraphQLPort = 8547 // Default TCP port for the GraphQL server -) - -// DefaultConfig contains reasonable default settings. -var DefaultConfig = Config{ - DataDir: DefaultDataDir(), - HTTPPort: DefaultHTTPPort, - HTTPModules: []string{"net", "web3"}, - HTTPVirtualHosts: []string{"localhost"}, - HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSPort: DefaultWSPort, - WSModules: []string{"net", "web3"}, - GraphQLPort: DefaultGraphQLPort, - GraphQLVirtualHosts: []string{"localhost"}, - P2P: p2p.Config{ - ListenAddr: ":30303", - MaxPeers: 50, - NAT: nat.Any(), - }, -} - -// DefaultDataDir is the default data directory to use for the databases and other -// persistence requirements. -func DefaultDataDir() string { - // Try to place the data folder in the user's home dir - home := homeDir() - if home != "" { - switch runtime.GOOS { - case "darwin": - return filepath.Join(home, "Library", "Ethereum") - case "windows": - // We used to put everything in %HOME%\AppData\Roaming, but this caused - // problems with non-typical setups. If this fallback location exists and - // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%. - fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum") - appdata := windowsAppData() - if appdata == "" || isNonEmptyDir(fallback) { - return fallback - } - return filepath.Join(appdata, "Ethereum") - default: - return filepath.Join(home, ".ethereum") - } - } - // As we cannot guess a stable location, return empty and handle later - return "" -} - -func windowsAppData() string { - v := os.Getenv("LOCALAPPDATA") - if v == "" { - // Windows XP and below don't have LocalAppData. Crash here because - // we don't support Windows XP and undefining the variable will cause - // other issues. - panic("environment variable LocalAppData is undefined") - } - return v -} - -func isNonEmptyDir(dir string) bool { - f, err := os.Open(dir) - if err != nil { - return false - } - names, _ := f.Readdir(1) - f.Close() - return len(names) > 0 -} - -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} diff --git a/node_2/endpoints.go b/node_2/endpoints.go deleted file mode 100644 index 0dce7b5acfe3..000000000000 --- a/node_2/endpoints.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "net" - "net/http" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" -) - -// StartHTTPEndpoint starts the HTTP RPC endpoint. -func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { - // start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, err - } - // make sure timeout values are meaningful - CheckTimeouts(&timeouts) - // Bundle and start the HTTP server - httpSrv := &http.Server{ - Handler: handler, - ReadTimeout: timeouts.ReadTimeout, - WriteTimeout: timeouts.WriteTimeout, - IdleTimeout: timeouts.IdleTimeout, - } - go httpSrv.Serve(listener) - return listener, err -} - -// startWSEndpoint starts a websocket endpoint. -func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { - // start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, err - } - wsSrv := &http.Server{Handler: handler} - go wsSrv.Serve(listener) - return listener, err -} - -// checkModuleAvailability checks that all names given in modules are actually -// available API services. It assumes that the MetadataApi module ("rpc") is always available; -// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. -func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available []string) { - availableSet := make(map[string]struct{}) - for _, api := range apis { - if _, ok := availableSet[api.Namespace]; !ok { - availableSet[api.Namespace] = struct{}{} - available = append(available, api.Namespace) - } - } - for _, name := range modules { - if _, ok := availableSet[name]; !ok && name != rpc.MetadataApi { - bad = append(bad, name) - } - } - return bad, available -} - -// CheckTimeouts ensures that timeout values are meaningful -func CheckTimeouts(timeouts *rpc.HTTPTimeouts) { - if timeouts.ReadTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout) - timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout - } - if timeouts.WriteTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout) - timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout - } - if timeouts.IdleTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", rpc.DefaultHTTPTimeouts.IdleTimeout) - timeouts.IdleTimeout = rpc.DefaultHTTPTimeouts.IdleTimeout - } -} diff --git a/node_2/errors.go b/node_2/errors.go deleted file mode 100644 index 7ae4440208cc..000000000000 --- a/node_2/errors.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "errors" - "fmt" - "reflect" - "syscall" -) - -var ( - ErrDatadirUsed = errors.New("datadir already used by another process") - ErrNodeStopped = errors.New("node not started") - ErrNodeRunning = errors.New("node already running") - ErrServiceUnknown = errors.New("unknown service") - - datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true} -) - -func convertFileLockError(err error) error { - if errno, ok := err.(syscall.Errno); ok && datadirInUseErrnos[uint(errno)] { - return ErrDatadirUsed - } - return err -} - -// DuplicateServiceError is returned during Node startup if a registered service -// constructor returns a service of the same type that was already started. -type DuplicateServiceError struct { - Kind reflect.Type -} - -// Error generates a textual representation of the duplicate service error. -func (e *DuplicateServiceError) Error() string { - return fmt.Sprintf("duplicate service: %v", e.Kind) -} - -// StopError is returned if a Node fails to stop either any of its registered -// services or itself. -type StopError struct { - Server error - Services map[reflect.Type]error -} - -// Error generates a textual representation of the stop error. -func (e *StopError) Error() string { - return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services) -} diff --git a/node_2/node.go b/node_2/node.go deleted file mode 100644 index 9668fee80f81..000000000000 --- a/node_2/node.go +++ /dev/null @@ -1,737 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "reflect" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/internal/debug" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" - "github.com/prometheus/tsdb/fileutil" -) - -// Node is a container on which services can be registered. -type Node struct { - eventmux *event.TypeMux // Event multiplexer used between the services of a stack - config *Config - accman *accounts.Manager - - ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop - instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory - - server *p2p.Server // Currently running P2P networking layer - - backends map[reflect.Type]Backend // TODO - services map[reflect.Type]Service // Currently running services - auxServices map[reflect.Type]AuxiliaryService // TODO - - rpcAPIs []rpc.API // List of APIs currently provided by the node - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - - ipcHandler *httpServer // TODO - httpHandler *httpServer // TODO - wsHandler *httpServer // TODO - - stop chan struct{} // Channel to wait for termination notifications - lock sync.RWMutex - - log log.Logger -} - -// New creates a new P2P node, ready for protocol registration. -func New(conf *Config) (*Node, error) { - // Copy config and resolve the datadir so future changes to the current - // working directory don't affect the node. - confCopy := *conf - conf = &confCopy - if conf.DataDir != "" { - absdatadir, err := filepath.Abs(conf.DataDir) - if err != nil { - return nil, err - } - conf.DataDir = absdatadir - } - // Ensure that the instance name doesn't cause weird conflicts with - // other files in the data directory. - if strings.ContainsAny(conf.Name, `/\`) { - return nil, errors.New(`Config.Name must not contain '/' or '\'`) - } - if conf.Name == datadirDefaultKeyStore { - return nil, errors.New(`Config.Name cannot be "` + datadirDefaultKeyStore + `"`) - } - if strings.HasSuffix(conf.Name, ".ipc") { - return nil, errors.New(`Config.Name cannot end in ".ipc"`) - } - // Ensure that the AccountManager method works before the node has started. - // We rely on this in cmd/geth. - am, ephemeralKeystore, err := makeAccountManager(conf) - if err != nil { - return nil, err - } - if conf.Logger == nil { - conf.Logger = log.New() - } - // Note: any interaction with Config that would create/touch files - // in the data directory or instance directory is delayed until Start. - return &Node{ - accman: am, - ephemeralKeystore: ephemeralKeystore, - config: conf, - ipcHandler: &httpServer{ - endpoint: endpoint{ - endpoint: conf.IPCEndpoint(), - }, - }, - httpHandler: &httpServer{ - endpoint: endpoint{ - endpoint: conf.HTTPEndpoint(), - host: conf.HTTPHost, - port: conf.HTTPPort, - }, - CorsAllowedOrigins: conf.HTTPCors, - Vhosts: conf.HTTPVirtualHosts, - Timeouts: conf.HTTPTimeouts, - WsOrigins: conf.WSOrigins, // TODO do i already add this? - RPCAllowed: true, - - }, - wsHandler: &httpServer{ - endpoint: endpoint{ - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - }, - WsOrigins: conf.WSOrigins, - WSAllowed: true, - }, - eventmux: new(event.TypeMux), - log: conf.Logger, - }, nil -} - -// Close stops the Node and releases resources acquired in -// Node constructor New. -func (n *Node) Close() error { - var errs []error - - // Terminate all subsystems and collect any errors - if err := n.Stop(); err != nil && err != ErrNodeStopped { - errs = append(errs, err) - } - if err := n.accman.Close(); err != nil { - errs = append(errs, err) - } - // Report any errors that might have occurred - switch len(errs) { - case 0: - return nil - case 1: - return errs[0] - default: - return fmt.Errorf("%v", errs) - } -} - -// Register injects a new service into the node's stack. The service created by -// the passed constructor must be unique in its type with regard to sibling ones. -func (n *Node) Register(constructor ServiceConstructor) error { - n.lock.Lock() - defer n.lock.Unlock() - - // TODO this should create the service and add it to the maps already - - //if n.server != nil { - // return ErrNodeRunning - //} - //n.serviceFuncs = append(n.serviceFuncs, constructor) - return nil -} - -// Start creates a live P2P node and starts running it. -func (n *Node) Start() error { - n.lock.Lock() - defer n.lock.Unlock() - - // Short circuit if the node's already running - if n.server != nil { - return ErrNodeRunning - } - if err := n.openDataDir(); err != nil { - return err - } - - // Initialize the p2p server. This creates the node key and - // discovery databases. - running := &p2p.Server{} - - running.PrivateKey = n.config.NodeKey() - running.Name = n.config.NodeName() - running.Logger = n.log - if running.StaticNodes == nil { - running.StaticNodes = n.config.StaticNodes() - } - if running.TrustedNodes == nil { - running.TrustedNodes = n.config.TrustedNodes() - } - if running.NodeDatabase == "" { - running.NodeDatabase = n.config.NodeDB() - } - n.log.Info("Starting peer-to-peer node", "instance", running.Name) - - // TODO document - backends := make(map[reflect.Type]Backend) - services := make(map[reflect.Type]Service) - auxServices := make(map[reflect.Type]AuxiliaryService) - - // Gather the protocols and start the freshly assembled P2P server - for _, backend := range backends { - running.Protocols = append(running.Protocols, backend.Protocols()...) - } - if err := running.Start(); err != nil { - return convertFileLockError(err) - } - - //TODO instead of constructor, make it actually construct the backends, services and aux services? - - // TODO this should just check for duplicates, and then start the backends, services, and auxServices - - var started []reflect.Type - - // Start each of the backends - for kind, backend := range backends { - if err := backend.Start(); err != nil { - for _, kind := range started { - services[kind].Stop() - } - running.Stop() - - return err - } - // Mark the service started for potential cleanup - started = append(started, kind) - } - - // Start each of the services - for kind, service := range services { - // Start the next service, stopping all previous upon failure - if err := service.Start(); err != nil { - for _, kind := range started { - services[kind].Stop() - } - running.Stop() - - return err - } - // Mark the service started for potential cleanup - started = append(started, kind) - } - - // Gather all the possible APIs to surface - apis := n.apis() - for _, backend := range backends { - apis = append(apis, backend.APIs()...) - } - for _, service := range services { - apis = append(apis, service.APIs()...) - } - - // Lastly, start the configured RPC interfaces - if err := n.startRPC(apis); err != nil { - for _, service := range services { - service.Stop() - } - running.Stop() - return err - } - - // Finish initializing the startup - n.backends = backends - n.services = services - n.auxServices = auxServices - n.server = running - n.stop = make(chan struct{}) - return nil -} - -// Config returns the configuration of node. -func (n *Node) Config() *Config { - return n.config -} - -func (n *Node) openDataDir() error { - if n.config.DataDir == "" { - return nil // ephemeral - } - - instdir := filepath.Join(n.config.DataDir, n.config.name()) - if err := os.MkdirAll(instdir, 0700); err != nil { - return err - } - // Lock the instance directory to prevent concurrent use by another instance as well as - // accidental use of the instance directory as a database. - release, _, err := fileutil.Flock(filepath.Join(instdir, "LOCK")) - if err != nil { - return convertFileLockError(err) - } - n.instanceDirLock = release - return nil -} - -// startRPC is a helper method to start all the various RPC endpoints during node -// startup. It's not meant to be called at any time afterwards as it makes certain -// assumptions about the state of the node. -func (n *Node) startRPC(apis []rpc.API) error { - // Start the various API endpoints, terminating all in case of errors - if err := n.startInProc(apis); err != nil { - return err - } - if err := n.startIPC(apis); err != nil { - n.stopInProc() - return err - } - if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { - n.stopIPC() - n.stopInProc() - return err - } - // if endpoints are not the same, start separate servers - if n.httpEndpoint != n.wsEndpoint { - if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { - n.stopHTTP() - n.stopIPC() - n.stopInProc() - return err - } - } - - // All API endpoints started successfully - n.rpcAPIs = apis - return nil -} - -// startInProc initializes an in-process RPC endpoint. -func (n *Node) startInProc(apis []rpc.API) error { - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("InProc registered", "namespace", api.Namespace) - } - n.inprocHandler = handler - return nil -} - -// stopInProc terminates the in-process RPC endpoint. -func (n *Node) stopInProc() { - if n.inprocHandler != nil { - n.inprocHandler.Stop() - n.inprocHandler = nil - } -} - -// startIPC initializes and starts the IPC RPC endpoint. -func (n *Node) startIPC(apis []rpc.API) error { - if n.ipcEndpoint == "" { - return nil // IPC disabled. - } - listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) - if err != nil { - return err - } - n.ipcListener = listener - n.ipcHandler = handler - n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) - return nil -} - -// stopIPC terminates the IPC RPC endpoint. -func (n *Node) stopIPC() { - if n.ipcListener != nil { - n.ipcListener.Close() - n.ipcListener = nil - - n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint) - } - if n.ipcHandler != nil { - n.ipcHandler.Stop() - n.ipcHandler = nil - } -} - -// startHTTP initializes and starts the HTTP RPC endpoint. -func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { - // Short circuit if the HTTP endpoint isn't being exposed - if endpoint == "" { - return nil - } - // register apis and create handler stack - srv := rpc.NewServer() - err := RegisterApisFromWhitelist(apis, modules, srv, false) - if err != nil { - return err - } - handler := NewHTTPHandlerStack(srv, cors, vhosts) - // wrap handler in websocket handler only if websocket port is the same as http rpc - if n.httpEndpoint == n.wsEndpoint { - handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) - } - listener, err := StartHTTPEndpoint(endpoint, timeouts, handler) - if err != nil { - return err - } - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), - "cors", strings.Join(cors, ","), - "vhosts", strings.Join(vhosts, ",")) - if n.httpEndpoint == n.wsEndpoint { - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) - } - // All listeners booted successfully - n.httpEndpoint = endpoint - n.httpListener = listener - n.httpHandler = srv - - return nil -} - -// stopHTTP terminates the HTTP RPC endpoint. -func (n *Node) stopHTTP() { - if n.httpListener != nil { - url := fmt.Sprintf("http://%v/", n.httpListener.Addr()) - n.httpListener.Close() - n.httpListener = nil - n.log.Info("HTTP endpoint closed", "url", url) - } - if n.httpHandler != nil { - n.httpHandler.Stop() - n.httpHandler = nil - } -} - -// startWS initializes and starts the websocket RPC endpoint. -func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error { - // Short circuit if the WS endpoint isn't being exposed - if endpoint == "" { - return nil - } - - srv := rpc.NewServer() - handler := srv.WebsocketHandler(wsOrigins) - err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) - if err != nil { - return err - } - listener, err := startWSEndpoint(endpoint, handler) - if err != nil { - return err - } - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) - // All listeners booted successfully - n.wsEndpoint = endpoint - n.wsListener = listener - n.wsHandler = srv - - return nil -} - -// stopWS terminates the websocket RPC endpoint. -func (n *Node) stopWS() { - if n.wsListener != nil { - n.wsListener.Close() - n.wsListener = nil - - n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsEndpoint)) - } - if n.wsHandler != nil { - n.wsHandler.Stop() - n.wsHandler = nil - } -} - -// Stop terminates a running node along with all it's services. In the node was -// not started, an error is returned. -func (n *Node) Stop() error { - n.lock.Lock() - defer n.lock.Unlock() - - // Short circuit if the node's not running - if n.server == nil { - return ErrNodeStopped - } - - // Terminate the API, services and the p2p server. - n.stopWS() - n.stopHTTP() - n.stopIPC() - n.rpcAPIs = nil - failure := &StopError{ - Services: make(map[reflect.Type]error), - } - for kind, service := range n.services { - if err := service.Stop(); err != nil { - failure.Services[kind] = err - } - } - n.server.Stop() - n.services = nil - n.server = nil - - // Release instance directory lock. - if n.instanceDirLock != nil { - if err := n.instanceDirLock.Release(); err != nil { - n.log.Error("Can't release datadir lock", "err", err) - } - n.instanceDirLock = nil - } - - // unblock n.Wait - close(n.stop) - - // Remove the keystore if it was created ephemerally. - var keystoreErr error - if n.ephemeralKeystore != "" { - keystoreErr = os.RemoveAll(n.ephemeralKeystore) - } - - if len(failure.Services) > 0 { - return failure - } - if keystoreErr != nil { - return keystoreErr - } - return nil -} - -// Wait blocks the thread until the node is stopped. If the node is not running -// at the time of invocation, the method immediately returns. -func (n *Node) Wait() { - n.lock.RLock() - if n.server == nil { - n.lock.RUnlock() - return - } - stop := n.stop - n.lock.RUnlock() - - <-stop -} - -// Restart terminates a running node and boots up a new one in its place. If the -// node isn't running, an error is returned. -func (n *Node) Restart() error { - if err := n.Stop(); err != nil { - return err - } - if err := n.Start(); err != nil { - return err - } - return nil -} - -// Attach creates an RPC client attached to an in-process API handler. -func (n *Node) Attach() (*rpc.Client, error) { - n.lock.RLock() - defer n.lock.RUnlock() - - if n.server == nil { - return nil, ErrNodeStopped - } - return rpc.DialInProc(n.inprocHandler), nil -} - -// RPCHandler returns the in-process RPC request handler. -func (n *Node) RPCHandler() (*rpc.Server, error) { - n.lock.RLock() - defer n.lock.RUnlock() - - if n.inprocHandler == nil { - return nil, ErrNodeStopped - } - return n.inprocHandler, nil -} - -// Server retrieves the currently running P2P network layer. This method is meant -// only to inspect fields of the currently running server, life cycle management -// should be left to this Node entity. -func (n *Node) Server() *p2p.Server { - n.lock.RLock() - defer n.lock.RUnlock() - - return n.server -} - -// Service retrieves a currently running service registered of a specific type. -func (n *Node) Service(service interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if n.server == nil { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(service).Elem() - if running, ok := n.services[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - -// DataDir retrieves the current datadir used by the protocol stack. -// Deprecated: No files should be stored in this directory, use InstanceDir instead. -func (n *Node) DataDir() string { - return n.config.DataDir -} - -// InstanceDir retrieves the instance directory used by the protocol stack. -func (n *Node) InstanceDir() string { - return n.config.instanceDir() -} - -// AccountManager retrieves the account manager used by the protocol stack. -func (n *Node) AccountManager() *accounts.Manager { - return n.accman -} - -// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. -func (n *Node) IPCEndpoint() string { - return n.ipcEndpoint -} - -// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. -func (n *Node) HTTPEndpoint() string { - n.lock.Lock() - defer n.lock.Unlock() - - if n.httpListener != nil { - return n.httpListener.Addr().String() - } - return n.httpEndpoint -} - -// WSEndpoint retrieves the current WS endpoint used by the protocol stack. -func (n *Node) WSEndpoint() string { - n.lock.Lock() - defer n.lock.Unlock() - - if n.wsListener != nil { - return n.wsListener.Addr().String() - } - return n.wsEndpoint -} - -// EventMux retrieves the event multiplexer used by all the network services in -// the current protocol stack. -func (n *Node) EventMux() *event.TypeMux { - return n.eventmux -} - -// OpenDatabase opens an existing database with the given name (or creates one if no -// previous can be found) from within the node's instance directory. If the node is -// ephemeral, a memory database is returned. -func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { - if n.config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace) -} - -// OpenDatabaseWithFreezer opens an existing database with the given name (or -// creates one if no previous can be found) from within the node's data directory, -// also attaching a chain freezer to it that moves ancient chain data from the -// database to immutable append-only files. If the node is an ephemeral one, a -// memory database is returned. -func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { - if n.config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - root := n.config.ResolvePath(name) - - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = n.config.ResolvePath(freezer) - } - return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) -} - -// ResolvePath returns the absolute path of a resource in the instance directory. -func (n *Node) ResolvePath(x string) string { - return n.config.ResolvePath(x) -} - -// apis returns the collection of RPC descriptors this node offers. -func (n *Node) apis() []rpc.API { - return []rpc.API{ - { - Namespace: "admin", - Version: "1.0", - Service: NewPrivateAdminAPI(n), - }, { - Namespace: "admin", - Version: "1.0", - Service: NewPublicAdminAPI(n), - Public: true, - }, { - Namespace: "debug", - Version: "1.0", - Service: debug.Handler, - }, { - Namespace: "web3", - Version: "1.0", - Service: NewPublicWeb3API(n), - Public: true, - }, - } -} - -// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, -// and then registers all of the APIs exposed by the services. -func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { - if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { - log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) - } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := srv.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - } - } - return nil -} diff --git a/node_2/rpcstack.go b/node_2/rpcstack.go deleted file mode 100644 index eb3928655782..000000000000 --- a/node_2/rpcstack.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "compress/gzip" - "github.com/ethereum/go-ethereum/rpc" - "io" - "io/ioutil" - "net" - "net/http" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/log" - "github.com/rs/cors" -) - -type httpServer struct { - handler http.Handler - Srv *rpc.Server - - endpoint endpoint - - Whitelist []string - - CorsAllowedOrigins []string - Vhosts []string - WsOrigins []string - Timeouts rpc.HTTPTimeouts - - Listener net.Listener - - RPCAllowed bool - WSAllowed bool -} - -type endpoint struct { - endpoint string - host string - port int -} - -// NewHTTPHandlerStack returns wrapped http-related handlers -func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { - // Wrap the CORS-handler within a host-handler - handler := newCorsHandler(srv, cors) - handler = newVHostHandler(vhosts, handler) - return newGzipHandler(handler) -} - -func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { - // disable CORS support if user has not specified a custom CORS configuration - if len(allowedOrigins) == 0 { - return srv - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - return c.Handler(srv) -} - -// virtualHostHandler is a handler which validates the Host-header of incoming requests. -// Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to -// the service ip address (but without CORS headers). By verifying the targeted virtual host, we can -// ensure that it's a destination that the node operator has defined. -type virtualHostHandler struct { - vhosts map[string]struct{} - next http.Handler -} - -func newVHostHandler(vhosts []string, next http.Handler) http.Handler { - vhostMap := make(map[string]struct{}) - for _, allowedHost := range vhosts { - vhostMap[strings.ToLower(allowedHost)] = struct{}{} - } - return &virtualHostHandler{vhostMap, next} -} - -// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler -func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // if r.Host is not set, we can continue serving since a browser would set the Host header - if r.Host == "" { - h.next.ServeHTTP(w, r) - return - } - host, _, err := net.SplitHostPort(r.Host) - if err != nil { - // Either invalid (too many colons) or no port specified - host = r.Host - } - if ipAddr := net.ParseIP(host); ipAddr != nil { - // It's an IP address, we can serve that - h.next.ServeHTTP(w, r) - return - - } - // Not an IP address, but a hostname. Need to validate - if _, exist := h.vhosts["*"]; exist { - h.next.ServeHTTP(w, r) - return - } - if _, exist := h.vhosts[host]; exist { - h.next.ServeHTTP(w, r) - return - } - http.Error(w, "invalid host specified", http.StatusForbidden) -} - -var gzPool = sync.Pool{ - New: func() interface{} { - w := gzip.NewWriter(ioutil.Discard) - return w - }, -} - -type gzipResponseWriter struct { - io.Writer - http.ResponseWriter -} - -func (w *gzipResponseWriter) WriteHeader(status int) { - w.Header().Del("Content-Length") - w.ResponseWriter.WriteHeader(status) -} - -func (w *gzipResponseWriter) Write(b []byte) (int, error) { - return w.Writer.Write(b) -} - -func newGzipHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { - next.ServeHTTP(w, r) - return - } - - w.Header().Set("Content-Encoding", "gzip") - - gz := gzPool.Get().(*gzip.Writer) - defer gzPool.Put(gz) - - gz.Reset(w) - defer gz.Close() - - next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) - }) -} - -// NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade -// request to the websocket protocol. If not, serves the the request with the http handler. -func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if isWebsocket(r) { - ws.ServeHTTP(w, r) - log.Debug("serving websocket request") - return - } - - h.ServeHTTP(w, r) - }) -} - -// isWebsocket checks the header of an http request for a websocket upgrade request. -func isWebsocket(r *http.Request) bool { - return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && - strings.ToLower(r.Header.Get("Connection")) == "upgrade" -} diff --git a/node_2/service.go b/node_2/service.go deleted file mode 100644 index aabafa0737b9..000000000000 --- a/node_2/service.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node_2 - -import ( - "path/filepath" - "reflect" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" -) - -// ServiceContext is a collection of service independent options inherited from -// the protocol stack, that is passed to all constructors to be optionally used; -// as well as utility methods to operate on the service environment. -type ServiceContext struct { - services map[reflect.Type]Service // Index of the already constructed services - Config Config - EventMux *event.TypeMux // Event multiplexer used for decoupled notifications - AccountManager *accounts.Manager // Account manager created by the node. -} - -// OpenDatabase opens an existing database with the given name (or creates one -// if no previous can be found) from within the node's data directory. If the -// node is an ephemeral one, a memory database is returned. -func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) { - if ctx.Config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace) -} - -// OpenDatabaseWithFreezer opens an existing database with the given name (or -// creates one if no previous can be found) from within the node's data directory, -// also attaching a chain freezer to it that moves ancient chain data from the -// database to immutable append-only files. If the node is an ephemeral one, a -// memory database is returned. -func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { - if ctx.Config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - root := ctx.Config.ResolvePath(name) - - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = ctx.Config.ResolvePath(freezer) - } - return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) -} - -// ResolvePath resolves a user path into the data directory if that was relative -// and if the user actually uses persistent storage. It will return an empty string -// for emphemeral storage and the user's own input for absolute paths. -func (ctx *ServiceContext) ResolvePath(path string) string { - return ctx.Config.ResolvePath(path) -} - -// Service retrieves a currently running service registered of a specific type. -func (ctx *ServiceContext) Service(service interface{}) error { - element := reflect.ValueOf(service).Elem() - if running, ok := ctx.services[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - -// ExtRPCEnabled returns the indicator whether node enables the external -// RPC(http, ws or graphql). -func (ctx *ServiceContext) ExtRPCEnabled() bool { - return ctx.Config.ExtRPCEnabled() -} - -// ServiceConstructor is the function signature of the constructors needed to be -// registered for service instantiation. -type ServiceConstructor func(ctx *ServiceContext) (Service, error) - -// Backend is an individual protocol that can be registered into a node. -// -// Notes: -// -// • Backend service life-cycle management is delegated to the node. The backend service is allowed to -// initialize itself upon creation, but no goroutines should be spun up outside of the -// Start method. -type Backend interface { - // Protocols retrieves the P2P protocols the service wishes to start. - Protocols() []p2p.Protocol - - Service -} - -// TODO document -type Service interface { - // APIs retrieves the list of RPC descriptors the service provides - APIs() []rpc.API - - // Start is called after all services have been constructed and the networking - // layer was also initialized to spawn any goroutines required by the service. - Start() error - - // Stop terminates all goroutines belonging to the service, blocking until they - // are all terminated. - Stop() error -} - -// TODO document -type AuxiliaryService interface { - Start() error - - Stop() error -} - - From 7f36098628b46c69985a95e57cae775de2a0e2bf Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Apr 2020 15:51:51 +0200 Subject: [PATCH 004/160] committing progress, eth.ethereum + les.lightethereum impl backend --- cmd/faucet/faucet.go | 4 ++-- cmd/utils/flags.go | 4 ++-- eth/backend.go | 23 ++++++++++++--------- ethstats/ethstats.go | 48 ++++++++++++++++++++++++++------------------ les/client.go | 10 +++++++-- miner/miner.go | 2 +- node/node.go | 9 ++++++++- 7 files changed, 64 insertions(+), 36 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 475430e7f95a..a6daaa5dbd38 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,7 +241,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - return les.New(ctx, &cfg) + return les.New(stack.ServiceContext, &cfg) }); err != nil { return nil, err } @@ -249,7 +249,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u if stats != "" { if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { var serv *les.LightEthereum - return ethstats.New(stack, stats, nil, serv) + return ethstats.New(stack, stats, serv) }); err != nil { return nil, err } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e179cb9669c9..e256f7747c81 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1699,8 +1699,8 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { return les.New(ctx, cfg) }) } else { - err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - fullNode, err := eth.New(ctx, cfg) + err = stack.RegisterBackendLifecycle(func(node *node.Node) (node.Backend, error) { + fullNode, err := eth.New(node.ServiceContext, cfg, node.Server()) if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) diff --git a/eth/backend.go b/eth/backend.go index dfdd1f7bdb06..30a57314749f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -65,6 +65,8 @@ type LesServer interface { // Ethereum implements the Ethereum full node service. type Ethereum struct { + // TODO needs a p2pServer + config *Config // Handlers @@ -94,6 +96,8 @@ type Ethereum struct { networkID uint64 netRPCService *ethapi.PublicNetAPI + p2pServer *p2p.Server + lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) } @@ -112,7 +116,7 @@ func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config, p2pServer *p2p.Server) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -158,6 +162,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + p2pServer: p2pServer, } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -221,7 +226,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{node.ServiceContext.ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice @@ -535,27 +540,27 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // Start implements node.Service, starting all internal goroutines needed by the // Ethereum protocol implementation. -func (s *Ethereum) Start(srvr *p2p.Server) error { - s.startEthEntryUpdate(srvr.LocalNode()) +func (s *Ethereum) Start() error { + s.startEthEntryUpdate(s.p2pServer.LocalNode()) // Start the bloom bits servicing goroutines s.startBloomHandlers(params.BloomBitsBlocks) // Start the RPC service - s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion()) + s.netRPCService = ethapi.NewPublicNetAPI(s.p2pServer, s.NetVersion()) // Figure out a max peers count based on the server limits - maxPeers := srvr.MaxPeers + maxPeers := s.p2pServer.MaxPeers if s.config.LightServ > 0 { - if s.config.LightPeers >= srvr.MaxPeers { - return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers) + if s.config.LightPeers >= s.p2pServer.MaxPeers { + return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) } maxPeers -= s.config.LightPeers } // Start the networking layer and the light server if requested s.protocolManager.Start(maxPeers) if s.lesServer != nil { - s.lesServer.Start(srvr) + s.lesServer.Start(s.p2pServer) } return nil } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 7d630f7b9cf7..5847406d1b3e 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -83,31 +83,41 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (node.AuxiliaryService, error) { +func New(node *node.Node, url string, backend node.Backend) (node.AuxiliaryService, error) { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) if len(parts) != 5 { return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) } - // Assemble and return the stats service - var engine consensus.Engine - if ethServ != nil { - engine = ethServ.Engine() - } else { - engine = lesServ.Engine() - } - return &Service{ - server: node.Server(), - eth: ethServ, - les: lesServ, - engine: engine, - node: parts[1], - pass: parts[3], - host: parts[4], - pongCh: make(chan struct{}), - histCh: make(chan []uint64, 1), - }, nil + + // fetch type of Backend + if ethBackend, ok := backend.(*eth.Ethereum); ok { + return &Service{ + server: node.Server(), + eth: ethBackend, + les: nil, // TODO is this okay? + engine: ethBackend.Engine(), + node: parts[1], + pass: parts[3], + host: parts[4], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), + }, nil + } else if lesBackend, ok := backend.(*les.LightEthereum); ok { + return &Service{ + server: node.Server(), + eth: nil, // TODO is this okay? + les: lesBackend, + engine: lesBackend.Engine(), + node: parts[1], + pass: parts[3], + host: parts[4], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), + }, nil + } + return nil, errors.New("ethstats backend is of unidentified type") // TODO is this okay to return? } // Start implements node.Service, starting up the monitoring and reporting daemon. diff --git a/les/client.go b/les/client.go index a3ae647517c8..430e37d56c82 100644 --- a/les/client.go +++ b/les/client.go @@ -72,6 +72,8 @@ type LightEthereum struct { engine consensus.Engine accountManager *accounts.Manager netRPCService *ethapi.PublicNetAPI + + p2pServer *p2p.Server } func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { @@ -278,7 +280,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { // Start implements node.Service, starting all internal goroutines needed by the // light ethereum protocol implementation. -func (s *LightEthereum) Start(srvr *p2p.Server) error { +func (s *LightEthereum) Start() error { log.Warn("Light client mode is an experimental feature") s.serverPool.start() @@ -287,7 +289,11 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error { s.startBloomHandlers(params.BloomBitsBlocksClient) s.handler.start() - s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.config.NetworkId) + s.netRPCService = ethapi.NewPublicNetAPI(s.p2pServer, s.config.NetworkId) + + // clients are searching for the first advertised protocol in the list + protocolVersion := AdvertiseProtocolVersions[0] + s.serverPool.start(s.p2pServer, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion)) return nil } diff --git a/miner/miner.go b/miner/miner.go index 5249118cae14..d7d5872dba2f 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -36,7 +36,7 @@ import ( ) // Backend wraps all methods required for mining. -type Backend interface { +type Backend interface { BlockChain() *core.BlockChain TxPool() *core.TxPool } diff --git a/node/node.go b/node/node.go index 14bc99b726bc..5732319455ce 100644 --- a/node/node.go +++ b/node/node.go @@ -49,8 +49,10 @@ type Node struct { // TODO: removed p2pConfig b/c p2pServer already contains p2pConfig (is there a reason for it to be duplicated? server *p2p.Server // Currently running P2P networking layer + ServiceContext *ServiceContext + serviceConstructors []ServiceConstructor // Service constructors (in dependency order) - auxServiceConstructors []AuxiliarServiceConstructor // AuxiliaryService constructors + auxServiceConstructors []AuxiliaryServiceConstructor // AuxiliaryService constructors backend Backend // The registered Backend of the node services map[reflect.Type]Service // Currently running services @@ -106,6 +108,9 @@ func New(conf *Config) (*Node, error) { // Note: any interaction with Config that would create/touch files // in the data directory or instance directory is delayed until Start. return &Node{ + ServiceContext: &ServiceContext{ + Config: *conf, + }, accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, @@ -221,6 +226,8 @@ func (n *Node) Start() error { return err } + // TODO make sure to fill out servicecontext as you go + // Initialize the p2p server. This creates the node key and // discovery databases. n.serverConfig = n.config.P2P From bc838a297d485c12ff46a919ad81b5fd9fbc2eee Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Apr 2020 17:32:43 +0200 Subject: [PATCH 005/160] not working yet, but committing progress --- eth/backend.go | 2 +- ethstats/ethstats.go | 4 +- graphql/service.go | 66 +++++----- node/api.go | 10 +- node/config.go | 14 +- node/config_test.go | 2 +- node/doc.go | 6 +- node/endpoints.go | 8 +- node/node.go | 301 +++++++++++++++++++++++-------------------- node/node_test.go | 2 +- node/rpcstack.go | 23 +++- node/service.go | 6 +- 12 files changed, 241 insertions(+), 203 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 30a57314749f..7d46485ee220 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -226,7 +226,7 @@ func New(ctx *node.ServiceContext, config *Config, p2pServer *p2p.Server) (*Ethe eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{node.ServiceContext.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 5847406d1b3e..17857d5492dc 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -134,9 +134,7 @@ func (s *Service) Stop() error { return nil } -func (s *Service) Server() (*node.HttpServer, error) { - return nil, nil -} +func (s *Service) Server() *node.HttpServer { return nil } // loop keeps trying to connect to the netstats server, reporting chain events // until termination. diff --git a/graphql/service.go b/graphql/service.go index a20605302497..1f4d31356f27 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" @@ -32,56 +31,53 @@ import ( // Service encapsulates a GraphQL service. type Service struct { - endpoint string // The host:port endpoint for this service. - cors []string // Allowed CORS domains - vhosts []string // Recognised vhosts - timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests. backend ethapi.Backend // The backend that queries will operate on. - handler http.Handler // The `http.Handler` used to answer queries. - listener net.Listener // The listening socket. + graphqlServer *node.HttpServer } // New constructs a new GraphQL service instance. func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) { - return &Service{ - endpoint: endpoint, - cors: cors, - vhosts: vhosts, - timeouts: timeouts, + service := &Service{ backend: backend, - }, nil + graphqlServer: &node.HttpServer{ + Timeouts: timeouts, + Vhosts: vhosts, + CorsAllowedOrigins: cors, + }, + } + service.graphqlServer.SetEndpoint(endpoint) + return service, nil } -// Protocols returns the list of protocols exported by this service. -func (s *Service) Protocols() []p2p.Protocol { return nil } - -// APIs returns the list of APIs exported by this service. -func (s *Service) APIs() []rpc.API { return nil } - // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. -func (s *Service) Start(server *p2p.Server) error { +func (s *Service) Start() error { var err error - s.handler, err = newHandler(s.backend) + handler, err := newHandler(s.backend) if err != nil { return err } - if s.listener, err = net.Listen("tcp", s.endpoint); err != nil { + + listener, err := net.Listen("tcp", s.graphqlServer.Endpoint()) + if err != nil { return err } // create handler stack and wrap the graphql handler - handler := node.NewHTTPHandlerStack(s.handler, s.cors, s.vhosts) + handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) // make sure timeout values are meaningful - node.CheckTimeouts(&s.timeouts) + node.CheckTimeouts(&s.graphqlServer.Timeouts) // create http server httpSrv := &http.Server{ Handler: handler, - ReadTimeout: s.timeouts.ReadTimeout, - WriteTimeout: s.timeouts.WriteTimeout, - IdleTimeout: s.timeouts.IdleTimeout, + ReadTimeout: s.graphqlServer.Timeouts.ReadTimeout, + WriteTimeout: s.graphqlServer.Timeouts.WriteTimeout, + IdleTimeout: s.graphqlServer.Timeouts.IdleTimeout, } - go httpSrv.Serve(s.listener) - log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.endpoint)) + go httpSrv.Serve(listener) + log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.graphqlServer.Endpoint)) + // add information to graphql http server + s.graphqlServer.Listener = listener + s.graphqlServer.SetHandler(handler) return nil } @@ -106,10 +102,14 @@ func newHandler(backend ethapi.Backend) (http.Handler, error) { // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. func (s *Service) Stop() error { - if s.listener != nil { - s.listener.Close() - s.listener = nil - log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.endpoint)) + if s.graphqlServer.Listener != nil { + s.graphqlServer.Listener.Close() + s.graphqlServer.Listener = nil + log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.graphqlServer.Endpoint)) } return nil } + +func (s *Service) Server() *node.HttpServer { + return s.graphqlServer +} diff --git a/node/api.go b/node/api.go index 1a73d1321db5..df2b2cd69405 100644 --- a/node/api.go +++ b/node/api.go @@ -148,7 +148,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis defer api.node.lock.Unlock() if api.node.httpHandler != nil { - return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint) + return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpHandler.Endpoint) } if host == nil { @@ -178,7 +178,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - modules := api.node.httpWhitelist + modules := api.node.httpHandler.Whitelist if apis != nil { modules = nil for _, m := range strings.Split(*apis, ",") { @@ -192,7 +192,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis return true, nil } -// StopRPC terminates an already running HTTP RPC API endpoint. +// StopRPC terminates an already running HTTP RPC API Endpoint. func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -210,7 +210,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str defer api.node.lock.Unlock() if api.node.wsHandler != nil { - return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint) + return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsHandler.Endpoint) } if host == nil { @@ -246,7 +246,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str return true, nil } -// StopWS terminates an already running websocket RPC API endpoint. +// StopWS terminates an already running websocket RPC API Endpoint. func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() diff --git a/node/config.go b/node/config.go index 61566b7bee9b..d0c25c125b55 100644 --- a/node/config.go +++ b/node/config.go @@ -98,7 +98,7 @@ type Config struct { // SmartCardDaemonPath is the path to the smartcard daemon's socket SmartCardDaemonPath string `toml:",omitempty"` - // IPCPath is the requested location to place the IPC endpoint. If the path is + // IPCPath is the requested location to place the IPC Endpoint. If the path is // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or // relative), then that specific path is enforced. An empty path disables IPC. @@ -193,7 +193,7 @@ type Config struct { oldGethResourceWarning bool } -// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into +// IPCEndpoint resolves an IPC Endpoint based on a configured value, taking into // account the set data folders as well as the designated platform we're currently // running on. func (c *Config) IPCEndpoint() string { @@ -238,7 +238,7 @@ func DefaultIPCEndpoint(clientIdentifier string) string { return config.IPCEndpoint() } -// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface +// HTTPEndpoint resolves an HTTP Endpoint based on the configured host interface // and port parameters. func (c *Config) HTTPEndpoint() string { if c.HTTPHost == "" { @@ -247,7 +247,7 @@ func (c *Config) HTTPEndpoint() string { return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) } -// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface +// GraphQLEndpoint resolves a GraphQL Endpoint based on the configured host interface // and port parameters. func (c *Config) GraphQLEndpoint() string { if c.GraphQLHost == "" { @@ -256,13 +256,13 @@ func (c *Config) GraphQLEndpoint() string { return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort) } -// DefaultHTTPEndpoint returns the HTTP endpoint used by default. +// DefaultHTTPEndpoint returns the HTTP Endpoint used by default. func DefaultHTTPEndpoint() string { config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} return config.HTTPEndpoint() } -// WSEndpoint resolves a websocket endpoint based on the configured host interface +// WSEndpoint resolves a websocket Endpoint based on the configured host interface // and port parameters. func (c *Config) WSEndpoint() string { if c.WSHost == "" { @@ -271,7 +271,7 @@ func (c *Config) WSEndpoint() string { return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) } -// DefaultWSEndpoint returns the websocket endpoint used by default. +// DefaultWSEndpoint returns the websocket Endpoint used by default. func DefaultWSEndpoint() string { config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} return config.WSEndpoint() diff --git a/node/config_test.go b/node/config_test.go index 00c24a239123..17c473f655d2 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -99,7 +99,7 @@ func TestIPCPathResolution(t *testing.T) { // Only run when platform/test match if (runtime.GOOS == "windows") == test.Windows { if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint { - t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) + t.Errorf("test %d: IPC Endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) } } } diff --git a/node/doc.go b/node/doc.go index e3cc58e5f49c..3c6b696b3218 100644 --- a/node/doc.go +++ b/node/doc.go @@ -36,7 +36,7 @@ about other hosts is persisted. JSON-RPC servers which run HTTP, WebSocket or IPC can be started on a Node. RPC modules offered by registered services will be offered on those endpoints. Users can restrict any -endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3" +Endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3" modules. Service implementations can open LevelDB databases through the service context. Package @@ -77,14 +77,14 @@ directory. Node instance A opens the database "db", node instance B opens the da nodekey -- devp2p node key of instance A nodes/ -- devp2p discovery knowledge database of instance A db/ -- LevelDB content for "db" - A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A + A.ipc -- JSON-RPC UNIX domain socket Endpoint of instance A B/ nodekey -- devp2p node key of node B nodes/ -- devp2p discovery knowledge database of instance B static-nodes.json -- devp2p static node list of instance B db/ -- LevelDB content for "db" db-2/ -- LevelDB content for "db-2" - B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B + B.ipc -- JSON-RPC UNIX domain socket Endpoint of instance B keystore/ -- account key store, used by both instances */ package node diff --git a/node/endpoints.go b/node/endpoints.go index 1baa1b5c417f..13479b88a191 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -25,8 +25,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// StartHTTPEndpoint starts the HTTP RPC endpoint. -func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) { +// StartHTTPEndpoint starts the HTTP RPC Endpoint. +func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { // start the HTTP listener var ( listener net.Listener @@ -48,8 +48,8 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. return httpSrv, listener.Addr(), err } -// startWSEndpoint starts a websocket endpoint. -func startWSEndpoint(endpoint string, handler http.Handler) (*http.Server, net.Addr, error) { +// startWSEndpoint starts a websocket Endpoint. +func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { // start the HTTP listener var ( listener net.Listener diff --git a/node/node.go b/node/node.go index 5732319455ce..e65b0dc63b56 100644 --- a/node/node.go +++ b/node/node.go @@ -47,15 +47,12 @@ type Node struct { instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory // TODO: removed p2pConfig b/c p2pServer already contains p2pConfig (is there a reason for it to be duplicated? - server *p2p.Server // Currently running P2P networking layer + server *p2p.Server // Currently running P2P networking layer ServiceContext *ServiceContext - serviceConstructors []ServiceConstructor // Service constructors (in dependency order) - auxServiceConstructors []AuxiliaryServiceConstructor // AuxiliaryService constructors - - backend Backend // The registered Backend of the node - services map[reflect.Type]Service // Currently running services + backend Backend // The registered Backend of the node + services map[reflect.Type]Service // Currently running services auxServices map[reflect.Type]AuxiliaryService // Currently running auxiliary services rpcAPIs []rpc.API // List of APIs currently provided by the node @@ -108,18 +105,25 @@ func New(conf *Config) (*Node, error) { // Note: any interaction with Config that would create/touch files // in the data directory or instance directory is delayed until Start. return &Node{ - ServiceContext: &ServiceContext{ - Config: *conf, - }, accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, - serviceFuncs: []ServiceConstructor{}, - ipcEndpoint: conf.IPCEndpoint(), - httpEndpoint: conf.HTTPEndpoint(), - wsEndpoint: conf.WSEndpoint(), - eventmux: new(event.TypeMux), - log: conf.Logger, + ServiceContext: &ServiceContext{ + Config: *conf, + }, + services: make(map[reflect.Type]Service), + auxServices: make(map[reflect.Type]AuxiliaryService), + ipcHandler: &HttpServer{ + endpoint: conf.IPCEndpoint(), + }, + httpHandler: &HttpServer{ + endpoint: conf.HTTPEndpoint(), + }, + wsHandler: &HttpServer{ + endpoint: conf.WSEndpoint(), + }, + eventmux: new(event.TypeMux), + log: conf.Logger, }, nil } @@ -147,7 +151,7 @@ func (n *Node) Close() error { } // TODO document -func (n * Node) RegisterBackendLifecycle(constructor BackendConstructor) error { +func (n *Node) RegisterBackendLifecycle(constructor BackendConstructor) error { n.lock.Lock() defer n.lock.Unlock() @@ -226,87 +230,89 @@ func (n *Node) Start() error { return err } - // TODO make sure to fill out servicecontext as you go - // Initialize the p2p server. This creates the node key and // discovery databases. - n.serverConfig = n.config.P2P - n.serverConfig.PrivateKey = n.config.NodeKey() - n.serverConfig.Name = n.config.NodeName() - n.serverConfig.Logger = n.log - if n.serverConfig.StaticNodes == nil { - n.serverConfig.StaticNodes = n.config.StaticNodes() - } - if n.serverConfig.TrustedNodes == nil { - n.serverConfig.TrustedNodes = n.config.TrustedNodes() - } - if n.serverConfig.NodeDatabase == "" { - n.serverConfig.NodeDatabase = n.config.NodeDB() - } - running := &p2p.Server{Config: n.serverConfig} - n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name) - - // Otherwise copy and specialize the P2P configuration - services := make(map[reflect.Type]Service) - //for _, constructor := range n.serviceFuncs { - // // Create a new context for the particular service - // ctx := &ServiceContext{ - // Config: *n.config, - // services: make(map[reflect.Type]Service), - // EventMux: n.eventmux, - // AccountManager: n.accman, - // } - // for kind, s := range services { // copy needed for threaded access - // ctx.services[kind] = s - // } - // // Construct and save the service - // service, err := constructor(ctx) - // if err != nil { - // return err - // } - // kind := reflect.TypeOf(service) - // if _, exists := services[kind]; exists { - // return &DuplicateServiceError{Kind: kind} - // } - // services[kind] = service - //} - // Gather the protocols and start the freshly assembled P2P server - for _, service := range services { - running.Protocols = append(running.Protocols, service.Protocols()...) + n.server.Config = n.config.P2P + n.server.PrivateKey = n.config.NodeKey() + n.server.Name = n.config.NodeName() + n.server.Logger = n.log + if n.server.StaticNodes == nil { + n.server.StaticNodes = n.config.StaticNodes() + } + if n.server.TrustedNodes == nil { + n.server.TrustedNodes = n.config.TrustedNodes() + } + if n.server.NodeDatabase == "" { + n.server.NodeDatabase = n.config.NodeDB() } + running := &p2p.Server{Config: n.server.Config} + n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) + + // add backend's protocols to the o2o server + running.Protocols = append(running.Protocols, n.backend.Protocols()...) if err := running.Start(); err != nil { return convertFileLockError(err) } + // Start the backend + if err := n.backend.Start(); err != nil { + running.Stop() + return err + } // Start each of the services - var started []reflect.Type - for kind, service := range services { + var startedServices []reflect.Type + for kind, service := range n.services { // Start the next service, stopping all previous upon failure - if err := service.Start(running); err != nil { - for _, kind := range started { - services[kind].Stop() - } + if err := service.Start(); err != nil { + n.stopAllStartedServices(startedServices, nil) // TODO safe to pass nil here? + running.Stop() + + return err + } + // Mark the service started for potential cleanup + startedServices = append(startedServices, kind) + } + // Start each of the auxiliary services + var startedAuxServices []reflect.Type + for kind, auxService := range n.auxServices { + // Start the next auxiliary service, stopping all previous upon failure + if err := auxService.Start(); err != nil { + n.stopAllStartedServices(startedServices, startedAuxServices) running.Stop() return err } // Mark the service started for potential cleanup - started = append(started, kind) + startedAuxServices = append(startedAuxServices, kind) } // Lastly, start the configured RPC interfaces - if err := n.startRPC(services); err != nil { - for _, service := range services { - service.Stop() - } + if err := n.startRPC(); err != nil { + n.stopAllStartedServices(startedServices, startedAuxServices) running.Stop() return err } + // Finish initializing the service context + n.ServiceContext.backend = n.backend + n.ServiceContext.AccountManager = n.accman + n.ServiceContext.EventMux = n.eventmux + n.ServiceContext.services = n.services + n.ServiceContext.auxServices = n.auxServices + // Finish initializing the startup - n.services = services n.server = running n.stop = make(chan struct{}) return nil } +func (n *Node) stopAllStartedServices(startedServices []reflect.Type, startedAuxServices []reflect.Type) { + n.backend.Stop() + for _, kind := range startedServices { + n.services[kind].Stop() + } + for _, kind := range startedAuxServices { + n.auxServices[kind].Stop() + } +} + // Config returns the configuration of node. func (n *Node) Config() *Config { return n.config @@ -334,10 +340,11 @@ func (n *Node) openDataDir() error { // startRPC is a helper method to start all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. -func (n *Node) startRPC(services map[reflect.Type]Service) error { +func (n *Node) startRPC() error { // Gather all the possible APIs to surface apis := n.apis() - for _, service := range services { + apis = append(apis, n.backend.APIs()...) + for _, service := range n.services { apis = append(apis, service.APIs()...) } // Start the various API endpoints, terminating all in case of errors @@ -348,14 +355,14 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() return err } - if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { + if err := n.startHTTP(n.httpHandler.Endpoint(), apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { n.stopIPC() n.stopInProc() return err } // if endpoints are not the same, start separate servers - if n.httpEndpoint != n.wsEndpoint { - if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { + if n.httpHandler.Endpoint() != n.wsHandler.Endpoint() { + if err := n.startWS(n.wsHandler.Endpoint(), apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { n.stopHTTP() n.stopIPC() n.stopInProc() @@ -368,7 +375,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { return nil } -// startInProc initializes an in-process RPC endpoint. +// startInProc initializes an in-process RPC Endpoint. func (n *Node) startInProc(apis []rpc.API) error { // Register all the APIs exposed by the services handler := rpc.NewServer() @@ -382,7 +389,7 @@ func (n *Node) startInProc(apis []rpc.API) error { return nil } -// stopInProc terminates the in-process RPC endpoint. +// stopInProc terminates the in-process RPC Endpoint. func (n *Node) stopInProc() { if n.inprocHandler != nil { n.inprocHandler.Stop() @@ -390,38 +397,38 @@ func (n *Node) stopInProc() { } } -// startIPC initializes and starts the IPC RPC endpoint. +// startIPC initializes and starts the IPC RPC Endpoint. func (n *Node) startIPC(apis []rpc.API) error { - if n.ipcEndpoint == "" { + if n.ipcHandler.Endpoint() == "" { return nil // IPC disabled. } - listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) + listener, handler, err := rpc.StartIPCEndpoint(n.ipcHandler.Endpoint(), apis) if err != nil { return err } - n.ipcListener = listener - n.ipcHandler = handler - n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) + n.ipcHandler.Listener = listener + n.ipcHandler.handler = handler + n.log.Info("IPC Endpoint opened", "url", n.ipcHandler.Endpoint()) return nil } -// stopIPC terminates the IPC RPC endpoint. +// stopIPC terminates the IPC RPC Endpoint. func (n *Node) stopIPC() { - if n.ipcListener != nil { - n.ipcListener.Close() - n.ipcListener = nil + if n.ipcHandler.Listener != nil { + n.ipcHandler.Listener.Close() + n.ipcHandler.Listener = nil - n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint) + n.log.Info("IPC Endpoint closed", "url", n.ipcHandler.Endpoint()) } - if n.ipcHandler != nil { - n.ipcHandler.Stop() - n.ipcHandler = nil + if n.ipcHandler.Srv != nil { + n.ipcHandler.Srv.Stop() + n.ipcHandler.Srv = nil } } -// startHTTP initializes and starts the HTTP RPC endpoint. +// startHTTP initializes and starts the HTTP RPC Endpoint. func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { - // Short circuit if the HTTP endpoint isn't being exposed + // Short circuit if the HTTP Endpoint isn't being exposed if endpoint == "" { return nil } @@ -432,45 +439,45 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors return err } handler := NewHTTPHandlerStack(srv, cors, vhosts) - // wrap handler in WebSocket handler only if WebSocket port is the same as http rpc - if n.httpEndpoint == n.wsEndpoint { + // wrap handler in websocket handler only if websocket port is the same as http rpc + if n.httpHandler.Endpoint() == n.wsHandler.Endpoint() { handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) } httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) if err != nil { return err } - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), + n.log.Info("HTTP Endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) - if n.httpEndpoint == n.wsEndpoint { - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) + if n.httpHandler.Endpoint() == n.wsHandler.Endpoint() { + n.log.Info("WebSocket Endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) } // All listeners booted successfully - n.httpEndpoint = endpoint - n.httpListenerAddr = addr - n.httpServer = httpServer - n.httpHandler = srv + n.httpHandler.endpoint = endpoint + n.httpHandler.Listener = listener + n.httpHandler.Srv = srv return nil } -// stopHTTP terminates the HTTP RPC endpoint. +// stopHTTP terminates the HTTP RPC Endpoint. func (n *Node) stopHTTP() { - if n.httpServer != nil { - // Don't bother imposing a timeout here. - n.httpServer.Shutdown(context.Background()) - n.log.Info("HTTP endpoint closed", "url", fmt.Sprintf("http://%v/", n.httpListenerAddr)) + if n.httpHandler.Listener != nil { + url := fmt.Sprintf("http://%v/", n.httpHandler.Listener.Addr()) + n.httpHandler.Listener.Close() + n.httpHandler.Listener = nil + n.log.Info("HTTP Endpoint closed", "url", url) } - if n.httpHandler != nil { - n.httpHandler.Stop() - n.httpHandler = nil + if n.httpHandler.Srv != nil { + n.httpHandler.Srv.Stop() + n.httpHandler.Srv = nil } } -// startWS initializes and starts the WebSocket RPC endpoint. +// startWS initializes and starts the websocket RPC Endpoint. func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error { - // Short circuit if the WS endpoint isn't being exposed + // Short circuit if the WS Endpoint isn't being exposed if endpoint == "" { return nil } @@ -485,26 +492,26 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig if err != nil { return err } - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) + n.log.Info("WebSocket Endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) // All listeners booted successfully - n.wsEndpoint = endpoint - n.wsListenerAddr = addr - n.wsHTTPServer = httpServer - n.wsHandler = srv + n.wsHandler.endpoint = endpoint + n.wsHandler.Listener = listener + n.wsHandler.Srv = srv return nil } -// stopWS terminates the WebSocket RPC endpoint. +// stopWS terminates the websocket RPC Endpoint. func (n *Node) stopWS() { - if n.wsHTTPServer != nil { - // Don't bother imposing a timeout here. - n.wsHTTPServer.Shutdown(context.Background()) - n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%v", n.wsListenerAddr)) + if n.wsHandler.Listener != nil { + n.wsHandler.Listener.Close() + n.wsHandler.Listener = nil + + n.log.Info("WebSocket Endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsHandler.Endpoint)) } - if n.wsHandler != nil { - n.wsHandler.Stop() - n.wsHandler = nil + if n.wsHandler.Srv != nil { + n.wsHandler.Srv.Stop() + n.wsHandler.Srv = nil } } @@ -638,6 +645,24 @@ func (n *Node) Service(service interface{}) error { return ErrServiceUnknown } +// Service retrieves a currently running service registered of a specific type. +func (n *Node) AuxService(auxService interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if n.server == nil { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(auxService).Elem() + if running, ok := n.auxServices[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + // DataDir retrieves the current datadir used by the protocol stack. // Deprecated: No files should be stored in this directory, use InstanceDir instead. func (n *Node) DataDir() string { @@ -654,31 +679,31 @@ func (n *Node) AccountManager() *accounts.Manager { return n.accman } -// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. +// IPCEndpoint retrieves the current IPC Endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { - return n.ipcEndpoint + return n.ipcHandler.Endpoint() } -// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. +// HTTPEndpoint retrieves the current HTTP Endpoint used by the protocol stack. func (n *Node) HTTPEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.httpListenerAddr != nil { - return n.httpListenerAddr.String() + if n.httpHandler.Listener != nil { + return n.httpHandler.Listener.Addr().String() } - return n.httpEndpoint + return n.httpHandler.Endpoint() } -// WSEndpoint retrieves the current WS endpoint used by the protocol stack. +// WSEndpoint retrieves the current WS Endpoint used by the protocol stack. func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.wsListenerAddr != nil { - return n.wsListenerAddr.String() + if n.wsHandler.Listener != nil { + return n.wsHandler.Listener.Addr().String() } - return n.wsEndpoint + return n.wsHandler.Endpoint() } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/node_test.go b/node/node_test.go index d62194a87681..501d3a8a5d1e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -651,7 +651,7 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the given endpoint", err) + t.Error("could not issue a GET request to the given Endpoint", err) } return resp } diff --git a/node/rpcstack.go b/node/rpcstack.go index 86c7b6fd683e..3cf8a9c46c08 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -18,6 +18,7 @@ package node import ( "compress/gzip" + "fmt" "github.com/ethereum/go-ethereum/rpc" "io" "io/ioutil" @@ -35,7 +36,9 @@ type HttpServer struct { Srv *rpc.Server Listener net.Listener - endpoint endpoint + endpoint string + host string + port int Whitelist []string @@ -48,10 +51,20 @@ type HttpServer struct { WSAllowed bool } -type endpoint struct { - endpoint string - host string - port int +func (h *HttpServer) Handler() http.Handler { + return h.handler +} + +func (h *HttpServer) SetHandler(handler http.Handler) { + h.handler = handler +} + +func (h *HttpServer) Endpoint() string { + return fmt.Sprintf("%s:%d", h.host, h.port) +} + +func (h *HttpServer) SetEndpoint(endpoint string) { + h.endpoint = endpoint } // NewHTTPHandlerStack returns wrapped http-related handlers diff --git a/node/service.go b/node/service.go index 94bb012c699e..57bbe0c729a8 100644 --- a/node/service.go +++ b/node/service.go @@ -32,7 +32,9 @@ import ( // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. type ServiceContext struct { - services map[reflect.Type]Service // Index of the already constructed services + backend Backend // Copy of the already constructed Backend // TODO update this comment + services map[reflect.Type]Service // Index of the already constructed services + auxServices map[reflect.Type]AuxiliaryService // Index of the already constructed auxiliary services Config Config EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. @@ -130,7 +132,7 @@ type Service interface { // TODO document type AuxiliaryService interface { // TODO document - Server() (*HttpServer, error) + Server() *HttpServer // AuxiliaryService also implements Lifecycle Lifecycle From a607196fa891f105906e80b729e7e177d23d5f12 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 23 Apr 2020 17:45:22 +0200 Subject: [PATCH 006/160] fixed some registration for services --- cmd/utils/flags.go | 28 +++++++++------------------- ethstats/ethstats.go | 2 +- node/node.go | 4 ++++ whisper/whisperv6/whisper.go | 2 +- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e256f7747c81..cd6d54ff05bb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1695,8 +1695,8 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { func RegisterEthService(stack *node.Node, cfg *eth.Config) { var err error if cfg.SyncMode == downloader.LightSync { - err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return les.New(ctx, cfg) + err = stack.RegisterBackendLifecycle(func(stack *node.Node) (node.Backend, error) { + return les.New(stack.ServiceContext, cfg) }) } else { err = stack.RegisterBackendLifecycle(func(node *node.Node) (node.Backend, error) { @@ -1715,7 +1715,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) { + if err := stack.RegisterServiceLifecycle(func(stack *node.Node) (node.Service, error) { return whisper.New(cfg), nil }); err != nil { Fatalf("Failed to register the Whisper service: %v", err) @@ -1725,16 +1725,8 @@ func RegisterShhService(stack *node.Node, cfg *whisper.Config) { // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. func RegisterEthStatsService(stack *node.Node, url string) { - if err := stack.Register(func(stack *node.Node) (node.AuxiliaryService, error) { - // Retrieve both eth and les services - var ethServ *eth.Ethereum - ctx.Service(ðServ) - - var lesServ *les.LightEthereum - ctx.Service(&lesServ) - - // Let ethstats use whichever is not nil - return ethstats.New(url, ethServ, lesServ) + if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { + return ethstats.New(stack, url, stack.Backend()) }); err != nil { Fatalf("Failed to register the Ethereum Stats service: %v", err) } @@ -1744,14 +1736,12 @@ func RegisterEthStatsService(stack *node.Node, url string) { func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { // Try to construct the GraphQL service backed by a full node - var ethServ *eth.Ethereum - if err := ctx.Service(ðServ); err == nil { - return graphql.New(ethServ.APIBackend, endpoint, cors, vhosts, timeouts) + if ethBackend, ok := stack.Backend().(*eth.Ethereum); ok { + return graphql.New(ethBackend.APIBackend, endpoint, cors, vhosts, timeouts) } // Try to construct the GraphQL service backed by a light node - var lesServ *les.LightEthereum - if err := ctx.Service(&lesServ); err == nil { - return graphql.New(lesServ.ApiBackend, endpoint, cors, vhosts, timeouts) + if lesBackend, ok := stack.Backend().(*les.LightEthereum); ok { + return graphql.New(lesBackend.ApiBackend, endpoint, cors, vhosts, timeouts) } // Well, this should not have happened, bail out return nil, errors.New("no Ethereum service") diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 17857d5492dc..81283370c8ce 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -117,7 +117,7 @@ func New(node *node.Node, url string, backend node.Backend) (node.AuxiliaryServi histCh: make(chan []uint64, 1), }, nil } - return nil, errors.New("ethstats backend is of unidentified type") // TODO is this okay to return? + return nil, errors.New("no Ethereum service") // TODO is this okay to return? } // Start implements node.Service, starting up the monitoring and reporting daemon. diff --git a/node/node.go b/node/node.go index e65b0dc63b56..6701dc34105b 100644 --- a/node/node.go +++ b/node/node.go @@ -617,6 +617,10 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { return n.inprocHandler, nil } +func (n *Node) Backend() Backend { + return n.backend +} + // Server retrieves the currently running P2P network layer. This method is meant // only to inspect fields of the currently running server, life cycle management // should be left to this Node entity. diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 8fe648a7a7f6..053c22b3cb7d 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -636,7 +636,7 @@ func (whisper *Whisper) Send(envelope *Envelope) error { // Start implements node.Service, starting the background data propagation thread // of the Whisper protocol. -func (whisper *Whisper) Start(*p2p.Server) error { +func (whisper *Whisper) Start() error { log.Info("started whisper v." + ProtocolVersionStr) whisper.wg.Add(1) go whisper.update() From c842bf8f4d9901a5eba15734d6ef9f55abd3c465 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Apr 2020 16:16:41 +0200 Subject: [PATCH 007/160] semi-functional impl --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 2 +- eth/backend.go | 14 +++++++++++--- les/client.go | 10 ++++++++++ node/node.go | 42 ++++++++++++++++++++++++++++-------------- node/service.go | 3 +++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 62aa44e26476..df9de41f00af 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -354,6 +354,7 @@ func geth(ctx *cli.Context) error { node := makeFullNode(ctx) defer node.Close() startNode(ctx, node) + node.Wait() return nil } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cd6d54ff05bb..4cbaed5f0324 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1700,7 +1700,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { }) } else { err = stack.RegisterBackendLifecycle(func(node *node.Node) (node.Backend, error) { - fullNode, err := eth.New(node.ServiceContext, cfg, node.Server()) + fullNode, err := eth.New(node.ServiceContext, cfg) if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) diff --git a/eth/backend.go b/eth/backend.go index 7d46485ee220..a7ef070caa9a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -116,7 +116,7 @@ func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config, p2pServer *p2p.Server) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -162,7 +162,6 @@ func New(ctx *node.ServiceContext, config *Config, p2pServer *p2p.Server) (*Ethe etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), - p2pServer: p2pServer, } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -523,7 +522,7 @@ func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManage func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } -// Protocols implements node.Service, returning all the currently configured +// Protocols implements node.Backend, returning all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { protos := make([]p2p.Protocol, len(ProtocolVersions)) @@ -538,6 +537,15 @@ func (s *Ethereum) Protocols() []p2p.Protocol { return protos } +// P2PServer implements node.Backend, registering the node's running p2p server with the Backend. +func (s *Ethereum) P2PServer(server *p2p.Server) error { + if server == nil { + return errors.New("p2p server is not running, cannot register with eth backend") // TODO is this error message okay? + } + s.p2pServer = server + return nil +} + // Start implements node.Service, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { diff --git a/les/client.go b/les/client.go index 430e37d56c82..ae81c79db430 100644 --- a/les/client.go +++ b/les/client.go @@ -18,6 +18,7 @@ package les import ( + "errors" "fmt" "time" @@ -278,6 +279,15 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { }, s.dialCandidates) } +// P2PServer implements node.Backend, registering the node's running p2p server with the Backend. +func (s *LightEthereum) P2PServer(server *p2p.Server) error { + if server == nil { + return errors.New("p2p server is not running, cannot register with les backend") // TODO is this error message okay? + } + s.p2pServer = server + return nil +} + // Start implements node.Service, starting all internal goroutines needed by the // light ethereum protocol implementation. func (s *LightEthereum) Start() error { diff --git a/node/node.go b/node/node.go index 6701dc34105b..7f6b71afeb39 100644 --- a/node/node.go +++ b/node/node.go @@ -104,7 +104,7 @@ func New(conf *Config) (*Node, error) { } // Note: any interaction with Config that would create/touch files // in the data directory or instance directory is delayed until Start. - return &Node{ + node := &Node{ accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, @@ -124,7 +124,10 @@ func New(conf *Config) (*Node, error) { }, eventmux: new(event.TypeMux), log: conf.Logger, - }, nil + } + node.ServiceContext.EventMux = node.eventmux + node.ServiceContext.AccountManager = node.accman + return node, nil } // Close stops the Node and releases resources acquired in @@ -232,32 +235,40 @@ func (n *Node) Start() error { // Initialize the p2p server. This creates the node key and // discovery databases. - n.server.Config = n.config.P2P - n.server.PrivateKey = n.config.NodeKey() - n.server.Name = n.config.NodeName() - n.server.Logger = n.log - if n.server.StaticNodes == nil { - n.server.StaticNodes = n.config.StaticNodes() + running := &p2p.Server{Config: n.config.P2P} + + running.Config.PrivateKey = n.config.NodeKey() + running.Config.Name = n.config.NodeName() + running.Config.Logger = n.log + if running.Config.StaticNodes == nil { + running.Config.StaticNodes = n.config.StaticNodes() } - if n.server.TrustedNodes == nil { - n.server.TrustedNodes = n.config.TrustedNodes() + if running.Config.TrustedNodes == nil { + running.Config.TrustedNodes = n.config.TrustedNodes() } - if n.server.NodeDatabase == "" { - n.server.NodeDatabase = n.config.NodeDB() + if running.Config.NodeDatabase == "" { + running.Config.NodeDatabase = n.config.NodeDB() } - running := &p2p.Server{Config: n.server.Config} - n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) + n.log.Info("Starting peer-to-peer node", "instance", running.Name) // add backend's protocols to the o2o server running.Protocols = append(running.Protocols, n.backend.Protocols()...) if err := running.Start(); err != nil { return convertFileLockError(err) } + + // Register the running p2p server with the Backend + if err := n.backend.P2PServer(running); err != nil { + running.Stop() + return err + } + // Start the backend if err := n.backend.Start(); err != nil { running.Stop() return err } + // Start each of the services var startedServices []reflect.Type for kind, service := range n.services { @@ -271,6 +282,7 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup startedServices = append(startedServices, kind) } + // Start each of the auxiliary services var startedAuxServices []reflect.Type for kind, auxService := range n.auxServices { @@ -284,6 +296,7 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup startedAuxServices = append(startedAuxServices, kind) } + // Lastly, start the configured RPC interfaces if err := n.startRPC(); err != nil { n.stopAllStartedServices(startedServices, startedAuxServices) @@ -300,6 +313,7 @@ func (n *Node) Start() error { // Finish initializing the startup n.server = running n.stop = make(chan struct{}) + log.Error("HERE") return nil } diff --git a/node/service.go b/node/service.go index 57bbe0c729a8..cd035c863b30 100644 --- a/node/service.go +++ b/node/service.go @@ -107,6 +107,9 @@ type Backend interface { // Protocols retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol + // Backend can register a P2P Server + P2PServer(server *p2p.Server) error + // Backend also implements the Service interface. Service } From 8dbba6008fe95b9715f2b350ef3436126d192b8b Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Apr 2020 16:47:50 +0200 Subject: [PATCH 008/160] fixed dev mode start-up, should be working fine now --- cmd/geth/main.go | 10 +++++++--- node/node.go | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index df9de41f00af..bb39c512cf49 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -464,9 +464,13 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { - utils.Fatalf("Ethereum service not running: %v", err) + // Check if node's backend is eth and that it exists // TODO fix this section up -- not sure if it's doing what it's supposed to. + ethereum, ok := stack.Backend().(*eth.Ethereum) + if !ok { + if stack.Backend() == nil { + utils.Fatalf("Ethereum service not running: backend is nil") + } + utils.Fatalf("Ethereum service not running: backend is not an eth backend") } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) diff --git a/node/node.go b/node/node.go index 7f6b71afeb39..3b1d88002a1b 100644 --- a/node/node.go +++ b/node/node.go @@ -296,7 +296,7 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup startedAuxServices = append(startedAuxServices, kind) } - + // Lastly, start the configured RPC interfaces if err := n.startRPC(); err != nil { n.stopAllStartedServices(startedServices, startedAuxServices) @@ -313,7 +313,6 @@ func (n *Node) Start() error { // Finish initializing the startup n.server = running n.stop = make(chan struct{}) - log.Error("HERE") return nil } From 2b67abf426ed51ac9e7994e3c50edac793c85d9b Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:36:07 +0200 Subject: [PATCH 009/160] registerrpc and regiterprotocols --- node/api.go | 4 +- node/node.go | 102 +++++++++++++++++++++++++++++---------------------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/node/api.go b/node/api.go index df2b2cd69405..4b2a0f99718c 100644 --- a/node/api.go +++ b/node/api.go @@ -186,7 +186,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { + if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { return false, err } return true, nil @@ -240,7 +240,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } } - if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil { + if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), modules, origins, api.node.config.WSExposeAll); err != nil { return false, err } return true, nil diff --git a/node/node.go b/node/node.go index 3b1d88002a1b..f813189b01f5 100644 --- a/node/node.go +++ b/node/node.go @@ -215,6 +215,24 @@ func (n *Node) RegisterAuxServiceLifecycle(constructor AuxiliaryServiceConstruct return nil } +func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { + if !n.running() { + return ErrNodeStopped + } + // add backend's protocols to the o2o server + n.server.Protocols = append(n.server.Protocols, protocols...) + return nil +} + +func (n *Node) RegisterRPC(apis []rpc.API) { + // Gather all the possible APIs to surface + apis = append(apis, n.backend.APIs()...) + for _, service := range n.services { + apis = append(apis, service.APIs()...) + } + n.rpcAPIs = apis +} + // running returns true if the node's p2p server is already running func (n *Node) running() bool { return n.server != nil @@ -226,7 +244,7 @@ func (n *Node) Start() error { defer n.lock.Unlock() // Short circuit if the node's already running - if n.server != nil { + if n.running() { return ErrNodeRunning } if err := n.openDataDir(); err != nil { @@ -235,37 +253,39 @@ func (n *Node) Start() error { // Initialize the p2p server. This creates the node key and // discovery databases. - running := &p2p.Server{Config: n.config.P2P} - - running.Config.PrivateKey = n.config.NodeKey() - running.Config.Name = n.config.NodeName() - running.Config.Logger = n.log - if running.Config.StaticNodes == nil { - running.Config.StaticNodes = n.config.StaticNodes() + n.server = &p2p.Server{Config: n.config.P2P} + n.server.Config.PrivateKey = n.config.NodeKey() + n.server.Config.Name = n.config.NodeName() + n.server.Config.Logger = n.log + if n.server.Config.StaticNodes == nil { + n.server.Config.StaticNodes = n.config.StaticNodes() } - if running.Config.TrustedNodes == nil { - running.Config.TrustedNodes = n.config.TrustedNodes() + if n.server.Config.TrustedNodes == nil { + n.server.Config.TrustedNodes = n.config.TrustedNodes() } - if running.Config.NodeDatabase == "" { - running.Config.NodeDatabase = n.config.NodeDB() + if n.server.Config.NodeDatabase == "" { + n.server.Config.NodeDatabase = n.config.NodeDB() } - n.log.Info("Starting peer-to-peer node", "instance", running.Name) - // add backend's protocols to the o2o server - running.Protocols = append(running.Protocols, n.backend.Protocols()...) - if err := running.Start(); err != nil { + // Start the p2p node + if err := n.server.Start(); err != nil { return convertFileLockError(err) } + n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) // Register the running p2p server with the Backend - if err := n.backend.P2PServer(running); err != nil { - running.Stop() + if err := n.backend.P2PServer(n.server); err != nil { + n.server.Stop() + return err + } + // Register the Backend's protocols with the p2p server + if err := n.RegisterProtocols(n.backend.Protocols()); err != nil { + n.server.Stop() return err } - // Start the backend if err := n.backend.Start(); err != nil { - running.Stop() + n.server.Stop() return err } @@ -275,7 +295,7 @@ func (n *Node) Start() error { // Start the next service, stopping all previous upon failure if err := service.Start(); err != nil { n.stopAllStartedServices(startedServices, nil) // TODO safe to pass nil here? - running.Stop() + n.server.Stop() return err } @@ -289,7 +309,7 @@ func (n *Node) Start() error { // Start the next auxiliary service, stopping all previous upon failure if err := auxService.Start(); err != nil { n.stopAllStartedServices(startedServices, startedAuxServices) - running.Stop() + n.server.Stop() return err } @@ -300,7 +320,7 @@ func (n *Node) Start() error { // Lastly, start the configured RPC interfaces if err := n.startRPC(); err != nil { n.stopAllStartedServices(startedServices, startedAuxServices) - running.Stop() + n.server.Stop() return err } // Finish initializing the service context @@ -311,7 +331,6 @@ func (n *Node) Start() error { n.ServiceContext.auxServices = n.auxServices // Finish initializing the startup - n.server = running n.stop = make(chan struct{}) return nil } @@ -354,28 +373,24 @@ func (n *Node) openDataDir() error { // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. func (n *Node) startRPC() error { - // Gather all the possible APIs to surface - apis := n.apis() - apis = append(apis, n.backend.APIs()...) - for _, service := range n.services { - apis = append(apis, service.APIs()...) - } + n.RegisterRPC(n.apis()) + // Start the various API endpoints, terminating all in case of errors - if err := n.startInProc(apis); err != nil { + if err := n.startInProc(); err != nil { return err } - if err := n.startIPC(apis); err != nil { + if err := n.startIPC(); err != nil { n.stopInProc() return err } - if err := n.startHTTP(n.httpHandler.Endpoint(), apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { + if err := n.startHTTP(n.httpHandler.Endpoint(), n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { n.stopIPC() n.stopInProc() return err } // if endpoints are not the same, start separate servers if n.httpHandler.Endpoint() != n.wsHandler.Endpoint() { - if err := n.startWS(n.wsHandler.Endpoint(), apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { + if err := n.startWS(n.wsHandler.Endpoint(), n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { n.stopHTTP() n.stopIPC() n.stopInProc() @@ -384,15 +399,14 @@ func (n *Node) startRPC() error { } // All API endpoints started successfully - n.rpcAPIs = apis return nil } // startInProc initializes an in-process RPC Endpoint. -func (n *Node) startInProc(apis []rpc.API) error { +func (n *Node) startInProc() error { // Register all the APIs exposed by the services handler := rpc.NewServer() - for _, api := range apis { + for _, api := range n.rpcAPIs { if err := handler.RegisterName(api.Namespace, api.Service); err != nil { return err } @@ -411,11 +425,11 @@ func (n *Node) stopInProc() { } // startIPC initializes and starts the IPC RPC Endpoint. -func (n *Node) startIPC(apis []rpc.API) error { +func (n *Node) startIPC() error { if n.ipcHandler.Endpoint() == "" { return nil // IPC disabled. } - listener, handler, err := rpc.StartIPCEndpoint(n.ipcHandler.Endpoint(), apis) + listener, handler, err := rpc.StartIPCEndpoint(n.ipcHandler.Endpoint(), n.rpcAPIs) if err != nil { return err } @@ -440,14 +454,14 @@ func (n *Node) stopIPC() { } // startHTTP initializes and starts the HTTP RPC Endpoint. -func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { +func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { // Short circuit if the HTTP Endpoint isn't being exposed if endpoint == "" { return nil } // register apis and create handler stack srv := rpc.NewServer() - err := RegisterApisFromWhitelist(apis, modules, srv, false) + err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, false) if err != nil { return err } @@ -489,7 +503,7 @@ func (n *Node) stopHTTP() { } // startWS initializes and starts the websocket RPC Endpoint. -func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error { +func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, exposeAll bool) error { // Short circuit if the WS Endpoint isn't being exposed if endpoint == "" { return nil @@ -497,7 +511,7 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig srv := rpc.NewServer() handler := srv.WebsocketHandler(wsOrigins) - err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) + err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, exposeAll) if err != nil { return err } @@ -524,7 +538,7 @@ func (n *Node) stopWS() { } if n.wsHandler.Srv != nil { n.wsHandler.Srv.Stop() - n.wsHandler.Srv = nil + n.wsHandler.Srv = nil } } From bf8a05b234615c55b32eaec11948e81b106c433e Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Apr 2020 14:30:24 +0200 Subject: [PATCH 010/160] fixed RegisterLifecycle to be generic --- cmd/faucet/faucet.go | 28 +++++++++------ cmd/utils/flags.go | 83 ++++++++++++++++++++++++++------------------ node/node.go | 83 ++++++++++++++------------------------------ node/service.go | 2 +- 4 files changed, 94 insertions(+), 102 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index a6daaa5dbd38..db2505b51cd3 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -235,24 +235,30 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u if err != nil { return nil, err } + // Assemble the Ethereum light client protocol - if err := stack.RegisterBackendLifecycle(func(stack *node.Node) (node.Backend, error) { - cfg := eth.DefaultConfig - cfg.SyncMode = downloader.LightSync - cfg.NetworkId = network - cfg.Genesis = genesis - return les.New(stack.ServiceContext, &cfg) - }); err != nil { + cfg := eth.DefaultConfig + cfg.SyncMode = downloader.LightSync + cfg.NetworkId = network + cfg.Genesis = genesis + lesBackend, err := les.New(stack.ServiceContext, &cfg) + // register the backend and lifecycle + if err := stack.RegisterBackend(lesBackend); err != nil { return nil, err } + stack.RegisterLifecycle(lesBackend) + // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { - var serv *les.LightEthereum - return ethstats.New(stack, stats, serv) - }); err != nil { + var serv *les.LightEthereum + ethstatsAuxService, err := ethstats.New(stack, stats, serv) + if err != nil { + return nil, err + } + if err := stack.RegisterAuxService(ethstatsAuxService); err != nil { return nil, err } + stack.RegisterLifecycle(ethstatsAuxService) } // Boot up the client and ensure it connects to bootnodes if err := stack.Start(); err != nil { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4cbaed5f0324..f915566f9dc4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,7 +19,6 @@ package utils import ( "crypto/ecdsa" - "errors" "fmt" "io" "io/ioutil" @@ -1693,61 +1692,79 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { // RegisterEthService adds an Ethereum client to the stack. func RegisterEthService(stack *node.Node, cfg *eth.Config) { - var err error if cfg.SyncMode == downloader.LightSync { - err = stack.RegisterBackendLifecycle(func(stack *node.Node) (node.Backend, error) { - return les.New(stack.ServiceContext, cfg) - }) + lesBackend, err := les.New(stack.ServiceContext, cfg) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + stack.RegisterLifecycle(lesBackend) + if err := stack.RegisterBackend(lesBackend); err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } } else { - err = stack.RegisterBackendLifecycle(func(node *node.Node) (node.Backend, error) { - fullNode, err := eth.New(node.ServiceContext, cfg) - if fullNode != nil && cfg.LightServ > 0 { - ls, _ := les.NewLesServer(fullNode, cfg) - fullNode.AddLesServer(ls) - } - return fullNode, err - }) - } - if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) + fullNode, err := eth.New(stack.ServiceContext, cfg) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + if fullNode != nil && cfg.LightServ > 0 { + ls, _ := les.NewLesServer(fullNode, cfg) + fullNode.AddLesServer(ls) + } + if err := stack.RegisterBackend(fullNode); err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + stack.RegisterLifecycle(fullNode) } } // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - if err := stack.RegisterServiceLifecycle(func(stack *node.Node) (node.Service, error) { - return whisper.New(cfg), nil - }); err != nil { + whisperService := whisper.New(cfg) + if err := stack.RegisterService(whisperService); err != nil { Fatalf("Failed to register the Whisper service: %v", err) } + stack.RegisterLifecycle(whisperService) } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. func RegisterEthStatsService(stack *node.Node, url string) { - if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { - return ethstats.New(stack, url, stack.Backend()) - }); err != nil { + ethstatsAuxService, err := ethstats.New(stack, url, stack.Backend()) + if err != nil { Fatalf("Failed to register the Ethereum Stats service: %v", err) } + if err := stack.RegisterAuxService(ethstatsAuxService); err != nil { + Fatalf("Failed to register the Ethereum Stats service: %v", err) + } + stack.RegisterLifecycle(ethstatsAuxService) } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - if err := stack.RegisterAuxServiceLifecycle(func(stack *node.Node) (node.AuxiliaryService, error) { - // Try to construct the GraphQL service backed by a full node - if ethBackend, ok := stack.Backend().(*eth.Ethereum); ok { - return graphql.New(ethBackend.APIBackend, endpoint, cors, vhosts, timeouts) + // Try to construct the GraphQL service backed by a full node + if ethBackend, ok := stack.Backend().(*eth.Ethereum); ok { + graphqlAuxService, err := graphql.New(ethBackend.APIBackend, endpoint, cors, vhosts, timeouts) + if err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) + } + if err := stack.RegisterAuxService(graphqlAuxService); err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) + } + stack.RegisterLifecycle(graphqlAuxService) + } + // Try to construct the GraphQL service backed by a light node + if lesBackend, ok := stack.Backend().(*les.LightEthereum); ok { + graphqlAuxService, err := graphql.New(lesBackend.ApiBackend, endpoint, cors, vhosts, timeouts) + if err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) } - // Try to construct the GraphQL service backed by a light node - if lesBackend, ok := stack.Backend().(*les.LightEthereum); ok { - return graphql.New(lesBackend.ApiBackend, endpoint, cors, vhosts, timeouts) + if err := stack.RegisterAuxService(graphqlAuxService); err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) } - // Well, this should not have happened, bail out - return nil, errors.New("no Ethereum service") - }); err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) + stack.RegisterLifecycle(graphqlAuxService) } + // Well, this should not have happened, bail out + Fatalf("no Ethereum service") } func SetupMetrics(ctx *cli.Context) { diff --git a/node/node.go b/node/node.go index f813189b01f5..b4292775acdf 100644 --- a/node/node.go +++ b/node/node.go @@ -51,6 +51,8 @@ type Node struct { ServiceContext *ServiceContext + lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + backend Backend // The registered Backend of the node services map[reflect.Type]Service // Currently running services auxServices map[reflect.Type]AuxiliaryService // Currently running auxiliary services @@ -153,18 +155,21 @@ func (n *Node) Close() error { } } +func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { + n.lifecycles = append(n.lifecycles, lifecycle) +} + // TODO document -func (n *Node) RegisterBackendLifecycle(constructor BackendConstructor) error { +func (n *Node) RegisterBackend(backend Backend) error { n.lock.Lock() defer n.lock.Unlock() - + // check if p2p node is already running if n.running() { return ErrNodeRunning } - - backend, err := constructor(n) - if err != nil { - return err + // check that there is not already a backend registered on the node + if n.backend != nil { + return errors.New("a backend has already been registered on the node") // TODO is this error okay? } n.backend = backend return nil @@ -172,7 +177,7 @@ func (n *Node) RegisterBackendLifecycle(constructor BackendConstructor) error { // Register injects a new service into the node's stack. The service created by // the passed constructor must be unique in its type with regard to sibling ones. -func (n *Node) RegisterServiceLifecycle(constructor ServiceConstructor) error { +func (n *Node) RegisterService(service Service) error { n.lock.Lock() defer n.lock.Unlock() @@ -180,10 +185,6 @@ func (n *Node) RegisterServiceLifecycle(constructor ServiceConstructor) error { return ErrNodeRunning } - service, err := constructor(n) - if err != nil { - return err - } kind := reflect.TypeOf(service) if _, exists := n.services[kind]; exists { return &DuplicateServiceError{Kind: kind} @@ -194,23 +195,19 @@ func (n *Node) RegisterServiceLifecycle(constructor ServiceConstructor) error { } // TODO document -func (n *Node) RegisterAuxServiceLifecycle(constructor AuxiliaryServiceConstructor) error { +func (n *Node) RegisterAuxService(auxService AuxiliaryService) error { n.lock.Lock() defer n.lock.Unlock() if n.running() { return ErrNodeRunning } - - service, err := constructor(n) - if err != nil { - return err - } - kind := reflect.TypeOf(service) + // make sure auxiliary service is not duplicated + kind := reflect.TypeOf(auxService) if _, exists := n.auxServices[kind]; exists { return &DuplicateServiceError{Kind: kind} } - n.auxServices[kind] = service + n.auxServices[kind] = auxService return nil } @@ -283,43 +280,19 @@ func (n *Node) Start() error { n.server.Stop() return err } - // Start the backend - if err := n.backend.Start(); err != nil { - n.server.Stop() - return err - } - // Start each of the services - var startedServices []reflect.Type - for kind, service := range n.services { - // Start the next service, stopping all previous upon failure - if err := service.Start(); err != nil { - n.stopAllStartedServices(startedServices, nil) // TODO safe to pass nil here? - n.server.Stop() - - return err + // Start all registered lifecycles + var started []Lifecycle + for _, lifecycle := range n.lifecycles { + if err := lifecycle.Start(); err != nil { + n.stopLifecycles(started) } - // Mark the service started for potential cleanup - startedServices = append(startedServices, kind) - } - - // Start each of the auxiliary services - var startedAuxServices []reflect.Type - for kind, auxService := range n.auxServices { - // Start the next auxiliary service, stopping all previous upon failure - if err := auxService.Start(); err != nil { - n.stopAllStartedServices(startedServices, startedAuxServices) - n.server.Stop() - - return err - } - // Mark the service started for potential cleanup - startedAuxServices = append(startedAuxServices, kind) + started = append(started, lifecycle) } // Lastly, start the configured RPC interfaces if err := n.startRPC(); err != nil { - n.stopAllStartedServices(startedServices, startedAuxServices) + n.stopLifecycles(n.lifecycles) n.server.Stop() return err } @@ -335,13 +308,9 @@ func (n *Node) Start() error { return nil } -func (n *Node) stopAllStartedServices(startedServices []reflect.Type, startedAuxServices []reflect.Type) { - n.backend.Stop() - for _, kind := range startedServices { - n.services[kind].Stop() - } - for _, kind := range startedAuxServices { - n.auxServices[kind].Stop() +func (n *Node) stopLifecycles(started []Lifecycle) { + for _, lifecycle := range started { + lifecycle.Stop() } } diff --git a/node/service.go b/node/service.go index cd035c863b30..442d308afadc 100644 --- a/node/service.go +++ b/node/service.go @@ -141,7 +141,7 @@ type AuxiliaryService interface { Lifecycle } -// TODO, this might be overkill +// TODO document type Lifecycle interface { // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. From fc7bff86f5339a01ec9c07a0da9765c2fe1b738e Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Apr 2020 14:40:23 +0200 Subject: [PATCH 011/160] reverted capitalization of endpoint -- accident --- node/api.go | 4 ++-- node/config.go | 14 +++++++------- node/config_test.go | 2 +- node/doc.go | 6 +++--- node/endpoints.go | 4 ++-- node/node.go | 39 ++++++++++++++++++++------------------- node/node_test.go | 2 +- 7 files changed, 36 insertions(+), 35 deletions(-) diff --git a/node/api.go b/node/api.go index 4b2a0f99718c..f29911c61818 100644 --- a/node/api.go +++ b/node/api.go @@ -192,7 +192,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis return true, nil } -// StopRPC terminates an already running HTTP RPC API Endpoint. +// StopRPC terminates an already running HTTP RPC API endpoint. func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -246,7 +246,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str return true, nil } -// StopWS terminates an already running websocket RPC API Endpoint. +// StopWS terminates an already running websocket RPC API endpoint. func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() diff --git a/node/config.go b/node/config.go index d0c25c125b55..61566b7bee9b 100644 --- a/node/config.go +++ b/node/config.go @@ -98,7 +98,7 @@ type Config struct { // SmartCardDaemonPath is the path to the smartcard daemon's socket SmartCardDaemonPath string `toml:",omitempty"` - // IPCPath is the requested location to place the IPC Endpoint. If the path is + // IPCPath is the requested location to place the IPC endpoint. If the path is // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or // relative), then that specific path is enforced. An empty path disables IPC. @@ -193,7 +193,7 @@ type Config struct { oldGethResourceWarning bool } -// IPCEndpoint resolves an IPC Endpoint based on a configured value, taking into +// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into // account the set data folders as well as the designated platform we're currently // running on. func (c *Config) IPCEndpoint() string { @@ -238,7 +238,7 @@ func DefaultIPCEndpoint(clientIdentifier string) string { return config.IPCEndpoint() } -// HTTPEndpoint resolves an HTTP Endpoint based on the configured host interface +// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface // and port parameters. func (c *Config) HTTPEndpoint() string { if c.HTTPHost == "" { @@ -247,7 +247,7 @@ func (c *Config) HTTPEndpoint() string { return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) } -// GraphQLEndpoint resolves a GraphQL Endpoint based on the configured host interface +// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface // and port parameters. func (c *Config) GraphQLEndpoint() string { if c.GraphQLHost == "" { @@ -256,13 +256,13 @@ func (c *Config) GraphQLEndpoint() string { return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort) } -// DefaultHTTPEndpoint returns the HTTP Endpoint used by default. +// DefaultHTTPEndpoint returns the HTTP endpoint used by default. func DefaultHTTPEndpoint() string { config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} return config.HTTPEndpoint() } -// WSEndpoint resolves a websocket Endpoint based on the configured host interface +// WSEndpoint resolves a websocket endpoint based on the configured host interface // and port parameters. func (c *Config) WSEndpoint() string { if c.WSHost == "" { @@ -271,7 +271,7 @@ func (c *Config) WSEndpoint() string { return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) } -// DefaultWSEndpoint returns the websocket Endpoint used by default. +// DefaultWSEndpoint returns the websocket endpoint used by default. func DefaultWSEndpoint() string { config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} return config.WSEndpoint() diff --git a/node/config_test.go b/node/config_test.go index 17c473f655d2..00c24a239123 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -99,7 +99,7 @@ func TestIPCPathResolution(t *testing.T) { // Only run when platform/test match if (runtime.GOOS == "windows") == test.Windows { if endpoint := (&Config{DataDir: test.DataDir, IPCPath: test.IPCPath}).IPCEndpoint(); endpoint != test.Endpoint { - t.Errorf("test %d: IPC Endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) + t.Errorf("test %d: IPC endpoint mismatch: have %s, want %s", i, endpoint, test.Endpoint) } } } diff --git a/node/doc.go b/node/doc.go index 3c6b696b3218..e3cc58e5f49c 100644 --- a/node/doc.go +++ b/node/doc.go @@ -36,7 +36,7 @@ about other hosts is persisted. JSON-RPC servers which run HTTP, WebSocket or IPC can be started on a Node. RPC modules offered by registered services will be offered on those endpoints. Users can restrict any -Endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3" +endpoint to a subset of RPC modules. Node itself offers the "debug", "admin" and "web3" modules. Service implementations can open LevelDB databases through the service context. Package @@ -77,14 +77,14 @@ directory. Node instance A opens the database "db", node instance B opens the da nodekey -- devp2p node key of instance A nodes/ -- devp2p discovery knowledge database of instance A db/ -- LevelDB content for "db" - A.ipc -- JSON-RPC UNIX domain socket Endpoint of instance A + A.ipc -- JSON-RPC UNIX domain socket endpoint of instance A B/ nodekey -- devp2p node key of node B nodes/ -- devp2p discovery knowledge database of instance B static-nodes.json -- devp2p static node list of instance B db/ -- LevelDB content for "db" db-2/ -- LevelDB content for "db-2" - B.ipc -- JSON-RPC UNIX domain socket Endpoint of instance B + B.ipc -- JSON-RPC UNIX domain socket endpoint of instance B keystore/ -- account key store, used by both instances */ package node diff --git a/node/endpoints.go b/node/endpoints.go index 13479b88a191..5c9eddd88638 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// StartHTTPEndpoint starts the HTTP RPC Endpoint. +// StartHTTPEndpoint starts the HTTP RPC endpoint. func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { // start the HTTP listener var ( @@ -48,7 +48,7 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. return httpSrv, listener.Addr(), err } -// startWSEndpoint starts a websocket Endpoint. +// startWSEndpoint starts a websocket endpoint. func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { // start the HTTP listener var ( diff --git a/node/node.go b/node/node.go index b4292775acdf..f487266dd833 100644 --- a/node/node.go +++ b/node/node.go @@ -371,7 +371,7 @@ func (n *Node) startRPC() error { return nil } -// startInProc initializes an in-process RPC Endpoint. +// startInProc initializes an in-process RPC endpoint. func (n *Node) startInProc() error { // Register all the APIs exposed by the services handler := rpc.NewServer() @@ -385,7 +385,7 @@ func (n *Node) startInProc() error { return nil } -// stopInProc terminates the in-process RPC Endpoint. +// stopInProc terminates the in-process RPC endpoint. func (n *Node) stopInProc() { if n.inprocHandler != nil { n.inprocHandler.Stop() @@ -393,7 +393,7 @@ func (n *Node) stopInProc() { } } -// startIPC initializes and starts the IPC RPC Endpoint. +// startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC() error { if n.ipcHandler.Endpoint() == "" { return nil // IPC disabled. @@ -404,17 +404,17 @@ func (n *Node) startIPC() error { } n.ipcHandler.Listener = listener n.ipcHandler.handler = handler - n.log.Info("IPC Endpoint opened", "url", n.ipcHandler.Endpoint()) + n.log.Info("IPC endpoint opened", "url", n.ipcHandler.Endpoint()) return nil } -// stopIPC terminates the IPC RPC Endpoint. +// stopIPC terminates the IPC RPC endpoint. func (n *Node) stopIPC() { if n.ipcHandler.Listener != nil { n.ipcHandler.Listener.Close() n.ipcHandler.Listener = nil - n.log.Info("IPC Endpoint closed", "url", n.ipcHandler.Endpoint()) + n.log.Info("IPC endpoint closed", "url", n.ipcHandler.Endpoint()) } if n.ipcHandler.Srv != nil { n.ipcHandler.Srv.Stop() @@ -422,9 +422,9 @@ func (n *Node) stopIPC() { } } -// startHTTP initializes and starts the HTTP RPC Endpoint. +// startHTTP initializes and starts the HTTP RPC endpoint. func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { - // Short circuit if the HTTP Endpoint isn't being exposed + // Short circuit if the HTTP endpoint isn't being exposed if endpoint == "" { return nil } @@ -443,11 +443,11 @@ func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhost if err != nil { return err } - n.log.Info("HTTP Endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), + n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) if n.httpHandler.Endpoint() == n.wsHandler.Endpoint() { - n.log.Info("WebSocket Endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) } // All listeners booted successfully n.httpHandler.endpoint = endpoint @@ -457,7 +457,7 @@ func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhost return nil } -// stopHTTP terminates the HTTP RPC Endpoint. +// stopHTTP terminates the HTTP RPC endpoint. func (n *Node) stopHTTP() { if n.httpHandler.Listener != nil { url := fmt.Sprintf("http://%v/", n.httpHandler.Listener.Addr()) @@ -471,9 +471,9 @@ func (n *Node) stopHTTP() { } } -// startWS initializes and starts the websocket RPC Endpoint. +// startWS initializes and starts the websocket RPC endpoint. func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, exposeAll bool) error { - // Short circuit if the WS Endpoint isn't being exposed + // Short circuit if the WS endpoint isn't being exposed if endpoint == "" { return nil } @@ -488,7 +488,7 @@ func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, ex if err != nil { return err } - n.log.Info("WebSocket Endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) // All listeners booted successfully n.wsHandler.endpoint = endpoint n.wsHandler.Listener = listener @@ -497,13 +497,13 @@ func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, ex return nil } -// stopWS terminates the websocket RPC Endpoint. +// stopWS terminates the websocket RPC endpoint. func (n *Node) stopWS() { if n.wsHandler.Listener != nil { n.wsHandler.Listener.Close() n.wsHandler.Listener = nil - n.log.Info("WebSocket Endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsHandler.Endpoint)) + n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsHandler.Endpoint)) } if n.wsHandler.Srv != nil { n.wsHandler.Srv.Stop() @@ -679,12 +679,12 @@ func (n *Node) AccountManager() *accounts.Manager { return n.accman } -// IPCEndpoint retrieves the current IPC Endpoint used by the protocol stack. +// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { return n.ipcHandler.Endpoint() } -// HTTPEndpoint retrieves the current HTTP Endpoint used by the protocol stack. +// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. func (n *Node) HTTPEndpoint() string { n.lock.Lock() defer n.lock.Unlock() @@ -695,7 +695,8 @@ func (n *Node) HTTPEndpoint() string { return n.httpHandler.Endpoint() } -// WSEndpoint retrieves the current WS Endpoint used by the protocol stack. +// WSEndpoint retrieves the current WS endpoint +// used by the protocol stack. func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() diff --git a/node/node_test.go b/node/node_test.go index 501d3a8a5d1e..d62194a87681 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -651,7 +651,7 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the given Endpoint", err) + t.Error("could not issue a GET request to the given endpoint", err) } return resp } From ca32adfa2e2ce7e3100bf82a3d7c88a685fb1666 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Apr 2020 14:45:33 +0200 Subject: [PATCH 012/160] removed TODO ; --- eth/backend.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index a7ef070caa9a..d0cf9f779878 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -65,8 +65,6 @@ type LesServer interface { // Ethereum implements the Ethereum full node service. type Ethereum struct { - // TODO needs a p2pServer - config *Config // Handlers From 8290243a7f4600eb5e9c9efea04e74ea44da66b4 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Apr 2020 15:23:28 +0200 Subject: [PATCH 013/160] remove unnecessary constructor types --- node/service.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/node/service.go b/node/service.go index 442d308afadc..7a482161e1d9 100644 --- a/node/service.go +++ b/node/service.go @@ -93,16 +93,6 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.Config.ExtRPCEnabled() } -// TODO document -type BackendConstructor func(node *Node) (Backend, error) - -// ServiceConstructor is the function signature of the constructors needed to be -// registered for service instantiation. -type ServiceConstructor func(node *Node) (Service, error) - -//TODO document -type AuxiliaryServiceConstructor func(node *Node) (AuxiliaryService, error) - type Backend interface { // Protocols retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol From 95bc2569ee19b4100539d132939156339ca7e0f5 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 29 Apr 2020 13:14:29 +0200 Subject: [PATCH 014/160] fixes http/ws servers not being able to be started on given host/port --- graphql/service.go | 11 +++-- node/api.go | 14 +++--- node/endpoints.go | 4 +- node/node.go | 114 ++++++++++++++++++++++++--------------------- node/rpcstack.go | 5 +- 5 files changed, 79 insertions(+), 69 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index 1f4d31356f27..99dd87ef88eb 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,6 +17,7 @@ package graphql import ( + "context" "fmt" "net" "net/http" @@ -76,7 +77,8 @@ func (s *Service) Start() error { go httpSrv.Serve(listener) log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.graphqlServer.Endpoint)) // add information to graphql http server - s.graphqlServer.Listener = listener + s.graphqlServer.Server = httpSrv + s.graphqlServer.ListenerAddr = listener.Addr() s.graphqlServer.SetHandler(handler) return nil } @@ -102,10 +104,9 @@ func newHandler(backend ethapi.Backend) (http.Handler, error) { // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. func (s *Service) Stop() error { - if s.graphqlServer.Listener != nil { - s.graphqlServer.Listener.Close() - s.graphqlServer.Listener = nil - log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.graphqlServer.Endpoint)) + if s.graphqlServer.Server != nil { + s.graphqlServer.Server.Shutdown(context.Background()) + log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%v", s.graphqlServer.ListenerAddr)) } return nil } diff --git a/node/api.go b/node/api.go index f29911c61818..01a206a6438f 100644 --- a/node/api.go +++ b/node/api.go @@ -147,8 +147,8 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.httpHandler != nil { - return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpHandler.Endpoint) + if api.node.http != nil { + return false, fmt.Errorf("HTTP RPC already running on %s", api.node.http.Endpoint) } if host == nil { @@ -178,7 +178,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - modules := api.node.httpHandler.Whitelist + modules := api.node.http.Whitelist if apis != nil { modules = nil for _, m := range strings.Split(*apis, ",") { @@ -197,7 +197,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.httpHandler == nil { + if api.node.http == nil { return false, fmt.Errorf("HTTP RPC not running") } api.node.stopHTTP() @@ -209,8 +209,8 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.wsHandler != nil { - return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsHandler.Endpoint) + if api.node.ws != nil { + return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.ws.Endpoint) } if host == nil { @@ -251,7 +251,7 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.wsHandler == nil { + if api.node.ws == nil { return false, fmt.Errorf("WebSocket RPC not running") } api.node.stopWS() diff --git a/node/endpoints.go b/node/endpoints.go index 5c9eddd88638..1baa1b5c417f 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -26,7 +26,7 @@ import ( ) // StartHTTPEndpoint starts the HTTP RPC endpoint. -func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { +func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) { // start the HTTP listener var ( listener net.Listener @@ -49,7 +49,7 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. } // startWSEndpoint starts a websocket endpoint. -func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { +func startWSEndpoint(endpoint string, handler http.Handler) (*http.Server, net.Addr, error) { // start the HTTP listener var ( listener net.Listener diff --git a/node/node.go b/node/node.go index f487266dd833..db6e3b22cfca 100644 --- a/node/node.go +++ b/node/node.go @@ -60,9 +60,9 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - ipcHandler *HttpServer // TODO - httpHandler *HttpServer // TODO - wsHandler *HttpServer // TODO + ipc *HttpServer // TODO + http *HttpServer // TODO + ws *HttpServer // TODO stop chan struct{} // Channel to wait for termination notifications @@ -115,14 +115,18 @@ func New(conf *Config) (*Node, error) { }, services: make(map[reflect.Type]Service), auxServices: make(map[reflect.Type]AuxiliaryService), - ipcHandler: &HttpServer{ + ipc: &HttpServer{ endpoint: conf.IPCEndpoint(), }, - httpHandler: &HttpServer{ + http: &HttpServer{ endpoint: conf.HTTPEndpoint(), + host: conf.HTTPHost, + port: conf.HTTPPort, }, - wsHandler: &HttpServer{ + ws: &HttpServer{ endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, }, eventmux: new(event.TypeMux), log: conf.Logger, @@ -352,14 +356,14 @@ func (n *Node) startRPC() error { n.stopInProc() return err } - if err := n.startHTTP(n.httpHandler.Endpoint(), n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { + if err := n.startHTTP(n.http.Endpoint(), n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { n.stopIPC() n.stopInProc() return err } // if endpoints are not the same, start separate servers - if n.httpHandler.Endpoint() != n.wsHandler.Endpoint() { - if err := n.startWS(n.wsHandler.Endpoint(), n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { + if n.http.Endpoint() != n.ws.Endpoint() { + if err := n.startWS(n.ws.Endpoint(), n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { n.stopHTTP() n.stopIPC() n.stopInProc() @@ -395,30 +399,30 @@ func (n *Node) stopInProc() { // startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC() error { - if n.ipcHandler.Endpoint() == "" { + if n.ipc.Endpoint() == "" { return nil // IPC disabled. } - listener, handler, err := rpc.StartIPCEndpoint(n.ipcHandler.Endpoint(), n.rpcAPIs) + listener, handler, err := rpc.StartIPCEndpoint(n.ipc.Endpoint(), n.rpcAPIs) if err != nil { return err } - n.ipcHandler.Listener = listener - n.ipcHandler.handler = handler - n.log.Info("IPC endpoint opened", "url", n.ipcHandler.Endpoint()) + n.ipc.Listener = listener + n.ipc.handler = handler + n.log.Info("IPC endpoint opened", "url", n.ipc.Endpoint()) return nil } // stopIPC terminates the IPC RPC endpoint. func (n *Node) stopIPC() { - if n.ipcHandler.Listener != nil { - n.ipcHandler.Listener.Close() - n.ipcHandler.Listener = nil + if n.ipc.Listener != nil { + n.ipc.Listener.Close() + n.ipc.Listener = nil - n.log.Info("IPC endpoint closed", "url", n.ipcHandler.Endpoint()) + n.log.Info("IPC endpoint closed", "url", n.ipc.Endpoint()) } - if n.ipcHandler.Srv != nil { - n.ipcHandler.Srv.Stop() - n.ipcHandler.Srv = nil + if n.ipc.Srv != nil { + n.ipc.Srv.Stop() + n.ipc.Srv = nil } } @@ -436,38 +440,39 @@ func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhost } handler := NewHTTPHandlerStack(srv, cors, vhosts) // wrap handler in websocket handler only if websocket port is the same as http rpc - if n.httpHandler.Endpoint() == n.wsHandler.Endpoint() { + if n.http.Endpoint() == n.ws.Endpoint() { handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) } httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) if err != nil { return err } - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), + n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) - if n.httpHandler.Endpoint() == n.wsHandler.Endpoint() { - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) + if n.http.Endpoint() == n.ws.Endpoint() { + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) } // All listeners booted successfully - n.httpHandler.endpoint = endpoint - n.httpHandler.Listener = listener - n.httpHandler.Srv = srv + n.http.endpoint = endpoint + n.http.Server = httpServer + n.http.ListenerAddr = addr + n.http.Srv = srv return nil } // stopHTTP terminates the HTTP RPC endpoint. func (n *Node) stopHTTP() { - if n.httpHandler.Listener != nil { - url := fmt.Sprintf("http://%v/", n.httpHandler.Listener.Addr()) - n.httpHandler.Listener.Close() - n.httpHandler.Listener = nil + if n.http.Server != nil { + url := fmt.Sprintf("http://%v/", n.http.ListenerAddr) + // Don't bother imposing a timeout here. + n.http.Server.Shutdown(context.Background()) n.log.Info("HTTP Endpoint closed", "url", url) } - if n.httpHandler.Srv != nil { - n.httpHandler.Srv.Stop() - n.httpHandler.Srv = nil + if n.http.Srv != nil { + n.http.Srv.Stop() + n.http.Srv = nil } } @@ -488,26 +493,27 @@ func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, ex if err != nil { return err } - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", addr)) // All listeners booted successfully - n.wsHandler.endpoint = endpoint - n.wsHandler.Listener = listener - n.wsHandler.Srv = srv + n.ws.endpoint = endpoint + n.ws.ListenerAddr = addr + n.ws.Server = httpServer + n.ws.Srv = srv return nil } // stopWS terminates the websocket RPC endpoint. func (n *Node) stopWS() { - if n.wsHandler.Listener != nil { - n.wsHandler.Listener.Close() - n.wsHandler.Listener = nil - - n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsHandler.Endpoint)) + if n.ws.Server != nil { + url := fmt.Sprintf("http://%v/", n.ws.ListenerAddr) + // Don't bother imposing a timeout here. + n.ws.Server.Shutdown(context.Background()) + n.log.Info("HTTP Endpoint closed", "url", url) } - if n.wsHandler.Srv != nil { - n.wsHandler.Srv.Stop() - n.wsHandler.Srv = nil + if n.ws.Srv != nil { + n.ws.Srv.Stop() + n.ws.Srv = nil } } @@ -681,7 +687,7 @@ func (n *Node) AccountManager() *accounts.Manager { // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { - return n.ipcHandler.Endpoint() + return n.ipc.Endpoint() } // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. @@ -689,10 +695,10 @@ func (n *Node) HTTPEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.httpHandler.Listener != nil { - return n.httpHandler.Listener.Addr().String() + if n.http.Server != nil { + return n.http.ListenerAddr.String() } - return n.httpHandler.Endpoint() + return n.http.Endpoint() } // WSEndpoint retrieves the current WS endpoint @@ -701,10 +707,10 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.wsHandler.Listener != nil { - return n.wsHandler.Listener.Addr().String() + if n.ws.Server != nil { + return n.ws.ListenerAddr.String() } - return n.wsHandler.Endpoint() + return n.ws.Endpoint() } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/rpcstack.go b/node/rpcstack.go index 3cf8a9c46c08..fbb74cd7f97b 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -34,7 +34,10 @@ import ( type HttpServer struct { handler http.Handler Srv *rpc.Server - Listener net.Listener + Server *http.Server + + Listener net.Listener + ListenerAddr net.Addr endpoint string host string From 0ba43ecfa19798a8a34f0f35972d9bf9aea91ecf Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Sun, 3 May 2020 20:41:35 +0200 Subject: [PATCH 015/160] registerHTTP but not w the signature felix suggested --- ethstats/ethstats.go | 2 +- graphql/service.go | 6 +-- node/node.go | 89 ++++++++++++++++++++++++++++++++++++-------- node/rpcstack.go | 34 ++++++++++++++--- node/service.go | 3 +- 5 files changed, 108 insertions(+), 26 deletions(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 81283370c8ce..c9f6343da56f 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -134,7 +134,7 @@ func (s *Service) Stop() error { return nil } -func (s *Service) Server() *node.HttpServer { return nil } +func (s *Service) Server() *node.HTTPServer { return nil } // loop keeps trying to connect to the netstats server, reporting chain events // until termination. diff --git a/graphql/service.go b/graphql/service.go index 99dd87ef88eb..b88e7109e561 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -33,14 +33,14 @@ import ( // Service encapsulates a GraphQL service. type Service struct { backend ethapi.Backend // The backend that queries will operate on. - graphqlServer *node.HttpServer + graphqlServer *node.HTTPServer } // New constructs a new GraphQL service instance. func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) { service := &Service{ backend: backend, - graphqlServer: &node.HttpServer{ + graphqlServer: &node.HTTPServer{ Timeouts: timeouts, Vhosts: vhosts, CorsAllowedOrigins: cors, @@ -111,6 +111,6 @@ func (s *Service) Stop() error { return nil } -func (s *Service) Server() *node.HttpServer { +func (s *Service) Server() *node.HTTPServer { return s.graphqlServer } diff --git a/node/node.go b/node/node.go index db6e3b22cfca..c9d639945eea 100644 --- a/node/node.go +++ b/node/node.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "net" + "net/http" "os" "path/filepath" "reflect" @@ -60,9 +62,9 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - ipc *HttpServer // TODO - http *HttpServer // TODO - ws *HttpServer // TODO + ipc *HTTPServer // TODO + http *HTTPServer // TODO + ws *HTTPServer // TODO stop chan struct{} // Channel to wait for termination notifications @@ -115,15 +117,23 @@ func New(conf *Config) (*Node, error) { }, services: make(map[reflect.Type]Service), auxServices: make(map[reflect.Type]AuxiliaryService), - ipc: &HttpServer{ + ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, - http: &HttpServer{ + http: &HTTPServer{ + CorsAllowedOrigins: conf.HTTPCors, + Vhosts: conf.HTTPVirtualHosts, + Whitelist: conf.HTTPModules, + Timeouts: conf.HTTPTimeouts, + Srv: rpc.NewServer(), endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, port: conf.HTTPPort, }, - ws: &HttpServer{ + ws: &HTTPServer{ + CorsAllowedOrigins: conf.WSOrigins, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), endpoint: conf.WSEndpoint(), host: conf.WSHost, port: conf.WSPort, @@ -159,6 +169,7 @@ func (n *Node) Close() error { } } +// TODO document func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { n.lifecycles = append(n.lifecycles, lifecycle) } @@ -234,6 +245,36 @@ func (n *Node) RegisterRPC(apis []rpc.API) { n.rpcAPIs = apis } +// TODO what is the purpose of this function? Define it and document it. +func (n *Node) RegisterHTTP(h *HTTPServer, exposeAll bool) error { + // register apis and create handler stack + err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) + if err != nil { + return err + } + + // start the HTTP listener + listener, err := net.Listen("tcp", h.endpoint) + if err != nil { + return err + } + // create the HTTP server + httpSrv := &http.Server{Handler: h.handler} + // check timeouts if they exist + if h.Timeouts != (rpc.HTTPTimeouts{}) { + CheckTimeouts(&h.Timeouts) + httpSrv.ReadTimeout = h.Timeouts.ReadTimeout + httpSrv.WriteTimeout = h.Timeouts.WriteTimeout + httpSrv.IdleTimeout = h.Timeouts.IdleTimeout + } + // complete the HTTPServer + h.Listener = listener + h.ListenerAddr = listener.Addr() + h.Server = httpSrv + + return nil +} + // running returns true if the node's p2p server is already running func (n *Node) running() bool { return n.server != nil @@ -312,6 +353,7 @@ func (n *Node) Start() error { return nil } +// TODO document func (n *Node) stopLifecycles(started []Lifecycle) { for _, lifecycle := range started { lifecycle.Stop() @@ -347,7 +389,6 @@ func (n *Node) openDataDir() error { // assumptions about the state of the node. func (n *Node) startRPC() error { n.RegisterRPC(n.apis()) - // Start the various API endpoints, terminating all in case of errors if err := n.startInProc(); err != nil { return err @@ -356,21 +397,37 @@ func (n *Node) startRPC() error { n.stopInProc() return err } - if err := n.startHTTP(n.http.Endpoint(), n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { - n.stopIPC() - n.stopInProc() - return err + // create and start http server if the endpoint exists + if n.http.endpoint != "" { + // wrap handler in websocket handler only if websocket port is the same as http rpc + n.http.handler = NewHTTPHandlerStack(n.http.Srv, n.http.CorsAllowedOrigins, n.http.Vhosts) + if n.http.endpoint == n.ws.endpoint { + n.http.handler = NewWebsocketUpgradeHandler(n.http.handler, n.http.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins)) + } + if err := n.RegisterHTTP(n.http, false); err != nil { + n.stopIPC() + n.stopInProc() + return err + } + n.http.Start() + n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", n.http.ListenerAddr), + "cors", strings.Join(n.http.CorsAllowedOrigins, ","), + "vhosts", strings.Join(n.http.Vhosts, ",")) + if n.http.Endpoint() == n.ws.Endpoint() { + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", n.http.ListenerAddr)) + } } - // if endpoints are not the same, start separate servers - if n.http.Endpoint() != n.ws.Endpoint() { - if err := n.startWS(n.ws.Endpoint(), n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { - n.stopHTTP() + // create and start ws server if the endpoint exists + if n.ws.endpoint != "" && n.http.endpoint != n.ws.endpoint { + n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) + if err := n.RegisterHTTP(n.ws, n.config.WSExposeAll); err != nil { n.stopIPC() n.stopInProc() return err } + n.ws.Start() + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", n.ws.ListenerAddr)) } - // All API endpoints started successfully return nil } diff --git a/node/rpcstack.go b/node/rpcstack.go index fbb74cd7f97b..8696c2808558 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -18,6 +18,7 @@ package node import ( "compress/gzip" + "context" "fmt" "github.com/ethereum/go-ethereum/rpc" "io" @@ -31,7 +32,7 @@ import ( "github.com/rs/cors" ) -type HttpServer struct { +type HTTPServer struct { handler http.Handler Srv *rpc.Server Server *http.Server @@ -54,19 +55,42 @@ type HttpServer struct { WSAllowed bool } -func (h *HttpServer) Handler() http.Handler { +// TODO document +func (h *HTTPServer) Start() { + go h.Server.Serve(h.Listener) +} + +// TODO document +func (h *HTTPServer) Stop() { + if h.Server != nil { + //url := fmt.Sprintf("http://%v/", h.ListenerAddr) + // Don't bother imposing a timeout here. + h.Server.Shutdown(context.Background()) + //n.log.Info("HTTP Endpoint closed", "url", url) // TODO log wherever this is called instead + } + if h.Srv != nil { + h.Srv.Stop() + h.Srv = nil + } +} + +// Handler returns the handler of the HTTPServer +func (h *HTTPServer) Handler() http.Handler { return h.handler } -func (h *HttpServer) SetHandler(handler http.Handler) { +// TODO document +func (h *HTTPServer) SetHandler(handler http.Handler) { h.handler = handler } -func (h *HttpServer) Endpoint() string { +// TODO is this really necessary? +func (h *HTTPServer) Endpoint() string { return fmt.Sprintf("%s:%d", h.host, h.port) } -func (h *HttpServer) SetEndpoint(endpoint string) { +// TODO is this necessary? +func (h *HTTPServer) SetEndpoint(endpoint string) { h.endpoint = endpoint } diff --git a/node/service.go b/node/service.go index 7a482161e1d9..00ff552d76ba 100644 --- a/node/service.go +++ b/node/service.go @@ -93,6 +93,7 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.Config.ExtRPCEnabled() } +// TODO document type Backend interface { // Protocols retrieves the P2P protocols the service wishes to start. Protocols() []p2p.Protocol @@ -125,7 +126,7 @@ type Service interface { // TODO document type AuxiliaryService interface { // TODO document - Server() *HttpServer + Server() *HTTPServer // AuxiliaryService also implements Lifecycle Lifecycle From 4985505f02f06a50203255e8aac419d9dd4d03fc Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 May 2020 11:38:43 +0200 Subject: [PATCH 016/160] enable rpc/ws --- graphql/service.go | 8 +++++--- node/node.go | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index b88e7109e561..59f9aa21cbac 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -53,18 +53,19 @@ func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeout // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. func (s *Service) Start() error { - var err error + // create handler stack and wrap the graphql handler handler, err := newHandler(s.backend) if err != nil { return err } + handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) + s.graphqlServer.SetHandler(handler) listener, err := net.Listen("tcp", s.graphqlServer.Endpoint()) if err != nil { return err } - // create handler stack and wrap the graphql handler - handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) + // make sure timeout values are meaningful node.CheckTimeouts(&s.graphqlServer.Timeouts) // create http server @@ -80,6 +81,7 @@ func (s *Service) Start() error { s.graphqlServer.Server = httpSrv s.graphqlServer.ListenerAddr = listener.Addr() s.graphqlServer.SetHandler(handler) + return nil } diff --git a/node/node.go b/node/node.go index c9d639945eea..9445495a823d 100644 --- a/node/node.go +++ b/node/node.go @@ -410,10 +410,12 @@ func (n *Node) startRPC() error { return err } n.http.Start() + n.http.RPCAllowed = true n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", n.http.ListenerAddr), "cors", strings.Join(n.http.CorsAllowedOrigins, ","), "vhosts", strings.Join(n.http.Vhosts, ",")) if n.http.Endpoint() == n.ws.Endpoint() { + n.http.WSAllowed = true n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", n.http.ListenerAddr)) } } @@ -426,6 +428,7 @@ func (n *Node) startRPC() error { return err } n.ws.Start() + n.ws.WSAllowed = true n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", n.ws.ListenerAddr)) } // All API endpoints started successfully From 62ec8473d1bea36cf74bb7b549be6b71731793da Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 May 2020 14:07:28 +0200 Subject: [PATCH 017/160] changed RegisterHTTP and cleaned up some code, now gql can start on same port as http --- cmd/utils/flags.go | 40 +++++++++++++++--------------- graphql/service.go | 61 +++++++++++++++++++++++++++++----------------- node/node.go | 46 ++++++++++++++++++++++++++-------- node/rpcstack.go | 22 +++++++++++++++-- 4 files changed, 114 insertions(+), 55 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f915566f9dc4..9eac2beb7110 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,6 +20,7 @@ package utils import ( "crypto/ecdsa" "fmt" + "github.com/ethereum/go-ethereum/internal/ethapi" "io" "io/ioutil" "math/big" @@ -1741,30 +1742,27 @@ func RegisterEthStatsService(stack *node.Node, url string) { // RegisterGraphQLService is a utility function to construct a new service and register it against a node. func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - // Try to construct the GraphQL service backed by a full node - if ethBackend, ok := stack.Backend().(*eth.Ethereum); ok { - graphqlAuxService, err := graphql.New(ethBackend.APIBackend, endpoint, cors, vhosts, timeouts) - if err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } - if err := stack.RegisterAuxService(graphqlAuxService); err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } - stack.RegisterLifecycle(graphqlAuxService) + var backend ethapi.Backend + switch stack.Backend() { + case stack.Backend().(*eth.Ethereum): + backend = stack.Backend().(*eth.Ethereum).APIBackend + case stack.Backend().(*les.LightEthereum): + backend = stack.Backend().(*les.LightEthereum).ApiBackend + default: + // Well, this should not have happened, bail out + Fatalf("no Ethereum service") } - // Try to construct the GraphQL service backed by a light node - if lesBackend, ok := stack.Backend().(*les.LightEthereum); ok { - graphqlAuxService, err := graphql.New(lesBackend.ApiBackend, endpoint, cors, vhosts, timeouts) - if err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } - if err := stack.RegisterAuxService(graphqlAuxService); err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } + + graphqlAuxService, err := graphql.New(backend, endpoint, cors, vhosts, timeouts) + if err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) + } + if err := stack.RegisterAuxService(graphqlAuxService); err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) + } + if graphqlAuxService.Server().Endpoint() != stack.Config().HTTPEndpoint() { stack.RegisterLifecycle(graphqlAuxService) } - // Well, this should not have happened, bail out - Fatalf("no Ethereum service") } func SetupMetrics(ctx *cli.Context) { diff --git a/graphql/service.go b/graphql/service.go index 59f9aa21cbac..5224fd0a61d7 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -44,28 +44,63 @@ func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeout Timeouts: timeouts, Vhosts: vhosts, CorsAllowedOrigins: cors, + GQLAllowed: true, }, } service.graphqlServer.SetEndpoint(endpoint) + // create handler + handler, err := service.CreateHandler() + if err != nil { + return nil, err + } + service.graphqlServer.SetHandler(handler) + return service, nil } +func (s *Service) CreateHandler() (http.Handler, error) { + // create handler stack and wrap the graphql handler + handler, err := newHandler(s.backend) + if err != nil { + return nil, err + } + + return handler, nil +} + +// newHandler returns a new `http.Handler` that will answer GraphQL queries. +// It additionally exports an interactive query browser on the / endpoint. +func newHandler(backend ethapi.Backend) (http.Handler, error) { + q := Resolver{backend} + + s, err := graphql.ParseSchema(schema, &q) + if err != nil { + return nil, err + } + h := &relay.Handler{Schema: s} + + mux := http.NewServeMux() + mux.Handle("/", GraphiQL{}) + mux.Handle("/graphql", h) + mux.Handle("/graphql/", h) + return mux, nil +} + // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. func (s *Service) Start() error { - // create handler stack and wrap the graphql handler - handler, err := newHandler(s.backend) + handler, err := s.CreateHandler() if err != nil { return err } handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) s.graphqlServer.SetHandler(handler) + // start listening on given endpoint listener, err := net.Listen("tcp", s.graphqlServer.Endpoint()) if err != nil { return err } - // make sure timeout values are meaningful node.CheckTimeouts(&s.graphqlServer.Timeouts) // create http server @@ -76,7 +111,7 @@ func (s *Service) Start() error { IdleTimeout: s.graphqlServer.Timeouts.IdleTimeout, } go httpSrv.Serve(listener) - log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.graphqlServer.Endpoint)) + log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%v", listener.Addr())) // add information to graphql http server s.graphqlServer.Server = httpSrv s.graphqlServer.ListenerAddr = listener.Addr() @@ -85,24 +120,6 @@ func (s *Service) Start() error { return nil } -// newHandler returns a new `http.Handler` that will answer GraphQL queries. -// It additionally exports an interactive query browser on the / endpoint. -func newHandler(backend ethapi.Backend) (http.Handler, error) { - q := Resolver{backend} - - s, err := graphql.ParseSchema(schema, &q) - if err != nil { - return nil, err - } - h := &relay.Handler{Schema: s} - - mux := http.NewServeMux() - mux.Handle("/", GraphiQL{}) - mux.Handle("/graphql", h) - mux.Handle("/graphql/", h) - return mux, nil -} - // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. func (s *Service) Stop() error { diff --git a/node/node.go b/node/node.go index 9445495a823d..275c1d2794fe 100644 --- a/node/node.go +++ b/node/node.go @@ -129,6 +129,7 @@ func New(conf *Config) (*Node, error) { endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, port: conf.HTTPPort, + RPCAllowed: true, }, ws: &HTTPServer{ CorsAllowedOrigins: conf.WSOrigins, @@ -137,6 +138,7 @@ func New(conf *Config) (*Node, error) { endpoint: conf.WSEndpoint(), host: conf.WSHost, port: conf.WSPort, + WSAllowed: true, }, eventmux: new(event.TypeMux), log: conf.Logger, @@ -245,8 +247,24 @@ func (n *Node) RegisterRPC(apis []rpc.API) { n.rpcAPIs = apis } -// TODO what is the purpose of this function? Define it and document it. -func (n *Node) RegisterHTTP(h *HTTPServer, exposeAll bool) error { +// TODO document +func (n *Node) RegisterHTTP(dest *HTTPServer, toRegister *HTTPServer) { + // takes in default existing http server + // enables ____ on it + if toRegister.WSAllowed { + dest.handler = NewWebsocketUpgradeHandler(dest.handler, toRegister.handler) + dest.WSAllowed = true + log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", toRegister.endpoint)) + } + if toRegister.GQLAllowed { + dest.handler = NewGQLUpgradeHandler(dest.handler, toRegister.handler) + dest.GQLAllowed = true + log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%v", toRegister.endpoint)) + } +} + +// CreateHTTPServer creates an http.Server and adds it to the given HTTPServer // TODO improve? +func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // register apis and create handler stack err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) if err != nil { @@ -325,7 +343,6 @@ func (n *Node) Start() error { n.server.Stop() return err } - // Start all registered lifecycles var started []Lifecycle for _, lifecycle := range n.lifecycles { @@ -398,31 +415,40 @@ func (n *Node) startRPC() error { return err } // create and start http server if the endpoint exists + // TODO CLEAN THIS UP!!!!!!!!! if n.http.endpoint != "" { + var exposeAll bool + // wrap handler in websocket handler only if websocket port is the same as http rpc n.http.handler = NewHTTPHandlerStack(n.http.Srv, n.http.CorsAllowedOrigins, n.http.Vhosts) if n.http.endpoint == n.ws.endpoint { - n.http.handler = NewWebsocketUpgradeHandler(n.http.handler, n.http.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins)) + n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) + n.RegisterHTTP(n.http, n.ws) + exposeAll = n.config.WSExposeAll } - if err := n.RegisterHTTP(n.http, false); err != nil { + + for _, auxServices := range n.auxServices { + if auxServices.Server().endpoint == n.http.endpoint { + n.RegisterHTTP(n.http, auxServices.Server()) + } + } + + if err := n.CreateHTTPServer(n.http, exposeAll); err != nil { n.stopIPC() n.stopInProc() return err } + n.http.Start() n.http.RPCAllowed = true n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", n.http.ListenerAddr), "cors", strings.Join(n.http.CorsAllowedOrigins, ","), "vhosts", strings.Join(n.http.Vhosts, ",")) - if n.http.Endpoint() == n.ws.Endpoint() { - n.http.WSAllowed = true - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", n.http.ListenerAddr)) - } } // create and start ws server if the endpoint exists if n.ws.endpoint != "" && n.http.endpoint != n.ws.endpoint { n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) - if err := n.RegisterHTTP(n.ws, n.config.WSExposeAll); err != nil { + if err := n.CreateHTTPServer(n.ws, n.config.WSExposeAll); err != nil { n.stopIPC() n.stopInProc() return err diff --git a/node/rpcstack.go b/node/rpcstack.go index 8696c2808558..ec10d6335703 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -19,7 +19,6 @@ package node import ( "compress/gzip" "context" - "fmt" "github.com/ethereum/go-ethereum/rpc" "io" "io/ioutil" @@ -53,6 +52,7 @@ type HTTPServer struct { RPCAllowed bool WSAllowed bool + GQLAllowed bool } // TODO document @@ -86,7 +86,7 @@ func (h *HTTPServer) SetHandler(handler http.Handler) { // TODO is this really necessary? func (h *HTTPServer) Endpoint() string { - return fmt.Sprintf("%s:%d", h.host, h.port) + return h.endpoint } // TODO is this necessary? @@ -222,3 +222,21 @@ func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && strings.ToLower(r.Header.Get("Connection")) == "upgrade" } + +// TODO document +func NewGQLUpgradeHandler(h http.Handler, gql http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isGQL(r) { + gql.ServeHTTP(w, r) + log.Debug("serving graphql request") + return + } + + h.ServeHTTP(w, r) + }) +} + +// TODO document +func isGQL(r *http.Request) bool { + return r.URL.Path == "/graphql" || r.URL.Path == "/graphql/" +} From 104ca9cb38c343df918d0cbce0b7b8b732364142 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 May 2020 14:33:05 +0200 Subject: [PATCH 018/160] some clean up --- graphql/service.go | 2 +- node/node.go | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index 5224fd0a61d7..523a6b4eff68 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -65,6 +65,7 @@ func (s *Service) CreateHandler() (http.Handler, error) { return nil, err } + handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) return handler, nil } @@ -93,7 +94,6 @@ func (s *Service) Start() error { if err != nil { return err } - handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) s.graphqlServer.SetHandler(handler) // start listening on given endpoint diff --git a/node/node.go b/node/node.go index 275c1d2794fe..397fce4a3193 100644 --- a/node/node.go +++ b/node/node.go @@ -249,8 +249,8 @@ func (n *Node) RegisterRPC(apis []rpc.API) { // TODO document func (n *Node) RegisterHTTP(dest *HTTPServer, toRegister *HTTPServer) { - // takes in default existing http server - // enables ____ on it + // takes in default existing http server + // enables ____ on it if toRegister.WSAllowed { dest.handler = NewWebsocketUpgradeHandler(dest.handler, toRegister.handler) dest.WSAllowed = true @@ -414,11 +414,9 @@ func (n *Node) startRPC() error { n.stopInProc() return err } - // create and start http server if the endpoint exists - // TODO CLEAN THIS UP!!!!!!!!! + // create and start http server if the endpoint exists // TODO CLEAN THIS UP!!!!!!!!! if n.http.endpoint != "" { var exposeAll bool - // wrap handler in websocket handler only if websocket port is the same as http rpc n.http.handler = NewHTTPHandlerStack(n.http.Srv, n.http.CorsAllowedOrigins, n.http.Vhosts) if n.http.endpoint == n.ws.endpoint { @@ -426,13 +424,13 @@ func (n *Node) startRPC() error { n.RegisterHTTP(n.http, n.ws) exposeAll = n.config.WSExposeAll } - + // if an auxiliary service has not been started, check if it has the same endpoint, then start for _, auxServices := range n.auxServices { if auxServices.Server().endpoint == n.http.endpoint { n.RegisterHTTP(n.http, auxServices.Server()) } } - + // create the HTTP Server if err := n.CreateHTTPServer(n.http, exposeAll); err != nil { n.stopIPC() n.stopInProc() From 6e53f68ceb0a59f17dd2101a06682e74f501617a Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 May 2020 14:49:32 +0200 Subject: [PATCH 019/160] changed console http startup --- node/api.go | 25 +++++++++++++++++++------ node/node.go | 4 +--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/node/api.go b/node/api.go index 01a206a6438f..b32984c2b4ef 100644 --- a/node/api.go +++ b/node/api.go @@ -147,8 +147,8 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.http != nil { - return false, fmt.Errorf("HTTP RPC already running on %s", api.node.http.Endpoint) + if api.node.http.Server != nil { + return false, fmt.Errorf("HTTP RPC already running on %v", api.node.http.ListenerAddr) } if host == nil { @@ -161,6 +161,9 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis if port == nil { port = &api.node.config.HTTPPort } + api.node.http.host = *host + api.node.http.port = *port + api.node.http.endpoint = fmt.Sprintf("%s:%d", *host, *port) allowedOrigins := api.node.config.HTTPCors if cors != nil { @@ -169,6 +172,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) } } + api.node.http.CorsAllowedOrigins = allowedOrigins allowedVHosts := api.node.config.HTTPVirtualHosts if vhosts != nil { @@ -177,6 +181,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) } } + api.node.http.Vhosts = allowedVHosts modules := api.node.http.Whitelist if apis != nil { @@ -185,10 +190,18 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis modules = append(modules, strings.TrimSpace(m)) } } - - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { + api.node.http.Whitelist = modules + // create handler + api.node.http.handler = NewHTTPHandlerStack(api.node.http.Srv, api.node.http.CorsAllowedOrigins, api.node.http.Vhosts) + // create HTTP server + if err := api.node.CreateHTTPServer(api.node.http, false); err != nil { return false, err } + // start the HTTP server + api.node.http.Start() + api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", api.node.http.ListenerAddr), + "cors", strings.Join(api.node.http.CorsAllowedOrigins, ","), + "vhosts", strings.Join(api.node.http.Vhosts, ",")) return true, nil } @@ -209,8 +222,8 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.ws != nil { - return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.ws.Endpoint) + if api.node.ws.Server != nil { + return false, fmt.Errorf("WebSocket RPC already running on %v", api.node.ws.ListenerAddr) } if host == nil { diff --git a/node/node.go b/node/node.go index 397fce4a3193..fbac1a9fb0b2 100644 --- a/node/node.go +++ b/node/node.go @@ -426,7 +426,7 @@ func (n *Node) startRPC() error { } // if an auxiliary service has not been started, check if it has the same endpoint, then start for _, auxServices := range n.auxServices { - if auxServices.Server().endpoint == n.http.endpoint { + if auxServices.Server().endpoint == n.http.endpoint { // TODO maybe also check that auxServices.Server().Server == nil? or is it unnecessary? n.RegisterHTTP(n.http, auxServices.Server()) } } @@ -436,9 +436,7 @@ func (n *Node) startRPC() error { n.stopInProc() return err } - n.http.Start() - n.http.RPCAllowed = true n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", n.http.ListenerAddr), "cors", strings.Join(n.http.CorsAllowedOrigins, ","), "vhosts", strings.Join(n.http.Vhosts, ",")) From deb1a8ce73595e29c1c6bd159015cd990672794c Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 May 2020 17:47:47 +0200 Subject: [PATCH 020/160] allow ws to be enabled on same port as http from console --- node/api.go | 28 +++++++++++++++++--- node/node.go | 59 ++++++++++++++++++++++++++----------------- node/rpcstack.go | 4 +-- node/rpcstack_test.go | 5 ++-- 4 files changed, 65 insertions(+), 31 deletions(-) diff --git a/node/api.go b/node/api.go index b32984c2b4ef..437807f6dc53 100644 --- a/node/api.go +++ b/node/api.go @@ -224,8 +224,10 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if api.node.ws.Server != nil { return false, fmt.Errorf("WebSocket RPC already running on %v", api.node.ws.ListenerAddr) + } else if api.node.http.WSAllowed { + return false, fmt.Errorf("WebSocket RPC already running on %v", api.node.http.ListenerAddr) } - + // set host, port and endpoint if host == nil { h := DefaultWSHost if api.node.config.WSHost != "" { @@ -236,6 +238,15 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if port == nil { port = &api.node.config.WSPort } + api.node.ws.host = *host + api.node.ws.port = *port + api.node.ws.endpoint = fmt.Sprintf("%s:%d", *host, *port) + + if api.node.ws.endpoint == api.node.http.endpoint && api.node.http.Server != nil { + api.node.http.WSAllowed = true + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", api.node.http.ListenerAddr)) + return true, nil + } origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -244,6 +255,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str origins = append(origins, strings.TrimSpace(origin)) } } + api.node.ws.WsOrigins = origins modules := api.node.config.WSModules if apis != nil { @@ -252,10 +264,15 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str modules = append(modules, strings.TrimSpace(m)) } } + api.node.ws.Whitelist = modules - if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), modules, origins, api.node.config.WSExposeAll); err != nil { + api.node.ws.handler = api.node.ws.Srv.WebsocketHandler(api.node.ws.WsOrigins) + if err := api.node.CreateHTTPServer(api.node.ws, api.node.config.WSExposeAll); err != nil { return false, err } + + api.node.ws.Start() + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", api.node.ws.ListenerAddr)) return true, nil } @@ -264,9 +281,14 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.ws == nil { + if api.node.ws.Server == nil && !api.node.http.WSAllowed { return false, fmt.Errorf("WebSocket RPC not running") } + if api.node.http.WSAllowed { + api.node.http.WSAllowed = false + return true, nil + } + api.node.stopWS() return true, nil } diff --git a/node/node.go b/node/node.go index fbac1a9fb0b2..155593b53c43 100644 --- a/node/node.go +++ b/node/node.go @@ -129,7 +129,6 @@ func New(conf *Config) (*Node, error) { endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, port: conf.HTTPPort, - RPCAllowed: true, }, ws: &HTTPServer{ CorsAllowedOrigins: conf.WSOrigins, @@ -138,11 +137,17 @@ func New(conf *Config) (*Node, error) { endpoint: conf.WSEndpoint(), host: conf.WSHost, port: conf.WSPort, - WSAllowed: true, }, eventmux: new(event.TypeMux), log: conf.Logger, } + if conf.HTTPHost != "" { + node.http.RPCAllowed = true + } + if conf.WSHost != "" { + node.ws.WSAllowed = true + } + node.ServiceContext.EventMux = node.eventmux node.ServiceContext.AccountManager = node.accman return node, nil @@ -251,15 +256,15 @@ func (n *Node) RegisterRPC(apis []rpc.API) { func (n *Node) RegisterHTTP(dest *HTTPServer, toRegister *HTTPServer) { // takes in default existing http server // enables ____ on it - if toRegister.WSAllowed { - dest.handler = NewWebsocketUpgradeHandler(dest.handler, toRegister.handler) - dest.WSAllowed = true - log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", toRegister.endpoint)) - } if toRegister.GQLAllowed { dest.handler = NewGQLUpgradeHandler(dest.handler, toRegister.handler) dest.GQLAllowed = true log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%v", toRegister.endpoint)) + return + } + dest.handler = dest.NewWebsocketUpgradeHandler(dest.handler, toRegister.handler) + if toRegister.WSAllowed { + log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", toRegister.endpoint)) } } @@ -414,15 +419,30 @@ func (n *Node) startRPC() error { n.stopInProc() return err } + // create and start ws server if the endpoint exists + if n.ws.endpoint != "" && n.http.endpoint != n.ws.endpoint { + n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) + if err := n.CreateHTTPServer(n.ws, n.config.WSExposeAll); err != nil { + n.stopIPC() + n.stopInProc() + return err + } + n.ws.Start() + n.ws.WSAllowed = true + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", n.ws.ListenerAddr)) + } // create and start http server if the endpoint exists // TODO CLEAN THIS UP!!!!!!!!! if n.http.endpoint != "" { - var exposeAll bool // wrap handler in websocket handler only if websocket port is the same as http rpc n.http.handler = NewHTTPHandlerStack(n.http.Srv, n.http.CorsAllowedOrigins, n.http.Vhosts) - if n.http.endpoint == n.ws.endpoint { + // if websocket server is not already started, or is specified on the same endpoint as http, + // register websocket on the http server + if n.ws.Server == nil { + if n.http.endpoint == n.ws.endpoint { + n.http.WSAllowed = true + } n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) n.RegisterHTTP(n.http, n.ws) - exposeAll = n.config.WSExposeAll } // if an auxiliary service has not been started, check if it has the same endpoint, then start for _, auxServices := range n.auxServices { @@ -430,6 +450,11 @@ func (n *Node) startRPC() error { n.RegisterHTTP(n.http, auxServices.Server()) } } + // only set exposeAll if websocket is enabled + var exposeAll bool + if n.http.WSAllowed { + exposeAll = n.config.WSExposeAll + } // create the HTTP Server if err := n.CreateHTTPServer(n.http, exposeAll); err != nil { n.stopIPC() @@ -441,18 +466,6 @@ func (n *Node) startRPC() error { "cors", strings.Join(n.http.CorsAllowedOrigins, ","), "vhosts", strings.Join(n.http.Vhosts, ",")) } - // create and start ws server if the endpoint exists - if n.ws.endpoint != "" && n.http.endpoint != n.ws.endpoint { - n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) - if err := n.CreateHTTPServer(n.ws, n.config.WSExposeAll); err != nil { - n.stopIPC() - n.stopInProc() - return err - } - n.ws.Start() - n.ws.WSAllowed = true - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", n.ws.ListenerAddr)) - } // All API endpoints started successfully return nil } @@ -523,7 +536,7 @@ func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhost handler := NewHTTPHandlerStack(srv, cors, vhosts) // wrap handler in websocket handler only if websocket port is the same as http rpc if n.http.Endpoint() == n.ws.Endpoint() { - handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) + handler = n.http.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) } httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) if err != nil { diff --git a/node/rpcstack.go b/node/rpcstack.go index ec10d6335703..4d214d80e45d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -205,9 +205,9 @@ func newGzipHandler(next http.Handler) http.Handler { // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. -func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { +func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if isWebsocket(r) { + if hs.WSAllowed && isWebsocket(r) { ws.ServeHTTP(w, r) log.Debug("serving websocket request") return diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 9db03181c9a7..040e087dcbbc 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -10,9 +10,8 @@ import ( ) func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { - srv := rpc.NewServer() - - handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{})) + h := &HTTPServer{Srv: rpc.NewServer()} + handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) ts := httptest.NewServer(handler) defer ts.Close() From 06a8de885f8ee229c7734fa3b6009e4a454cef31 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 7 May 2020 20:32:05 +0200 Subject: [PATCH 021/160] added todos --- cmd/utils/flags.go | 4 ++-- ethstats/ethstats.go | 2 +- node/node.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9eac2beb7110..335878339387 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1711,10 +1711,10 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) } - if err := stack.RegisterBackend(fullNode); err != nil { + if err := stack.RegisterBackend(fullNode); err != nil { // TODO this stuff should be handled by eth package (cause eth package only knows that it's a backend) Fatalf("Failed to register the Ethereum service: %v", err) } - stack.RegisterLifecycle(fullNode) + stack.RegisterLifecycle(fullNode) // TODO this too (node should call this stuff so it register itself) } } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index c9f6343da56f..b1598803b4aa 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -83,7 +83,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, url string, backend node.Backend) (node.AuxiliaryService, error) { +func New(node *node.Node, url string, backend node.Backend) (node.AuxiliaryService, error) { // TODO, this thing receives the backend explicitly, does whatever, registers itself // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) diff --git a/node/node.go b/node/node.go index 155593b53c43..622d1b88fce4 100644 --- a/node/node.go +++ b/node/node.go @@ -55,7 +55,7 @@ type Node struct { lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - backend Backend // The registered Backend of the node + backend Backend // The registered Backend of the node // TODO unnecessary services map[reflect.Type]Service // Currently running services auxServices map[reflect.Type]AuxiliaryService // Currently running auxiliary services @@ -318,7 +318,7 @@ func (n *Node) Start() error { // Initialize the p2p server. This creates the node key and // discovery databases. - n.server = &p2p.Server{Config: n.config.P2P} + n.server = &p2p.Server{Config: n.config.P2P} // TODO add init step for p2p server n.server.Config.PrivateKey = n.config.NodeKey() n.server.Config.Name = n.config.NodeName() n.server.Config.Logger = n.log From 4dd45c91c440d92273e6419a47f46b2578cdb640 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 12 May 2020 12:33:30 +0200 Subject: [PATCH 022/160] first steps implementing new design --- cmd/faucet/faucet.go | 15 +--- cmd/geth/config.go | 7 +- cmd/utils/flags.go | 67 +++++------------- eth/backend.go | 32 ++++++--- ethstats/ethstats.go | 46 +++++------- graphql/service.go | 20 ++++-- les/client.go | 28 ++++---- node/node.go | 132 +++++++---------------------------- node/service.go | 44 ------------ whisper/whisperv6/whisper.go | 9 ++- 10 files changed, 127 insertions(+), 273 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index db2505b51cd3..988c5dc0d094 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,24 +241,15 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - lesBackend, err := les.New(stack.ServiceContext, &cfg) - // register the backend and lifecycle - if err := stack.RegisterBackend(lesBackend); err != nil { - return nil, err + if err := les.New(stack, &cfg); err != nil { + return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } - stack.RegisterLifecycle(lesBackend) // Assemble the ethstats monitoring and reporting service' if stats != "" { - var serv *les.LightEthereum - ethstatsAuxService, err := ethstats.New(stack, stats, serv) - if err != nil { - return nil, err - } - if err := stack.RegisterAuxService(ethstatsAuxService); err != nil { + if err := ethstats.New(stack, stats); err != nil { return nil, err } - stack.RegisterLifecycle(ethstatsAuxService) } // Boot up the client and ensure it connects to bootnodes if err := stack.Start(); err != nil { diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 01ca0a9cfbd9..766d3fb24a37 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -146,7 +146,7 @@ func enableWhisper(ctx *cli.Context) bool { func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - utils.RegisterEthService(stack, &cfg.Eth) + ethBackend, lesBackend := utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode shhEnabled := enableWhisper(ctx) @@ -165,12 +165,13 @@ func makeFullNode(ctx *cli.Context) *node.Node { } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { - utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) + utils.RegisterEthStatsService(stack, ethBackend, lesBackend, cfg.Ethstats.URL) } + return stack } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 335878339387..3ec66962ba5e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,7 +20,6 @@ package utils import ( "crypto/ecdsa" "fmt" - "github.com/ethereum/go-ethereum/internal/ethapi" "io" "io/ioutil" "math/big" @@ -1692,76 +1691,42 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) { +func RegisterEthService(stack *node.Node, cfg *eth.Config) (*eth.Ethereum, *les.LightEthereum) { if cfg.SyncMode == downloader.LightSync { - lesBackend, err := les.New(stack.ServiceContext, cfg) + backend, err := les.New(stack, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - stack.RegisterLifecycle(lesBackend) - if err := stack.RegisterBackend(lesBackend); err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) + Fatalf("Failed to register the Ethereum service: %w", err) } + return nil, backend } else { - fullNode, err := eth.New(stack.ServiceContext, cfg) + backend, err := eth.New(stack, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - if fullNode != nil && cfg.LightServ > 0 { - ls, _ := les.NewLesServer(fullNode, cfg) - fullNode.AddLesServer(ls) - } - if err := stack.RegisterBackend(fullNode); err != nil { // TODO this stuff should be handled by eth package (cause eth package only knows that it's a backend) - Fatalf("Failed to register the Ethereum service: %v", err) + Fatalf("Failed to register the Ethereum service: %w", err) } - stack.RegisterLifecycle(fullNode) // TODO this too (node should call this stuff so it register itself) + + return backend, nil } } // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - whisperService := whisper.New(cfg) - if err := stack.RegisterService(whisperService); err != nil { - Fatalf("Failed to register the Whisper service: %v", err) + if err := whisper.New(stack, cfg); err != nil { + Fatalf("Failed to register the Whisper service: %w", err) } - stack.RegisterLifecycle(whisperService) } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. -func RegisterEthStatsService(stack *node.Node, url string) { - ethstatsAuxService, err := ethstats.New(stack, url, stack.Backend()) - if err != nil { - Fatalf("Failed to register the Ethereum Stats service: %v", err) - } - if err := stack.RegisterAuxService(ethstatsAuxService); err != nil { - Fatalf("Failed to register the Ethereum Stats service: %v", err) +func RegisterEthStatsService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, url string) { + if err := ethstats.New(stack, ethBackend, lesBackend, url); err != nil { + Fatalf("Failed to register the Ethereum Stats service: %w", err) } - stack.RegisterLifecycle(ethstatsAuxService) } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - var backend ethapi.Backend - switch stack.Backend() { - case stack.Backend().(*eth.Ethereum): - backend = stack.Backend().(*eth.Ethereum).APIBackend - case stack.Backend().(*les.LightEthereum): - backend = stack.Backend().(*les.LightEthereum).ApiBackend - default: - // Well, this should not have happened, bail out - Fatalf("no Ethereum service") - } - - graphqlAuxService, err := graphql.New(backend, endpoint, cors, vhosts, timeouts) - if err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } - if err := stack.RegisterAuxService(graphqlAuxService); err != nil { - Fatalf("Failed to register the GraphQL service: %v", err) - } - if graphqlAuxService.Server().Endpoint() != stack.Config().HTTPEndpoint() { - stack.RegisterLifecycle(graphqlAuxService) +func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { + if err := graphql.New(stack, ethBackend, lesBackend, endpoint, cors, vhosts, timeouts); err != nil { + Fatalf("Failed to register the GraphQL service: %w", err) } } diff --git a/eth/backend.go b/eth/backend.go index d0cf9f779878..7dbd9a95ad6c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,6 +20,7 @@ package eth import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/les" "math/big" "runtime" "sync" @@ -114,7 +115,7 @@ func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { +func New(stack *node.Node, config *Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -138,7 +139,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) // Assemble the Ethereum object - chainDb, err := ctx.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") + chainDb, err := stack.ServiceContext.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") if err != nil { return nil, err } @@ -151,9 +152,9 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, chainDb: chainDb, - eventMux: ctx.EventMux, - accountManager: ctx.AccountManager, - engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + eventMux: stack.ServiceContext.EventMux, + accountManager: stack.ServiceContext.AccountManager, + engine: CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -207,7 +208,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.bloomIndexer.Start(eth.blockchain) if config.TxPool.Journal != "" { - config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) + config.TxPool.Journal = stack.ServiceContext.ResolvePath(config.TxPool.Journal) } eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) @@ -223,19 +224,32 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.ServiceContext.ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&ctx.Config.P2P) + eth.dialCandiates, err = eth.setupDiscovery(&stack.ServiceContext.Config.P2P) if err != nil { return nil, err } - return eth, nil + if config.LightServ > 0 { + ls, _ := les.NewLesServer(eth, config) + eth.AddLesServer(ls) + } + + // Register the backend on the node + stack.RegisterAPIs(eth.APIs()) + if err := stack.RegisterProtocols(eth.Protocols()); err != nil { + return nil, err + } + if err := stack.RegisterBackend(eth, nil); err != nil { + return nil, err + } + return eth, stack.RegisterLifecycle(eth) } func makeExtraData(extra []byte) []byte { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index b1598803b4aa..f1328f5ed9a4 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -83,41 +83,31 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, url string, backend node.Backend) (node.AuxiliaryService, error) { // TODO, this thing receives the backend explicitly, does whatever, registers itself +func New(node *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, url string) error { // TODO, this thing receives the backend explicitly, does whatever, registers itself // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) if len(parts) != 5 { - return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) + return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) } // fetch type of Backend - if ethBackend, ok := backend.(*eth.Ethereum); ok { - return &Service{ - server: node.Server(), - eth: ethBackend, - les: nil, // TODO is this okay? - engine: ethBackend.Engine(), - node: parts[1], - pass: parts[3], - host: parts[4], - pongCh: make(chan struct{}), - histCh: make(chan []uint64, 1), - }, nil - } else if lesBackend, ok := backend.(*les.LightEthereum); ok { - return &Service{ - server: node.Server(), - eth: nil, // TODO is this okay? - les: lesBackend, - engine: lesBackend.Engine(), - node: parts[1], - pass: parts[3], - host: parts[4], - pongCh: make(chan struct{}), - histCh: make(chan []uint64, 1), - }, nil - } - return nil, errors.New("no Ethereum service") // TODO is this okay to return? + if ethBackend == nil && lesBackend == nil { + return errors.New("no Ethereum service") // TODO is this okay to return? + } + ethstats := &Service{ + server: node.Server(), + eth: ethBackend, + les: lesBackend, + engine: ethBackend.Engine(), + node: parts[1], + pass: parts[3], + host: parts[4], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), + } + + return node.RegisterLifecycle(ethstats) } // Start implements node.Service, starting up the monitoring and reporting daemon. diff --git a/graphql/service.go b/graphql/service.go index 523a6b4eff68..68e41fde4689 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,7 +18,10 @@ package graphql import ( "context" + "errors" "fmt" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les" "net" "net/http" @@ -37,9 +40,8 @@ type Service struct { } // New constructs a new GraphQL service instance. -func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) { +func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { service := &Service{ - backend: backend, graphqlServer: &node.HTTPServer{ Timeouts: timeouts, Vhosts: vhosts, @@ -47,15 +49,25 @@ func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeout GQLAllowed: true, }, } + // add backend + if ethBackend != nil { + service.backend = ethBackend.APIBackend + } else if lesBackend != nil { + service.backend = lesBackend.ApiBackend + } else { + return errors.New("no Ethereum service") + } + service.graphqlServer.SetEndpoint(endpoint) // create handler handler, err := service.CreateHandler() if err != nil { - return nil, err + return err } service.graphqlServer.SetHandler(handler) - return service, nil + // TODO register http + return stack.RegisterLifecycle(service) } func (s *Service) CreateHandler() (http.Handler, error) { diff --git a/les/client.go b/les/client.go index ae81c79db430..3e587ad0adce 100644 --- a/les/client.go +++ b/les/client.go @@ -77,12 +77,12 @@ type LightEthereum struct { p2pServer *p2p.Server } -func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { - chainDb, err := ctx.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") +func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { + chainDb, err := stack.ServiceContext.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { return nil, err } - lespayDb, err := ctx.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + lespayDb, err := stack.ServiceContext.OpenDatabase("lespay", 0, 0, "eth/db/lespay") if err != nil { return nil, err } @@ -103,10 +103,10 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { closeCh: make(chan struct{}), }, peers: peers, - eventMux: ctx.EventMux, + eventMux: stack.ServiceContext.EventMux, reqDist: newRequestDistributor(peers, &mclock.System{}), - accountManager: ctx.AccountManager, - engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), + accountManager: stack.ServiceContext.AccountManager, + engine: eth.CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), @@ -163,19 +163,23 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil} + leth.ApiBackend = &LesApiBackend{stack.ServiceContext.ExtRPCEnabled(), leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) - leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth) - if leth.handler.ulc != nil { - log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction) - leth.blockchain.DisableCheckFreq() + + // Register the backend on the node + stack.RegisterAPIs(leth.APIs()) + if err := stack.RegisterProtocols(leth.Protocols()); err != nil { + return nil, err + } + if err := stack.RegisterBackend(nil, leth); err != nil { + return nil, err } - return leth, nil + return leth, stack.RegisterLifecycle(leth) } // vtSubscription implements serverPeerSubscriber diff --git a/node/node.go b/node/node.go index 622d1b88fce4..2ce3d547db16 100644 --- a/node/node.go +++ b/node/node.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les" "net" "net/http" "os" @@ -55,10 +57,6 @@ type Node struct { lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - backend Backend // The registered Backend of the node // TODO unnecessary - services map[reflect.Type]Service // Currently running services - auxServices map[reflect.Type]AuxiliaryService // Currently running auxiliary services - rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -66,7 +64,6 @@ type Node struct { http *HTTPServer // TODO ws *HTTPServer // TODO - stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -115,8 +112,6 @@ func New(conf *Config) (*Node, error) { ServiceContext: &ServiceContext{ Config: *conf, }, - services: make(map[reflect.Type]Service), - auxServices: make(map[reflect.Type]AuxiliaryService), ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, @@ -177,60 +172,14 @@ func (n *Node) Close() error { } // TODO document -func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - n.lifecycles = append(n.lifecycles, lifecycle) -} - -// TODO document -func (n *Node) RegisterBackend(backend Backend) error { - n.lock.Lock() - defer n.lock.Unlock() - // check if p2p node is already running - if n.running() { - return ErrNodeRunning - } - // check that there is not already a backend registered on the node - if n.backend != nil { - return errors.New("a backend has already been registered on the node") // TODO is this error okay? - } - n.backend = backend - return nil -} - -// Register injects a new service into the node's stack. The service created by -// the passed constructor must be unique in its type with regard to sibling ones. -func (n *Node) RegisterService(service Service) error { - n.lock.Lock() - defer n.lock.Unlock() - - if n.running() { - return ErrNodeRunning - } - - kind := reflect.TypeOf(service) - if _, exists := n.services[kind]; exists { - return &DuplicateServiceError{Kind: kind} - } - n.services[kind] = service - - return nil -} - -// TODO document -func (n *Node) RegisterAuxService(auxService AuxiliaryService) error { - n.lock.Lock() - defer n.lock.Unlock() - - if n.running() { - return ErrNodeRunning - } - // make sure auxiliary service is not duplicated - kind := reflect.TypeOf(auxService) - if _, exists := n.auxServices[kind]; exists { - return &DuplicateServiceError{Kind: kind} +func (n *Node) RegisterLifecycle(lifecycle Lifecycle) error { + for _, existing := range n.lifecycles { // TODO is checking for duplicates a good idea? + if existing == lifecycle { + return errors.New("Lifecycle already registered") + } } - n.auxServices[kind] = auxService + n.lifecycles = append(n.lifecycles, lifecycle) return nil } @@ -238,18 +187,17 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { if !n.running() { return ErrNodeStopped } - // add backend's protocols to the o2o server + // TODO check for duplicates? + + // add backend's protocols to the p2p server n.server.Protocols = append(n.server.Protocols, protocols...) return nil } -func (n *Node) RegisterRPC(apis []rpc.API) { - // Gather all the possible APIs to surface - apis = append(apis, n.backend.APIs()...) - for _, service := range n.services { - apis = append(apis, service.APIs()...) - } - n.rpcAPIs = apis +func (n *Node) RegisterAPIs(apis []rpc.API) { + // TODO check for duplicates? + + n.rpcAPIs = append(n.rpcAPIs, apis...) } // TODO document @@ -364,11 +312,8 @@ func (n *Node) Start() error { return err } // Finish initializing the service context - n.ServiceContext.backend = n.backend n.ServiceContext.AccountManager = n.accman n.ServiceContext.EventMux = n.eventmux - n.ServiceContext.services = n.services - n.ServiceContext.auxServices = n.auxServices // Finish initializing the startup n.stop = make(chan struct{}) @@ -714,8 +659,15 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { return n.inprocHandler, nil } -func (n *Node) Backend() Backend { - return n.backend +func (n *Node) Backend() (*eth.Ethereum, *les.LightEthereum) { + if n.ethBackend != nil && n.lesBackend == nil { + return n.ethBackend, nil + } + if n.ethBackend == nil && n.lesBackend != nil { + return nil, n.lesBackend + } + + return nil, nil } // Server retrieves the currently running P2P network layer. This method is meant @@ -728,42 +680,6 @@ func (n *Node) Server() *p2p.Server { return n.server } -// Service retrieves a currently running service registered of a specific type. -func (n *Node) Service(service interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if n.server == nil { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(service).Elem() - if running, ok := n.services[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - -// Service retrieves a currently running service registered of a specific type. -func (n *Node) AuxService(auxService interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if n.server == nil { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(auxService).Elem() - if running, ok := n.auxServices[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - // DataDir retrieves the current datadir used by the protocol stack. // Deprecated: No files should be stored in this directory, use InstanceDir instead. func (n *Node) DataDir() string { diff --git a/node/service.go b/node/service.go index 00ff552d76ba..80971d7c5301 100644 --- a/node/service.go +++ b/node/service.go @@ -24,17 +24,12 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" ) // ServiceContext is a collection of service independent options inherited from // the protocol stack, that is passed to all constructors to be optionally used; // as well as utility methods to operate on the service environment. type ServiceContext struct { - backend Backend // Copy of the already constructed Backend // TODO update this comment - services map[reflect.Type]Service // Index of the already constructed services - auxServices map[reflect.Type]AuxiliaryService // Index of the already constructed auxiliary services Config Config EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. @@ -93,45 +88,6 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.Config.ExtRPCEnabled() } -// TODO document -type Backend interface { - // Protocols retrieves the P2P protocols the service wishes to start. - Protocols() []p2p.Protocol - - // Backend can register a P2P Server - P2PServer(server *p2p.Server) error - - // Backend also implements the Service interface. - Service -} - -// Service is an individual protocol that can be registered into a node. -// -// Notes: -// -// • Service life-cycle management is delegated to the node. The service is allowed to -// initialize itself upon creation, but no goroutines should be spun up outside of the -// Start method. -// -// • Restart logic is not required as the node will create a fresh instance -// every time a service is started. -type Service interface { - // APIs retrieves the list of RPC descriptors the service provides - APIs() []rpc.API - - // Service also implements Lifecycle - Lifecycle -} - -// TODO document -type AuxiliaryService interface { - // TODO document - Server() *HTTPServer - - // AuxiliaryService also implements Lifecycle - Lifecycle -} - // TODO document type Lifecycle interface { // Start is called after all services have been constructed and the networking diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 053c22b3cb7d..6d2c9cf154fe 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "crypto/sha256" "fmt" + "github.com/ethereum/go-ethereum/node" "math" "runtime" "sync" @@ -93,7 +94,7 @@ type Whisper struct { } // New creates a Whisper client ready to communicate through the Ethereum P2P network. -func New(cfg *Config) *Whisper { +func New(stack *node.Node, cfg *Config) error { if cfg == nil { cfg = &DefaultConfig } @@ -132,7 +133,11 @@ func New(cfg *Config) *Whisper { }, } - return whisper + stack.RegisterAPIs(whisper.APIs()) + if err := stack.RegisterProtocols(whisper.Protocols()); err != nil { + return err + } + return stack.RegisterLifecycle(whisper) } // MinPow returns the PoW value required by this node. From c4f1d637ce64501e53de4065d9dd2506c2d78e3f Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 12 May 2020 18:38:55 +0200 Subject: [PATCH 023/160] functional geth --- cmd/faucet/faucet.go | 5 ++- cmd/geth/chaincmd.go | 14 +++--- cmd/geth/config.go | 5 ++- cmd/geth/consolecmd.go | 8 ++-- cmd/geth/main.go | 27 +++++------ cmd/utils/flags.go | 5 ++- eth/backend.go | 11 +---- les/client.go | 4 +- node/node.go | 100 ++++++++++++++--------------------------- node/service.go | 19 ++++---- p2p/server.go | 4 ++ 11 files changed, 81 insertions(+), 121 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 988c5dc0d094..7f66eae87fe7 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,13 +241,14 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - if err := les.New(stack, &cfg); err != nil { + lesBackend, err := les.New(stack, &cfg) + if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := ethstats.New(stack, stats); err != nil { + if err := ethstats.New(stack, nil, lesBackend, stats); err != nil { return nil, err } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 247c202bca17..07485306492e 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -240,7 +240,7 @@ func initGenesis(ctx *cli.Context) error { utils.Fatalf("invalid genesis file: %v", err) } // Open an initialise both full and light databases - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { @@ -277,7 +277,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -371,7 +371,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -406,7 +406,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -424,7 +424,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -446,7 +446,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -554,7 +554,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx) + stack, _, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 766d3fb24a37..df452b8b4fd8 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,6 +20,7 @@ import ( "bufio" "errors" "fmt" + "github.com/ethereum/go-ethereum/les" "os" "reflect" "unicode" @@ -144,7 +145,7 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context) *node.Node { +func makeFullNode(ctx *cli.Context) (*node.Node, *eth.Ethereum, *les.LightEthereum) { stack, cfg := makeConfigNode(ctx) ethBackend, lesBackend := utils.RegisterEthService(stack, &cfg.Eth) @@ -172,7 +173,7 @@ func makeFullNode(ctx *cli.Context) *node.Node { utils.RegisterEthStatsService(stack, ethBackend, lesBackend, cfg.Ethstats.URL) } - return stack + return stack, ethBackend, lesBackend } // dumpConfig is the dumpconfig command. diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 2a304636eb62..226a2537bac4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -78,8 +78,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - node := makeFullNode(ctx) - startNode(ctx, node) + node, ethBackend, lesBackend := makeFullNode(ctx) + startNode(ctx, node, ethBackend, lesBackend) defer node.Close() // Attach to the newly started node and start the JavaScript console @@ -190,8 +190,8 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - node := makeFullNode(ctx) - startNode(ctx, node) + node, ethBackend, lesBackend := makeFullNode(ctx) + startNode(ctx, node, ethBackend, lesBackend) defer node.Close() // Attach to the newly started node and start the JavaScript console diff --git a/cmd/geth/main.go b/cmd/geth/main.go index bb39c512cf49..5f71eb5324ca 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -351,9 +351,9 @@ func geth(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } prepare(ctx) - node := makeFullNode(ctx) + node, ethBackend, lesBackend := makeFullNode(ctx) defer node.Close() - startNode(ctx, node) + startNode(ctx, node, ethBackend, lesBackend) node.Wait() return nil @@ -362,7 +362,7 @@ func geth(ctx *cli.Context) error { // startNode boots up the system node and all registered protocols, after which // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. -func startNode(ctx *cli.Context, stack *node.Node) { +func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum) { debug.Memsize.Add("node", stack) // Start up the node itself @@ -385,20 +385,18 @@ func startNode(ctx *cli.Context, stack *node.Node) { // Set contract backend for ethereum service if local node // is serving LES requests. if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { - var ethService *eth.Ethereum - if err := stack.Service(ðService); err != nil { + if ethBackend == nil { utils.Fatalf("Failed to retrieve ethereum service: %v", err) } - ethService.SetContractBackend(ethClient) + ethBackend.SetContractBackend(ethClient) } // Set contract backend for les service if local node is // running as a light client. if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { - var lesService *les.LightEthereum - if err := stack.Service(&lesService); err != nil { + if lesBackend == nil { utils.Fatalf("Failed to retrieve light ethereum service: %v", err) } - lesService.SetContractBackend(ethClient) + lesBackend.SetContractBackend(ethClient) } go func() { @@ -465,11 +463,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { utils.Fatalf("Light clients do not support mining") } // Check if node's backend is eth and that it exists // TODO fix this section up -- not sure if it's doing what it's supposed to. - ethereum, ok := stack.Backend().(*eth.Ethereum) - if !ok { - if stack.Backend() == nil { - utils.Fatalf("Ethereum service not running: backend is nil") - } + if ethBackend == nil { utils.Fatalf("Ethereum service not running: backend is not an eth backend") } // Set the gas price to the limits from the CLI and start mining @@ -477,15 +471,14 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) } - ethereum.TxPool().SetGasPrice(gasprice) + ethBackend.TxPool().SetGasPrice(gasprice) threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") } - - if err := ethereum.StartMining(threads); err != nil { + if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 3ec66962ba5e..25faf1b91ec2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1703,7 +1703,10 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) (*eth.Ethereum, *les. if err != nil { Fatalf("Failed to register the Ethereum service: %w", err) } - + if cfg.LightServ > 0 { + ls, _ := les.NewLesServer(backend, cfg) + backend.AddLesServer(ls) + } return backend, nil } } diff --git a/eth/backend.go b/eth/backend.go index 7dbd9a95ad6c..361758cc435d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,7 +20,6 @@ package eth import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/les" "math/big" "runtime" "sync" @@ -161,6 +160,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + p2pServer: stack.Server(), } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -221,6 +221,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil { return nil, err } + eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) @@ -236,19 +237,11 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } - if config.LightServ > 0 { - ls, _ := les.NewLesServer(eth, config) - eth.AddLesServer(ls) - } - // Register the backend on the node stack.RegisterAPIs(eth.APIs()) if err := stack.RegisterProtocols(eth.Protocols()); err != nil { return nil, err } - if err := stack.RegisterBackend(eth, nil); err != nil { - return nil, err - } return eth, stack.RegisterLifecycle(eth) } diff --git a/les/client.go b/les/client.go index 3e587ad0adce..c8b7b39b5d00 100644 --- a/les/client.go +++ b/les/client.go @@ -110,6 +110,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + p2pServer: stack.Server(), } peers.subscribe((*vtSubscription)(leth.valueTracker)) @@ -176,9 +177,6 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { if err := stack.RegisterProtocols(leth.Protocols()); err != nil { return nil, err } - if err := stack.RegisterBackend(nil, leth); err != nil { - return nil, err - } return leth, stack.RegisterLifecycle(leth) } diff --git a/node/node.go b/node/node.go index 2ce3d547db16..2d56fceb7653 100644 --- a/node/node.go +++ b/node/node.go @@ -20,8 +20,6 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "net" "net/http" "os" @@ -117,21 +115,21 @@ func New(conf *Config) (*Node, error) { }, http: &HTTPServer{ CorsAllowedOrigins: conf.HTTPCors, - Vhosts: conf.HTTPVirtualHosts, - Whitelist: conf.HTTPModules, - Timeouts: conf.HTTPTimeouts, - Srv: rpc.NewServer(), - endpoint: conf.HTTPEndpoint(), - host: conf.HTTPHost, - port: conf.HTTPPort, + Vhosts: conf.HTTPVirtualHosts, + Whitelist: conf.HTTPModules, + Timeouts: conf.HTTPTimeouts, + Srv: rpc.NewServer(), + endpoint: conf.HTTPEndpoint(), + host: conf.HTTPHost, + port: conf.HTTPPort, }, ws: &HTTPServer{ CorsAllowedOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, }, eventmux: new(event.TypeMux), log: conf.Logger, @@ -143,6 +141,22 @@ func New(conf *Config) (*Node, error) { node.ws.WSAllowed = true } + // Initialize the p2p server. This creates the node key and + // discovery databases. + node.server = &p2p.Server{Config: conf.P2P} // TODO add init step for p2p server + node.server.Config.PrivateKey = node.config.NodeKey() + node.server.Config.Name = node.config.NodeName() + node.server.Config.Logger = node.log + if node.server.Config.StaticNodes == nil { + node.server.Config.StaticNodes = node.config.StaticNodes() + } + if node.server.Config.TrustedNodes == nil { + node.server.Config.TrustedNodes = node.config.TrustedNodes() + } + if node.server.Config.NodeDatabase == "" { + node.server.Config.NodeDatabase = node.config.NodeDB() + } + node.ServiceContext.EventMux = node.eventmux node.ServiceContext.AccountManager = node.accman return node, nil @@ -184,9 +198,6 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) error { } func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { - if !n.running() { - return ErrNodeStopped - } // TODO check for duplicates? // add backend's protocols to the p2p server @@ -248,7 +259,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // running returns true if the node's p2p server is already running func (n *Node) running() bool { - return n.server != nil + return n.server.Listening() } // Start creates a live P2P node and starts running it. @@ -264,38 +275,14 @@ func (n *Node) Start() error { return err } - // Initialize the p2p server. This creates the node key and - // discovery databases. - n.server = &p2p.Server{Config: n.config.P2P} // TODO add init step for p2p server - n.server.Config.PrivateKey = n.config.NodeKey() - n.server.Config.Name = n.config.NodeName() - n.server.Config.Logger = n.log - if n.server.Config.StaticNodes == nil { - n.server.Config.StaticNodes = n.config.StaticNodes() - } - if n.server.Config.TrustedNodes == nil { - n.server.Config.TrustedNodes = n.config.TrustedNodes() - } - if n.server.Config.NodeDatabase == "" { - n.server.Config.NodeDatabase = n.config.NodeDB() - } - // Start the p2p node if err := n.server.Start(); err != nil { return convertFileLockError(err) } n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) - // Register the running p2p server with the Backend - if err := n.backend.P2PServer(n.server); err != nil { - n.server.Stop() - return err - } - // Register the Backend's protocols with the p2p server - if err := n.RegisterProtocols(n.backend.Protocols()); err != nil { - n.server.Stop() - return err - } + // TODO running p2p server needs to somehow be added to the backend + // Start all registered lifecycles var started []Lifecycle for _, lifecycle := range n.lifecycles { @@ -355,7 +342,6 @@ func (n *Node) openDataDir() error { // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. func (n *Node) startRPC() error { - n.RegisterRPC(n.apis()) // Start the various API endpoints, terminating all in case of errors if err := n.startInProc(); err != nil { return err @@ -389,12 +375,6 @@ func (n *Node) startRPC() error { n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) n.RegisterHTTP(n.http, n.ws) } - // if an auxiliary service has not been started, check if it has the same endpoint, then start - for _, auxServices := range n.auxServices { - if auxServices.Server().endpoint == n.http.endpoint { // TODO maybe also check that auxServices.Server().Server == nil? or is it unnecessary? - n.RegisterHTTP(n.http, auxServices.Server()) - } - } // only set exposeAll if websocket is enabled var exposeAll bool if n.http.WSAllowed { @@ -576,13 +556,12 @@ func (n *Node) Stop() error { failure := &StopError{ Services: make(map[reflect.Type]error), } - for kind, service := range n.services { - if err := service.Stop(); err != nil { - failure.Services[kind] = err + for _, lifecycle := range n.lifecycles { + if err := lifecycle.Stop(); err != nil { + failure.Services[reflect.TypeOf(lifecycle)] = err } } n.server.Stop() - n.services = nil n.server = nil // Release instance directory lock. @@ -659,17 +638,6 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { return n.inprocHandler, nil } -func (n *Node) Backend() (*eth.Ethereum, *les.LightEthereum) { - if n.ethBackend != nil && n.lesBackend == nil { - return n.ethBackend, nil - } - if n.ethBackend == nil && n.lesBackend != nil { - return nil, n.lesBackend - } - - return nil, nil -} - // Server retrieves the currently running P2P network layer. This method is meant // only to inspect fields of the currently running server, life cycle management // should be left to this Node entity. diff --git a/node/service.go b/node/service.go index 80971d7c5301..c86372bb053e 100644 --- a/node/service.go +++ b/node/service.go @@ -18,7 +18,6 @@ package node import ( "path/filepath" - "reflect" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" @@ -72,15 +71,15 @@ func (ctx *ServiceContext) ResolvePath(path string) string { return ctx.Config.ResolvePath(path) } -// Service retrieves a currently running service registered of a specific type. -func (ctx *ServiceContext) Service(service interface{}) error { - element := reflect.ValueOf(service).Elem() - if running, ok := ctx.services[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} +//// Service retrieves a currently running service registered of a specific type. +//func (ctx *ServiceContext) Service(service interface{}) error { +// element := reflect.ValueOf(service).Elem() +// if running, ok := ctx.services[element.Type()]; ok { +// element.Set(reflect.ValueOf(running)) +// return nil +// } +// return ErrServiceUnknown +//} // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). diff --git a/p2p/server.go b/p2p/server.go index 1fe5f3978923..2e7a9c0b14be 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1119,3 +1119,7 @@ func (srv *Server) PeersInfo() []*PeerInfo { } return infos } + +func (srv *Server) Listening() bool { + return srv.listener != nil +} From e15007e3fda333b0c8ed4233583729059e31db8e Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 12 May 2020 19:26:58 +0200 Subject: [PATCH 024/160] registering all apis --- node/node.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/node.go b/node/node.go index 2d56fceb7653..7f7fe4e044d8 100644 --- a/node/node.go +++ b/node/node.go @@ -206,8 +206,6 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { } func (n *Node) RegisterAPIs(apis []rpc.API) { - // TODO check for duplicates? - n.rpcAPIs = append(n.rpcAPIs, apis...) } @@ -342,6 +340,8 @@ func (n *Node) openDataDir() error { // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. func (n *Node) startRPC() error { + n.RegisterAPIs(n.apis()) + // Start the various API endpoints, terminating all in case of errors if err := n.startInProc(); err != nil { return err From 3993923388d1e170469d3cdad6ff3e9f496abbfb Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 13 May 2020 14:21:20 +0200 Subject: [PATCH 025/160] added todo --- eth/backend.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eth/backend.go b/eth/backend.go index 361758cc435d..71d1ddee1576 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -237,6 +237,8 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } + // TODO don't forget to RegisterName on rpc.Server!!!!!!!! + // Register the backend on the node stack.RegisterAPIs(eth.APIs()) if err := stack.RegisterProtocols(eth.Protocols()); err != nil { From 0a3f9a212cf2ae01a8a0d5db006a6587b2a1366d Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 13 May 2020 14:55:58 +0200 Subject: [PATCH 026/160] removed todo --- eth/backend.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 71d1ddee1576..361758cc435d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -237,8 +237,6 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } - // TODO don't forget to RegisterName on rpc.Server!!!!!!!! - // Register the backend on the node stack.RegisterAPIs(eth.APIs()) if err := stack.RegisterProtocols(eth.Protocols()); err != nil { From 2888ca46dde0d0e7878d813f56d004c1417cb407 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 15 May 2020 16:16:24 +0200 Subject: [PATCH 027/160] Register http (#16) * register http new impl * start ws thorugh console on same port as http --- go.mod | 4 +- go.sum | 9 +- graphql/service.go | 91 +++++------- node/api.go | 121 +++++++++------ node/node.go | 361 +++++++++++++++++++++++++-------------------- node/rpcstack.go | 2 + 6 files changed, 327 insertions(+), 261 deletions(-) diff --git a/go.mod b/go.mod index 2299eb501753..db16cd3cb592 100644 --- a/go.mod +++ b/go.mod @@ -25,8 +25,8 @@ require ( github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 - github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c - github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 + github.com/golang/protobuf v1.3.3 + github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 diff --git a/go.sum b/go.sum index 4c46eeb5afb0..2bb6d1e16912 100644 --- a/go.sum +++ b/go.sum @@ -79,14 +79,15 @@ github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIi github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c h1:zqAKixg3cTcIasAMJV+EcfVbWwLpOZ7LeoWJvcuD/5Q= -github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= +github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= diff --git a/graphql/service.go b/graphql/service.go index 68e41fde4689..559bb2852643 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,16 +17,12 @@ package graphql import ( - "context" "errors" - "fmt" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les" - "net" "net/http" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" @@ -41,14 +37,7 @@ type Service struct { // New constructs a new GraphQL service instance. func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { - service := &Service{ - graphqlServer: &node.HTTPServer{ - Timeouts: timeouts, - Vhosts: vhosts, - CorsAllowedOrigins: cors, - GQLAllowed: true, - }, - } + service := new(Service) // add backend if ethBackend != nil { service.backend = ethBackend.APIBackend @@ -58,26 +47,52 @@ func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthere return errors.New("no Ethereum service") } - service.graphqlServer.SetEndpoint(endpoint) - // create handler - handler, err := service.CreateHandler() + // check if http server with given endpoint exists and enable graphQL on it + server := stack.ExistingHTTPServer(endpoint) + if server != nil { + server.GQLAllowed = true + server.Vhosts = append(server.Vhosts, vhosts...) + server.CorsAllowedOrigins = append(server.CorsAllowedOrigins, cors...) + server.Timeouts = timeouts + // create handler + handler, err := createHandler(service.backend, cors, vhosts) + if err != nil { + return err + } + server.GQLHandler = handler + // don't register lifecycle if registering on existing http server + return nil + } + // otherwise create a new server + handler, err := createHandler(service.backend, cors, vhosts) if err != nil { return err } - service.graphqlServer.SetHandler(handler) + // create the http server + gqlServer := &node.HTTPServer{ + Vhosts: vhosts, + CorsAllowedOrigins: cors, + Timeouts: timeouts, + GQLAllowed: true, + GQLHandler: handler, + Srv: rpc.NewServer(), + } + gqlServer.SetEndpoint(endpoint) + stack.RegisterHTTPServer(gqlServer) + + service.graphqlServer = gqlServer - // TODO register http - return stack.RegisterLifecycle(service) + return nil } -func (s *Service) CreateHandler() (http.Handler, error) { +func createHandler(backend ethapi.Backend, cors, vhosts []string) (http.Handler, error) { // create handler stack and wrap the graphql handler - handler, err := newHandler(s.backend) + handler, err := newHandler(backend) if err != nil { return nil, err } + handler = node.NewHTTPHandlerStack(handler, cors, vhosts) - handler = node.NewHTTPHandlerStack(handler, s.graphqlServer.CorsAllowedOrigins, s.graphqlServer.Vhosts) return handler, nil } @@ -102,32 +117,6 @@ func newHandler(backend ethapi.Backend) (http.Handler, error) { // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. func (s *Service) Start() error { - handler, err := s.CreateHandler() - if err != nil { - return err - } - s.graphqlServer.SetHandler(handler) - - // start listening on given endpoint - listener, err := net.Listen("tcp", s.graphqlServer.Endpoint()) - if err != nil { - return err - } - // make sure timeout values are meaningful - node.CheckTimeouts(&s.graphqlServer.Timeouts) - // create http server - httpSrv := &http.Server{ - Handler: handler, - ReadTimeout: s.graphqlServer.Timeouts.ReadTimeout, - WriteTimeout: s.graphqlServer.Timeouts.WriteTimeout, - IdleTimeout: s.graphqlServer.Timeouts.IdleTimeout, - } - go httpSrv.Serve(listener) - log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%v", listener.Addr())) - // add information to graphql http server - s.graphqlServer.Server = httpSrv - s.graphqlServer.ListenerAddr = listener.Addr() - s.graphqlServer.SetHandler(handler) return nil } @@ -135,10 +124,10 @@ func (s *Service) Start() error { // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. func (s *Service) Stop() error { - if s.graphqlServer.Server != nil { - s.graphqlServer.Server.Shutdown(context.Background()) - log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%v", s.graphqlServer.ListenerAddr)) - } + //if s.graphqlServer.Server != nil { + // s.graphqlServer.Server.Shutdown(context.Background()) + // log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%v", s.graphqlServer.ListenerAddr)) + //} return nil } diff --git a/node/api.go b/node/api.go index 437807f6dc53..0ae40cc208c6 100644 --- a/node/api.go +++ b/node/api.go @@ -146,9 +146,11 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - - if api.node.http.Server != nil { - return false, fmt.Errorf("HTTP RPC already running on %v", api.node.http.ListenerAddr) + // check if HTTP server already exists + for _, httpServer := range api.node.httpServers { + if httpServer.RPCAllowed { + return false, fmt.Errorf("HTTP RPC already running on %v", httpServer.ListenerAddr) + } } if host == nil { @@ -161,9 +163,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis if port == nil { port = &api.node.config.HTTPPort } - api.node.http.host = *host - api.node.http.port = *port - api.node.http.endpoint = fmt.Sprintf("%s:%d", *host, *port) + endpoint := fmt.Sprintf("%s:%d", *host, *port) allowedOrigins := api.node.config.HTTPCors if cors != nil { @@ -172,7 +172,6 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) } } - api.node.http.CorsAllowedOrigins = allowedOrigins allowedVHosts := api.node.config.HTTPVirtualHosts if vhosts != nil { @@ -181,27 +180,37 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) } } - api.node.http.Vhosts = allowedVHosts - modules := api.node.http.Whitelist + modules := api.node.config.HTTPModules if apis != nil { modules = nil for _, m := range strings.Split(*apis, ",") { modules = append(modules, strings.TrimSpace(m)) } } - api.node.http.Whitelist = modules + // configure http server + httpServer := &HTTPServer{ + host: *host, + port: *port, + endpoint: endpoint, + Srv: rpc.NewServer(), + CorsAllowedOrigins: allowedOrigins, + Vhosts: allowedVHosts, + Whitelist: modules, + } // create handler - api.node.http.handler = NewHTTPHandlerStack(api.node.http.Srv, api.node.http.CorsAllowedOrigins, api.node.http.Vhosts) + httpServer.handler = NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) // create HTTP server - if err := api.node.CreateHTTPServer(api.node.http, false); err != nil { + if err := api.node.CreateHTTPServer(httpServer, false); err != nil { return false, err } // start the HTTP server - api.node.http.Start() - api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", api.node.http.ListenerAddr), - "cors", strings.Join(api.node.http.CorsAllowedOrigins, ","), - "vhosts", strings.Join(api.node.http.Vhosts, ",")) + httpServer.Start() + api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.ListenerAddr), + "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), + "vhosts", strings.Join(httpServer.Vhosts, ",")) + + api.node.RegisterHTTPServer(httpServer) return true, nil } @@ -210,22 +219,25 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.http == nil { - return false, fmt.Errorf("HTTP RPC not running") + for _, httpServer := range api.node.httpServers { + if httpServer.RPCAllowed { + api.node.stopServer(httpServer) + return true, nil + } } - api.node.stopHTTP() - return true, nil + + return false, fmt.Errorf("HTTP RPC not running") } // StartWS starts the websocket RPC API server. func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - - if api.node.ws.Server != nil { - return false, fmt.Errorf("WebSocket RPC already running on %v", api.node.ws.ListenerAddr) - } else if api.node.http.WSAllowed { - return false, fmt.Errorf("WebSocket RPC already running on %v", api.node.http.ListenerAddr) + // check if an existing HTTP server already handles websocket + for _, httpServer := range api.node.httpServers { + if httpServer.WSAllowed { + return false, fmt.Errorf("WebSocket RPC already running on %v", httpServer.ListenerAddr) + } } // set host, port and endpoint if host == nil { @@ -238,15 +250,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if port == nil { port = &api.node.config.WSPort } - api.node.ws.host = *host - api.node.ws.port = *port - api.node.ws.endpoint = fmt.Sprintf("%s:%d", *host, *port) - - if api.node.ws.endpoint == api.node.http.endpoint && api.node.http.Server != nil { - api.node.http.WSAllowed = true - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", api.node.http.ListenerAddr)) - return true, nil - } + endpoint := fmt.Sprintf("%s:%d", *host, *port) origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -255,7 +259,14 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str origins = append(origins, strings.TrimSpace(origin)) } } - api.node.ws.WsOrigins = origins + // check if an HTTP server exists on the given endpoint, and if so, enable websocket on that HTTP server + existingServer := api.node.ExistingHTTPServer(endpoint) + if existingServer != nil { + existingServer.WSAllowed = true + existingServer.WsOrigins = origins + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.ListenerAddr)) + return true, nil + } modules := api.node.config.WSModules if apis != nil { @@ -264,15 +275,26 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str modules = append(modules, strings.TrimSpace(m)) } } - api.node.ws.Whitelist = modules - api.node.ws.handler = api.node.ws.Srv.WebsocketHandler(api.node.ws.WsOrigins) - if err := api.node.CreateHTTPServer(api.node.ws, api.node.config.WSExposeAll); err != nil { + wsServer := &HTTPServer{ + Srv: rpc.NewServer(), + endpoint: endpoint, + host: *host, + port: *port, + Whitelist: modules, + WsOrigins: origins, + WSAllowed: true, + } + + wsServer.handler = wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) + if err := api.node.CreateHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { return false, err } - api.node.ws.Start() - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", api.node.ws.ListenerAddr)) + wsServer.Start() + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.ListenerAddr)) + + api.node.RegisterHTTPServer(wsServer) return true, nil } @@ -281,16 +303,19 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - if api.node.ws.Server == nil && !api.node.http.WSAllowed { - return false, fmt.Errorf("WebSocket RPC not running") - } - if api.node.http.WSAllowed { - api.node.http.WSAllowed = false - return true, nil + for _, httpServer := range api.node.httpServers { + if httpServer.WSAllowed { + httpServer.WSAllowed = false + // if RPC is not enabled on the WS http server, shut it down + if !httpServer.RPCAllowed { + api.node.stopServer(httpServer) + } + + return true, nil + } } - api.node.stopWS() - return true, nil + return false, fmt.Errorf("WebSocket RPC not running") } // PublicAdminAPI is the collection of administrative API methods exposed over diff --git a/node/node.go b/node/node.go index 7f7fe4e044d8..11ebea4d32bd 100644 --- a/node/node.go +++ b/node/node.go @@ -58,9 +58,9 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + httpServers []*HTTPServer // Stores information about all http servers (if any), including http, ws, and graphql + ipc *HTTPServer // TODO - http *HTTPServer // TODO - ws *HTTPServer // TODO stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -110,36 +110,13 @@ func New(conf *Config) (*Node, error) { ServiceContext: &ServiceContext{ Config: *conf, }, + httpServers: make([]*HTTPServer, 0), ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, - http: &HTTPServer{ - CorsAllowedOrigins: conf.HTTPCors, - Vhosts: conf.HTTPVirtualHosts, - Whitelist: conf.HTTPModules, - Timeouts: conf.HTTPTimeouts, - Srv: rpc.NewServer(), - endpoint: conf.HTTPEndpoint(), - host: conf.HTTPHost, - port: conf.HTTPPort, - }, - ws: &HTTPServer{ - CorsAllowedOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - }, eventmux: new(event.TypeMux), log: conf.Logger, } - if conf.HTTPHost != "" { - node.http.RPCAllowed = true - } - if conf.WSHost != "" { - node.ws.WSAllowed = true - } // Initialize the p2p server. This creates the node key and // discovery databases. @@ -156,9 +133,44 @@ func New(conf *Config) (*Node, error) { if node.server.Config.NodeDatabase == "" { node.server.Config.NodeDatabase = node.config.NodeDB() } - + // Configure service context node.ServiceContext.EventMux = node.eventmux node.ServiceContext.AccountManager = node.accman + // Configure HTTP server(s) + if conf.HTTPHost != "" { + httpServ := &HTTPServer{ + CorsAllowedOrigins: conf.HTTPCors, + Vhosts: conf.HTTPVirtualHosts, + Whitelist: conf.HTTPModules, + Timeouts: conf.HTTPTimeouts, + Srv: rpc.NewServer(), + endpoint: conf.HTTPEndpoint(), + host: conf.HTTPHost, + port: conf.HTTPPort, + RPCAllowed: true, + } + // check if ws is enabled and if ws port is the same as http port + if conf.WSHost != "" && conf.WSPort == conf.HTTPPort { + httpServ.WSAllowed = true + httpServ.WsOrigins = conf.WSOrigins + httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) + node.httpServers = append(node.httpServers, httpServ) + return node, nil + } + node.httpServers = append(node.httpServers, httpServ) + } + if conf.WSHost != "" { + node.httpServers = append(node.httpServers, &HTTPServer{ + WsOrigins: conf.WSOrigins, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, + WSAllowed: true, + }) + } + return node, nil } @@ -209,6 +221,20 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { n.rpcAPIs = append(n.rpcAPIs, apis...) } +func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { + for _, httpServer := range n.httpServers { + if endpoint == httpServer.endpoint { + return httpServer + } + } + + return nil +} + +func (n *Node) RegisterHTTPServer(server *HTTPServer) { + n.httpServers = append(n.httpServers, server) +} + // TODO document func (n *Node) RegisterHTTP(dest *HTTPServer, toRegister *HTTPServer) { // takes in default existing http server @@ -247,6 +273,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { httpSrv.WriteTimeout = h.Timeouts.WriteTimeout httpSrv.IdleTimeout = h.Timeouts.IdleTimeout } + // complete the HTTPServer h.Listener = listener h.ListenerAddr = listener.Addr() @@ -350,46 +377,36 @@ func (n *Node) startRPC() error { n.stopInProc() return err } - // create and start ws server if the endpoint exists - if n.ws.endpoint != "" && n.http.endpoint != n.ws.endpoint { - n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) - if err := n.CreateHTTPServer(n.ws, n.config.WSExposeAll); err != nil { - n.stopIPC() - n.stopInProc() - return err + + for _, server := range n.httpServers { + // configure the handlers + if server.RPCAllowed { + server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) + // wrap ws handler just in case ws is enabled through the console after start-up + wsHandler := server.Srv.WebsocketHandler(server.WsOrigins) + server.handler = server.NewWebsocketUpgradeHandler(server.handler, wsHandler) + + n.log.Info("HTTP configured on endpoint ", "endpoint", server.endpoint) } - n.ws.Start() - n.ws.WSAllowed = true - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", n.ws.ListenerAddr)) - } - // create and start http server if the endpoint exists // TODO CLEAN THIS UP!!!!!!!!! - if n.http.endpoint != "" { - // wrap handler in websocket handler only if websocket port is the same as http rpc - n.http.handler = NewHTTPHandlerStack(n.http.Srv, n.http.CorsAllowedOrigins, n.http.Vhosts) - // if websocket server is not already started, or is specified on the same endpoint as http, - // register websocket on the http server - if n.ws.Server == nil { - if n.http.endpoint == n.ws.endpoint { - n.http.WSAllowed = true - } - n.ws.handler = n.ws.Srv.WebsocketHandler(n.ws.CorsAllowedOrigins) - n.RegisterHTTP(n.http, n.ws) + if server.WSAllowed && server.handler == nil { + server.handler = server.Srv.WebsocketHandler(server.WsOrigins) + n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) } - // only set exposeAll if websocket is enabled - var exposeAll bool - if n.http.WSAllowed { - exposeAll = n.config.WSExposeAll + if server.GQLAllowed { + if server.handler == nil { + server.handler = server.GQLHandler + } else { + server.handler = NewGQLUpgradeHandler(server.handler, server.GQLHandler) + } + n.log.Info("GraphQL configured on endpoint ", "endpoint", server.endpoint) } - // create the HTTP Server - if err := n.CreateHTTPServer(n.http, exposeAll); err != nil { - n.stopIPC() - n.stopInProc() + // create the HTTP server + if err := n.CreateHTTPServer(server, false); err != nil { return err } - n.http.Start() - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", n.http.ListenerAddr), - "cors", strings.Join(n.http.CorsAllowedOrigins, ","), - "vhosts", strings.Join(n.http.Vhosts, ",")) + // start HTTP server + server.Start() + n.log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", server.ListenerAddr)) } // All API endpoints started successfully return nil @@ -446,96 +463,115 @@ func (n *Node) stopIPC() { } } -// startHTTP initializes and starts the HTTP RPC endpoint. -func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { - // Short circuit if the HTTP endpoint isn't being exposed - if endpoint == "" { - return nil - } - // register apis and create handler stack - srv := rpc.NewServer() - err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, false) - if err != nil { - return err - } - handler := NewHTTPHandlerStack(srv, cors, vhosts) - // wrap handler in websocket handler only if websocket port is the same as http rpc - if n.http.Endpoint() == n.ws.Endpoint() { - handler = n.http.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) - } - httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) - if err != nil { - return err - } - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), - "cors", strings.Join(cors, ","), - "vhosts", strings.Join(vhosts, ",")) - if n.http.Endpoint() == n.ws.Endpoint() { - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) - } - // All listeners booted successfully - n.http.endpoint = endpoint - n.http.Server = httpServer - n.http.ListenerAddr = addr - n.http.Srv = srv - - return nil -} - -// stopHTTP terminates the HTTP RPC endpoint. -func (n *Node) stopHTTP() { - if n.http.Server != nil { - url := fmt.Sprintf("http://%v/", n.http.ListenerAddr) - // Don't bother imposing a timeout here. - n.http.Server.Shutdown(context.Background()) - n.log.Info("HTTP Endpoint closed", "url", url) - } - if n.http.Srv != nil { - n.http.Srv.Stop() - n.http.Srv = nil - } -} - -// startWS initializes and starts the websocket RPC endpoint. -func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, exposeAll bool) error { - // Short circuit if the WS endpoint isn't being exposed - if endpoint == "" { - return nil - } - - srv := rpc.NewServer() - handler := srv.WebsocketHandler(wsOrigins) - err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, exposeAll) - if err != nil { - return err - } - httpServer, addr, err := startWSEndpoint(endpoint, handler) - if err != nil { - return err - } - n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", addr)) - // All listeners booted successfully - n.ws.endpoint = endpoint - n.ws.ListenerAddr = addr - n.ws.Server = httpServer - n.ws.Srv = srv - - return nil -} +//// startHTTP initializes and starts the HTTP RPC endpoint. +//func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { +// // Short circuit if the HTTP endpoint isn't being exposed +// if endpoint == "" { +// return nil +// } +// // register apis and create handler stack +// srv := rpc.NewServer() +// err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, false) +// if err != nil { +// return err +// } +// handler := NewHTTPHandlerStack(srv, cors, vhosts) +// // wrap handler in websocket handler only if websocket port is the same as http rpc +// if n.http.Endpoint() == n.ws.Endpoint() { +// handler = n.http.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) +// } +// httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) +// if err != nil { +// return err +// } +// n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), +// "cors", strings.Join(cors, ","), +// "vhosts", strings.Join(vhosts, ",")) +// if n.http.Endpoint() == n.ws.Endpoint() { +// n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) +// } +// // All listeners booted successfully +// n.http.endpoint = endpoint +// n.http.Server = httpServer +// n.http.ListenerAddr = addr +// n.http.Srv = srv +// +// return nil +//} -// stopWS terminates the websocket RPC endpoint. -func (n *Node) stopWS() { - if n.ws.Server != nil { - url := fmt.Sprintf("http://%v/", n.ws.ListenerAddr) +// stopServers terminates the given HTTP servers' endpoints +func (n *Node) stopServer(server *HTTPServer) { + if server.Server != nil { + url := fmt.Sprintf("http://%v/", server.ListenerAddr) // Don't bother imposing a timeout here. - n.ws.Server.Shutdown(context.Background()) + server.Server.Shutdown(context.Background()) n.log.Info("HTTP Endpoint closed", "url", url) } - if n.ws.Srv != nil { - n.ws.Srv.Stop() - n.ws.Srv = nil - } -} + if server.Srv != nil { + server.Srv.Stop() + server.Srv = nil + } +} + + +//// stopHTTP terminates the HTTP RPC endpoint. +//func (n *Node) stopHTTP() { +// for _, server := range n.httpServers { +// if server.RPCAllowed { +// if server.Server != nil { +// url := fmt.Sprintf("http://%v/", server.ListenerAddr) +// // Don't bother imposing a timeout here. +// server.Server.Shutdown(context.Background()) +// n.log.Info("HTTP Endpoint closed", "url", url) +// } +// if server.Srv != nil { +// server.Srv.Stop() +// server.Srv = nil +// } +// } +// } +//} + +//// startWS initializes and starts the websocket RPC endpoint. +//func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, exposeAll bool) error { +// // Short circuit if the WS endpoint isn't being exposed +// if endpoint == "" { +// return nil +// } +// +// srv := rpc.NewServer() +// handler := srv.WebsocketHandler(wsOrigins) +// err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, exposeAll) +// if err != nil { +// return err +// } +// httpServer, addr, err := startWSEndpoint(endpoint, handler) +// if err != nil { +// return err +// } +// n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", addr)) +// // All listeners booted successfully +// n.ws.endpoint = endpoint +// n.ws.ListenerAddr = addr +// n.ws.Server = httpServer +// n.ws.Srv = srv +// +// return nil +//} + +//// stopWS terminates the websocket RPC endpoint. +//func (n *Node) stopWS() { +// if n.ws.Server != nil { +// url := fmt.Sprintf("http://%v/", n.ws.ListenerAddr) +// // Don't bother imposing a timeout here. +// n.ws.Server.Shutdown(context.Background()) +// n.log.Info("HTTP Endpoint closed", "url", url) +// } +// if n.ws.Srv != nil { +// n.ws.Srv.Stop() +// n.ws.Srv = nil +// } +//} // Stop terminates a running node along with all it's services. In the node was // not started, an error is returned. @@ -549,8 +585,9 @@ func (n *Node) Stop() error { } // Terminate the API, services and the p2p server. - n.stopWS() - n.stopHTTP() + for _, httpServer := range n.httpServers { + n.stopServer(httpServer) + } n.stopIPC() n.rpcAPIs = nil failure := &StopError{ @@ -674,10 +711,16 @@ func (n *Node) HTTPEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.http.Server != nil { - return n.http.ListenerAddr.String() + for _, httpServer := range n.httpServers { + if httpServer.RPCAllowed { + if httpServer.Listener != nil { + return httpServer.ListenerAddr.String() + } + return httpServer.endpoint + } } - return n.http.Endpoint() + + return "" // TODO should return an empty string if http server not configured? } // WSEndpoint retrieves the current WS endpoint @@ -686,10 +729,16 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - if n.ws.Server != nil { - return n.ws.ListenerAddr.String() + for _, httpServer := range n.httpServers { + if httpServer.WSAllowed { + if httpServer.Listener != nil { + return httpServer.ListenerAddr.String() + } + return httpServer.endpoint + } } - return n.ws.Endpoint() + + return "" // TODO should return an empty string if ws server not configured? } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/rpcstack.go b/node/rpcstack.go index 4d214d80e45d..3903762c88c2 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -53,6 +53,8 @@ type HTTPServer struct { RPCAllowed bool WSAllowed bool GQLAllowed bool + + GQLHandler http.Handler } // TODO document From 1f1bc0dcb33c5ab07f6b396d53f639f4803b70d7 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 10:11:09 +0200 Subject: [PATCH 028/160] remove unnecessary RegisterHTTP method --- node/node.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/node/node.go b/node/node.go index 11ebea4d32bd..da28d8f6a8b7 100644 --- a/node/node.go +++ b/node/node.go @@ -235,22 +235,6 @@ func (n *Node) RegisterHTTPServer(server *HTTPServer) { n.httpServers = append(n.httpServers, server) } -// TODO document -func (n *Node) RegisterHTTP(dest *HTTPServer, toRegister *HTTPServer) { - // takes in default existing http server - // enables ____ on it - if toRegister.GQLAllowed { - dest.handler = NewGQLUpgradeHandler(dest.handler, toRegister.handler) - dest.GQLAllowed = true - log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%v", toRegister.endpoint)) - return - } - dest.handler = dest.NewWebsocketUpgradeHandler(dest.handler, toRegister.handler) - if toRegister.WSAllowed { - log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", toRegister.endpoint)) - } -} - // CreateHTTPServer creates an http.Server and adds it to the given HTTPServer // TODO improve? func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // register apis and create handler stack From ae58c941965d5418ca231f3cb8d3fd4ab25e13f4 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 14:08:50 +0200 Subject: [PATCH 029/160] added docs, removed err return from register lifecycle --- eth/backend.go | 4 ++- ethstats/ethstats.go | 3 +- les/client.go | 4 ++- node/node.go | 53 +++++++++++++++++------------------- whisper/whisperv6/whisper.go | 4 ++- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 361758cc435d..d09db2374d42 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -242,7 +242,9 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if err := stack.RegisterProtocols(eth.Protocols()); err != nil { return nil, err } - return eth, stack.RegisterLifecycle(eth) + stack.RegisterLifecycle(eth) + + return eth, nil } func makeExtraData(extra []byte) []byte { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index f1328f5ed9a4..bada875b0bbb 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -106,8 +106,9 @@ func New(node *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereu pongCh: make(chan struct{}), histCh: make(chan []uint64, 1), } + node.RegisterLifecycle(ethstats) - return node.RegisterLifecycle(ethstats) + return nil } // Start implements node.Service, starting up the monitoring and reporting daemon. diff --git a/les/client.go b/les/client.go index c8b7b39b5d00..351cc952968f 100644 --- a/les/client.go +++ b/les/client.go @@ -177,7 +177,9 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { if err := stack.RegisterProtocols(leth.Protocols()); err != nil { return nil, err } - return leth, stack.RegisterLifecycle(leth) + stack.RegisterLifecycle(leth) + + return leth, nil } // vtSubscription implements serverPeerSubscriber diff --git a/node/node.go b/node/node.go index da28d8f6a8b7..755c4e161393 100644 --- a/node/node.go +++ b/node/node.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum/cmd/utils" "net" "net/http" "os" @@ -48,7 +49,6 @@ type Node struct { ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory - // TODO: removed p2pConfig b/c p2pServer already contains p2pConfig (is there a reason for it to be duplicated? server *p2p.Server // Currently running P2P networking layer ServiceContext *ServiceContext @@ -60,7 +60,7 @@ type Node struct { httpServers []*HTTPServer // Stores information about all http servers (if any), including http, ws, and graphql - ipc *HTTPServer // TODO + ipc *HTTPServer // Stores information about the ipc http server stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex @@ -147,7 +147,7 @@ func New(conf *Config) (*Node, error) { endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, port: conf.HTTPPort, - RPCAllowed: true, + RPCAllowed: true, } // check if ws is enabled and if ws port is the same as http port if conf.WSHost != "" && conf.WSPort == conf.HTTPPort { @@ -162,12 +162,12 @@ func New(conf *Config) (*Node, error) { if conf.WSHost != "" { node.httpServers = append(node.httpServers, &HTTPServer{ WsOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - WSAllowed: true, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, + WSAllowed: true, }) } @@ -197,44 +197,42 @@ func (n *Node) Close() error { } } -// TODO document -func (n *Node) RegisterLifecycle(lifecycle Lifecycle) error { - for _, existing := range n.lifecycles { // TODO is checking for duplicates a good idea? +// RegisterLifecycle registers the given Lifecycle on the node +func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { + for _, existing := range n.lifecycles { if existing == lifecycle { - return errors.New("Lifecycle already registered") + utils.Fatalf("Lifecycle cannot be registered more than once", lifecycle) } } - n.lifecycles = append(n.lifecycles, lifecycle) - return nil } +// RegisterProtocols adds backend's protocols to the node's p2p server func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { - // TODO check for duplicates? - - // add backend's protocols to the p2p server n.server.Protocols = append(n.server.Protocols, protocols...) return nil } +// RegisterAPIs registers the APIs a service provides on the node func (n *Node) RegisterAPIs(apis []rpc.API) { n.rpcAPIs = append(n.rpcAPIs, apis...) } +// RegisterHTTPServer registers the given HTTP server on the node +func (n *Node) RegisterHTTPServer(server *HTTPServer) { + n.httpServers = append(n.httpServers, server) +} + +// ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { for _, httpServer := range n.httpServers { if endpoint == httpServer.endpoint { return httpServer } } - return nil } -func (n *Node) RegisterHTTPServer(server *HTTPServer) { - n.httpServers = append(n.httpServers, server) -} - // CreateHTTPServer creates an http.Server and adds it to the given HTTPServer // TODO improve? func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // register apis and create handler stack @@ -302,7 +300,7 @@ func (n *Node) Start() error { } // Lastly, start the configured RPC interfaces - if err := n.startRPC(); err != nil { + if err := n.configureRPC(); err != nil { n.stopLifecycles(n.lifecycles) n.server.Stop() return err @@ -316,7 +314,7 @@ func (n *Node) Start() error { return nil } -// TODO document +// stopLifecycles stops the node's running Lifecycles func (n *Node) stopLifecycles(started []Lifecycle) { for _, lifecycle := range started { lifecycle.Stop() @@ -347,10 +345,10 @@ func (n *Node) openDataDir() error { return nil } -// startRPC is a helper method to start all the various RPC endpoints during node +// configureRPC is a helper method to configure all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. -func (n *Node) startRPC() error { +func (n *Node) configureRPC() error { n.RegisterAPIs(n.apis()) // Start the various API endpoints, terminating all in case of errors @@ -497,7 +495,6 @@ func (n *Node) stopServer(server *HTTPServer) { } } - //// stopHTTP terminates the HTTP RPC endpoint. //func (n *Node) stopHTTP() { // for _, server := range n.httpServers { diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 6d2c9cf154fe..8613893eb4a8 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -137,7 +137,9 @@ func New(stack *node.Node, cfg *Config) error { if err := stack.RegisterProtocols(whisper.Protocols()); err != nil { return err } - return stack.RegisterLifecycle(whisper) + stack.RegisterLifecycle(whisper) + + return nil } // MinPow returns the PoW value required by this node. From 7acc43f71504c9a4e4ad3c5f81c5e61785030d11 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 13:54:31 +0200 Subject: [PATCH 030/160] HTTPServer implements Lifecycle and can be started by the lifecycle loop in node (#17) --- node/api.go | 8 ++- node/node.go | 132 +++++------------------------------------------ node/rpcstack.go | 13 +++-- 3 files changed, 27 insertions(+), 126 deletions(-) diff --git a/node/api.go b/node/api.go index 0ae40cc208c6..fe31c4e7eea2 100644 --- a/node/api.go +++ b/node/api.go @@ -221,7 +221,9 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { for _, httpServer := range api.node.httpServers { if httpServer.RPCAllowed { - api.node.stopServer(httpServer) + if err := httpServer.Stop(); err != nil { + return false, err + } return true, nil } } @@ -308,7 +310,9 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down if !httpServer.RPCAllowed { - api.node.stopServer(httpServer) + if err := httpServer.Stop(); err != nil { + return false, err + } } return true, nil diff --git a/node/node.go b/node/node.go index 755c4e161393..57b5bb05271f 100644 --- a/node/node.go +++ b/node/node.go @@ -17,7 +17,6 @@ package node import ( - "context" "errors" "fmt" "github.com/ethereum/go-ethereum/cmd/utils" @@ -290,6 +289,13 @@ func (n *Node) Start() error { // TODO running p2p server needs to somehow be added to the backend + // Start the configured RPC interfaces + if err := n.startRPC(); err != nil { + n.stopLifecycles(n.lifecycles) + n.server.Stop() + return err + } + // Start all registered lifecycles var started []Lifecycle for _, lifecycle := range n.lifecycles { @@ -299,12 +305,6 @@ func (n *Node) Start() error { started = append(started, lifecycle) } - // Lastly, start the configured RPC interfaces - if err := n.configureRPC(); err != nil { - n.stopLifecycles(n.lifecycles) - n.server.Stop() - return err - } // Finish initializing the service context n.ServiceContext.AccountManager = n.accman n.ServiceContext.EventMux = n.eventmux @@ -369,6 +369,9 @@ func (n *Node) configureRPC() error { server.handler = server.NewWebsocketUpgradeHandler(server.handler, wsHandler) n.log.Info("HTTP configured on endpoint ", "endpoint", server.endpoint) + if server.WSAllowed { + n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) + } } if server.WSAllowed && server.handler == nil { server.handler = server.Srv.WebsocketHandler(server.WsOrigins) @@ -387,7 +390,9 @@ func (n *Node) configureRPC() error { return err } // start HTTP server - server.Start() + if err := n.RegisterLifecycle(server); err != nil { + return err + } n.log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", server.ListenerAddr)) } // All API endpoints started successfully @@ -445,114 +450,6 @@ func (n *Node) stopIPC() { } } -//// startHTTP initializes and starts the HTTP RPC endpoint. -//func (n *Node) startHTTP(endpoint string, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { -// // Short circuit if the HTTP endpoint isn't being exposed -// if endpoint == "" { -// return nil -// } -// // register apis and create handler stack -// srv := rpc.NewServer() -// err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, false) -// if err != nil { -// return err -// } -// handler := NewHTTPHandlerStack(srv, cors, vhosts) -// // wrap handler in websocket handler only if websocket port is the same as http rpc -// if n.http.Endpoint() == n.ws.Endpoint() { -// handler = n.http.NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) -// } -// httpServer, addr, err := StartHTTPEndpoint(endpoint, timeouts, handler) -// if err != nil { -// return err -// } -// n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", addr), -// "cors", strings.Join(cors, ","), -// "vhosts", strings.Join(vhosts, ",")) -// if n.http.Endpoint() == n.ws.Endpoint() { -// n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", addr)) -// } -// // All listeners booted successfully -// n.http.endpoint = endpoint -// n.http.Server = httpServer -// n.http.ListenerAddr = addr -// n.http.Srv = srv -// -// return nil -//} - -// stopServers terminates the given HTTP servers' endpoints -func (n *Node) stopServer(server *HTTPServer) { - if server.Server != nil { - url := fmt.Sprintf("http://%v/", server.ListenerAddr) - // Don't bother imposing a timeout here. - server.Server.Shutdown(context.Background()) - n.log.Info("HTTP Endpoint closed", "url", url) - } - if server.Srv != nil { - server.Srv.Stop() - server.Srv = nil - } -} - -//// stopHTTP terminates the HTTP RPC endpoint. -//func (n *Node) stopHTTP() { -// for _, server := range n.httpServers { -// if server.RPCAllowed { -// if server.Server != nil { -// url := fmt.Sprintf("http://%v/", server.ListenerAddr) -// // Don't bother imposing a timeout here. -// server.Server.Shutdown(context.Background()) -// n.log.Info("HTTP Endpoint closed", "url", url) -// } -// if server.Srv != nil { -// server.Srv.Stop() -// server.Srv = nil -// } -// } -// } -//} - -//// startWS initializes and starts the websocket RPC endpoint. -//func (n *Node) startWS(endpoint string, modules []string, wsOrigins []string, exposeAll bool) error { -// // Short circuit if the WS endpoint isn't being exposed -// if endpoint == "" { -// return nil -// } -// -// srv := rpc.NewServer() -// handler := srv.WebsocketHandler(wsOrigins) -// err := RegisterApisFromWhitelist(n.rpcAPIs, modules, srv, exposeAll) -// if err != nil { -// return err -// } -// httpServer, addr, err := startWSEndpoint(endpoint, handler) -// if err != nil { -// return err -// } -// n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", addr)) -// // All listeners booted successfully -// n.ws.endpoint = endpoint -// n.ws.ListenerAddr = addr -// n.ws.Server = httpServer -// n.ws.Srv = srv -// -// return nil -//} - -//// stopWS terminates the websocket RPC endpoint. -//func (n *Node) stopWS() { -// if n.ws.Server != nil { -// url := fmt.Sprintf("http://%v/", n.ws.ListenerAddr) -// // Don't bother imposing a timeout here. -// n.ws.Server.Shutdown(context.Background()) -// n.log.Info("HTTP Endpoint closed", "url", url) -// } -// if n.ws.Srv != nil { -// n.ws.Srv.Stop() -// n.ws.Srv = nil -// } -//} // Stop terminates a running node along with all it's services. In the node was // not started, an error is returned. @@ -566,9 +463,6 @@ func (n *Node) Stop() error { } // Terminate the API, services and the p2p server. - for _, httpServer := range n.httpServers { - n.stopServer(httpServer) - } n.stopIPC() n.rpcAPIs = nil failure := &StopError{ diff --git a/node/rpcstack.go b/node/rpcstack.go index 3903762c88c2..c9a1b47e522b 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -19,6 +19,7 @@ package node import ( "compress/gzip" "context" + "fmt" "github.com/ethereum/go-ethereum/rpc" "io" "io/ioutil" @@ -58,22 +59,24 @@ type HTTPServer struct { } // TODO document -func (h *HTTPServer) Start() { +func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) + return nil } -// TODO document -func (h *HTTPServer) Stop() { +func (h *HTTPServer) Stop() error { if h.Server != nil { - //url := fmt.Sprintf("http://%v/", h.ListenerAddr) + url := fmt.Sprintf("http://%v/", h.ListenerAddr) // Don't bother imposing a timeout here. h.Server.Shutdown(context.Background()) - //n.log.Info("HTTP Endpoint closed", "url", url) // TODO log wherever this is called instead + log.Info("HTTP Endpoint closed", "url", url) } if h.Srv != nil { h.Srv.Stop() h.Srv = nil } + + return nil } // Handler returns the handler of the HTTPServer From 35460fdade2bedf14dbb2d97537d1a57c75a8b94 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 14:22:17 +0200 Subject: [PATCH 031/160] removed unnecessary ListenerAddr --- graphql/service.go | 8 +------- node/api.go | 10 +++++----- node/node.go | 44 +++++++++++++++++++++++++++++++++++++++----- node/rpcstack.go | 4 ++-- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index 559bb2852643..b086315496aa 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -123,13 +123,7 @@ func (s *Service) Start() error { // Stop terminates all goroutines belonging to the service, blocking until they // are all terminated. -func (s *Service) Stop() error { - //if s.graphqlServer.Server != nil { - // s.graphqlServer.Server.Shutdown(context.Background()) - // log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%v", s.graphqlServer.ListenerAddr)) - //} - return nil -} +func (s *Service) Stop() error { return nil } func (s *Service) Server() *node.HTTPServer { return s.graphqlServer diff --git a/node/api.go b/node/api.go index fe31c4e7eea2..fec2fa6102c1 100644 --- a/node/api.go +++ b/node/api.go @@ -149,7 +149,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis // check if HTTP server already exists for _, httpServer := range api.node.httpServers { if httpServer.RPCAllowed { - return false, fmt.Errorf("HTTP RPC already running on %v", httpServer.ListenerAddr) + return false, fmt.Errorf("HTTP RPC already running on %v", httpServer.Listener.Addr()) } } @@ -206,7 +206,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } // start the HTTP server httpServer.Start() - api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.ListenerAddr), + api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.Listener.Addr()), "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), "vhosts", strings.Join(httpServer.Vhosts, ",")) @@ -238,7 +238,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // check if an existing HTTP server already handles websocket for _, httpServer := range api.node.httpServers { if httpServer.WSAllowed { - return false, fmt.Errorf("WebSocket RPC already running on %v", httpServer.ListenerAddr) + return false, fmt.Errorf("WebSocket RPC already running on %v", httpServer.Listener.Addr()) } } // set host, port and endpoint @@ -266,7 +266,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if existingServer != nil { existingServer.WSAllowed = true existingServer.WsOrigins = origins - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.ListenerAddr)) + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.Listener.Addr())) return true, nil } @@ -294,7 +294,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } wsServer.Start() - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.ListenerAddr)) + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.Listener.Addr())) api.node.RegisterHTTPServer(wsServer) return true, nil diff --git a/node/node.go b/node/node.go index 57b5bb05271f..3eaea73969d1 100644 --- a/node/node.go +++ b/node/node.go @@ -19,12 +19,13 @@ package node import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/cmd/utils" + "io" "net" "net/http" "os" "path/filepath" "reflect" + "runtime" "strings" "sync" @@ -200,7 +201,7 @@ func (n *Node) Close() error { func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { for _, existing := range n.lifecycles { if existing == lifecycle { - utils.Fatalf("Lifecycle cannot be registered more than once", lifecycle) + Fatalf("Lifecycle cannot be registered more than once", lifecycle) } } n.lifecycles = append(n.lifecycles, lifecycle) @@ -257,7 +258,6 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // complete the HTTPServer h.Listener = listener - h.ListenerAddr = listener.Addr() h.Server = httpSrv return nil @@ -450,6 +450,19 @@ func (n *Node) stopIPC() { } } +// stopServers terminates the given HTTP servers' endpoints +func (n *Node) stopServer(server *HTTPServer) { + if server.Server != nil { + url := fmt.Sprintf("http://%v/", server.Listener.Addr()) + // Don't bother imposing a timeout here. + server.Server.Shutdown(context.Background()) + n.log.Info("HTTP Endpoint closed", "url", url) + } + if server.Srv != nil { + server.Srv.Stop() + server.Srv = nil + } +} // Stop terminates a running node along with all it's services. In the node was // not started, an error is returned. @@ -589,7 +602,7 @@ func (n *Node) HTTPEndpoint() string { for _, httpServer := range n.httpServers { if httpServer.RPCAllowed { if httpServer.Listener != nil { - return httpServer.ListenerAddr.String() + return httpServer.Listener.Addr().String() } return httpServer.endpoint } @@ -607,7 +620,7 @@ func (n *Node) WSEndpoint() string { for _, httpServer := range n.httpServers { if httpServer.WSAllowed { if httpServer.Listener != nil { - return httpServer.ListenerAddr.String() + return httpServer.Listener.Addr().String() } return httpServer.endpoint } @@ -703,3 +716,24 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server } return nil } + +// TODO change this when you figure out how else to do a nice fatal err +// Fatalf formats a message to standard error and exits the program. +// The message is also printed to standard output if standard error +// is redirected to a different file. +func Fatalf(format string, args ...interface{}) { + w := io.MultiWriter(os.Stdout, os.Stderr) + if runtime.GOOS == "windows" { + // The SameFile check below doesn't work on Windows. + // stdout is unlikely to get redirected though, so just print there. + w = os.Stdout + } else { + outf, _ := os.Stdout.Stat() + errf, _ := os.Stderr.Stat() + if outf != nil && errf != nil && os.SameFile(outf, errf) { + w = os.Stderr + } + } + fmt.Fprintf(w, "Fatal: "+format+"\n", args...) + os.Exit(1) +} diff --git a/node/rpcstack.go b/node/rpcstack.go index c9a1b47e522b..3b439134a64d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -38,7 +38,6 @@ type HTTPServer struct { Server *http.Server Listener net.Listener - ListenerAddr net.Addr endpoint string host string @@ -61,12 +60,13 @@ type HTTPServer struct { // TODO document func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) + log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } func (h *HTTPServer) Stop() error { if h.Server != nil { - url := fmt.Sprintf("http://%v/", h.ListenerAddr) + url := fmt.Sprintf("http://%v/", h.Listener.Addr()) // Don't bother imposing a timeout here. h.Server.Shutdown(context.Background()) log.Info("HTTP Endpoint closed", "url", url) From ccadac33327b4075d7ba5a8a61fb7252aac46fb8 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 14:41:26 +0200 Subject: [PATCH 032/160] stopServer removes stopped server from list of httpservers on node and also removes from list of lifecycles --- node/api.go | 11 ++++------- node/node.go | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/node/api.go b/node/api.go index fec2fa6102c1..2effb4f2a48f 100644 --- a/node/api.go +++ b/node/api.go @@ -221,9 +221,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { for _, httpServer := range api.node.httpServers { if httpServer.RPCAllowed { - if err := httpServer.Stop(); err != nil { - return false, err - } + api.node.stopServer(httpServer) return true, nil } } @@ -309,10 +307,9 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { if httpServer.WSAllowed { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down - if !httpServer.RPCAllowed { - if err := httpServer.Stop(); err != nil { - return false, err - } + if !httpServer.RPCAllowed && !httpServer.GQLAllowed { // TODO is the gql check necessary? Can GQL ever be on a WS server that doesn't also support regular http? + api.node.stopServer(httpServer) + return true, nil } return true, nil diff --git a/node/node.go b/node/node.go index 3eaea73969d1..2b477c616f25 100644 --- a/node/node.go +++ b/node/node.go @@ -462,6 +462,31 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv.Stop() server.Srv = nil } + // remove stopped http server from node's http servers // TODO is this preferable? + remainingServers := make([]*HTTPServer, len(n.httpServers)-1) + index := 0 + for _, remaining := range n.httpServers { + if remaining.Server != nil && remaining.Srv != nil { + remainingServers[index] = remaining + index ++ + } + } + n.httpServers = remainingServers + // remove stopped http server from node's lifecycles + n.removeLifecycle(server) +} + +// removeLifecycle removes a stopped Lifecycle from the running node's Lifecycles +func (n *Node) removeLifecycle(lifecycle Lifecycle) { + remainingLifecycles := make([]Lifecycle, len(n.lifecycles)-1) + index := 0 + for _, remaining := range n.lifecycles { + if remaining != lifecycle { + remainingLifecycles[index] = remaining + index ++ + } + } + n.lifecycles = remainingLifecycles } // Stop terminates a running node along with all it's services. In the node was From e3f2bd1db3980b09d347def9e0b1a1f466af8bd4 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 16:14:46 +0200 Subject: [PATCH 033/160] starting to remove use of node.Service throughout codebase --- console/console_test.go | 10 +++++----- eth/backend.go | 4 ++-- ethclient/ethclient_test.go | 20 ++++++++++++-------- ethstats/ethstats.go | 4 ++-- les/api_test.go | 4 ++-- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/console/console_test.go b/console/console_test.go index a474f33300de..c6fd90be7f14 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -109,9 +109,12 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { if confOverride != nil { confOverride(ethConf) } - if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { + ethBackend, err := eth.New(stack, ethConf) + if err != nil { t.Fatalf("failed to register Ethereum protocol: %v", err) } + + // Start the node and assemble the JavaScript console around it if err = stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) @@ -135,13 +138,10 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { t.Fatalf("failed to create JavaScript console: %v", err) } // Create the final tester and return - var ethereum *eth.Ethereum - stack.Service(ðereum) - return &tester{ workspace: workspace, stack: stack, - ethereum: ethereum, + ethereum: ethBackend, console: console, input: prompter, output: printer, diff --git a/eth/backend.go b/eth/backend.go index d09db2374d42..82e5fb6bcd74 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -553,7 +553,7 @@ func (s *Ethereum) P2PServer(server *p2p.Server) error { return nil } -// Start implements node.Service, starting all internal goroutines needed by the +// Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { s.startEthEntryUpdate(s.p2pServer.LocalNode()) @@ -580,7 +580,7 @@ func (s *Ethereum) Start() error { return nil } -// Stop implements node.Service, terminating all internal goroutines used by the +// Stop implements node.Lifecycle, terminating all internal goroutines used by the // Ethereum protocol. func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 518a38788e57..c1d98f020279 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -188,15 +188,19 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Generate test chain. genesis, blocks := generateTestChain() - // Start Ethereum service. - var ethservice *eth.Ethereum + // Create node n, err := node.New(&node.Config{}) - n.Register(func(ctx *node.ServiceContext) (node.Service, error) { - config := ð.Config{Genesis: genesis} - config.Ethash.PowMode = ethash.ModeFake - ethservice, err = eth.New(ctx, config) - return ethservice, err - }) + if err != nil { + t.Fatalf("can't create new node: %v", err) + } + + // Create Ethereum Service + config := ð.Config{Genesis: genesis} + config.Ethash.PowMode = ethash.ModeFake + ethservice, err := eth.New(n, config) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } // Import the test chain. if err := n.Start(); err != nil { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index bada875b0bbb..594c844d1e59 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -111,7 +111,7 @@ func New(node *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereu return nil } -// Start implements node.Service, starting up the monitoring and reporting daemon. +// Start implements node.Lifecycle, starting up the monitoring and reporting daemon. func (s *Service) Start() error { go s.loop() @@ -119,7 +119,7 @@ func (s *Service) Start() error { return nil } -// Stop implements node.Service, terminating the monitoring and reporting daemon. +// Stop implements node.Lifecycle, terminating the monitoring and reporting daemon. func (s *Service) Stop() error { log.Info("Stats daemon stopped") return nil diff --git a/les/api_test.go b/les/api_test.go index 06a519b62bed..6a4a2565c5fd 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -492,11 +492,11 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] return test(ctx, net, servers, clients) } -func newLesClientService(ctx *adapters.ServiceContext) (node.Service, error) { +func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (*LightEthereum, error) { config := eth.DefaultConfig config.SyncMode = downloader.LightSync config.Ethash.PowMode = ethash.ModeFake - return New(ctx.NodeContext, &config) + return New(stack, &config) } func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) { From 1f5f52e073fef0850df76e41259b45788d8c08e9 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 19 May 2020 17:17:33 +0200 Subject: [PATCH 034/160] WIP, trying to remove node.Service from tests --- les/api_test.go | 4 +- les/client.go | 7 ++- miner/stress_clique.go | 40 ++++++++--------- miner/stress_ethash.go | 41 +++++++++--------- mobile/geth.go | 16 ++----- node/node_example_test.go | 30 ++++--------- node/node_test.go | 15 +++---- node/utils_test.go | 83 +++++++++--------------------------- whisper/whisperv6/whisper.go | 4 +- 9 files changed, 87 insertions(+), 153 deletions(-) diff --git a/les/api_test.go b/les/api_test.go index 6a4a2565c5fd..d6571fabe88f 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -499,12 +499,12 @@ func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (*Light return New(stack, &config) } -func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) { +func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (*eth.Ethereum, error) { config := eth.DefaultConfig config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity config.LightPeers = testMaxClients - ethereum, err := eth.New(ctx.NodeContext, &config) + ethereum, err := eth.New(stack, &config) if err != nil { return nil, err } diff --git a/les/client.go b/les/client.go index 351cc952968f..58d5606ae903 100644 --- a/les/client.go +++ b/les/client.go @@ -272,8 +272,7 @@ func (s *LightEthereum) LesVersion() int { return int(ClientP func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader } func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } -// Protocols implements node.Service, returning all the currently configured -// network protocols to start. +// Protocols returns all the currently configured network protocols to start. func (s *LightEthereum) Protocols() []p2p.Protocol { return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { if p := s.peers.peer(id.String()); p != nil { @@ -292,7 +291,7 @@ func (s *LightEthereum) P2PServer(server *p2p.Server) error { return nil } -// Start implements node.Service, starting all internal goroutines needed by the +// Start implements node.Lifecycle, starting all internal goroutines needed by the // light ethereum protocol implementation. func (s *LightEthereum) Start() error { log.Warn("Light client mode is an experimental feature") @@ -311,7 +310,7 @@ func (s *LightEthereum) Start() error { return nil } -// Stop implements node.Service, terminating all internal goroutines used by the +// Stop implements node.Lifecycle, terminating all internal goroutines used by the // Ethereum protocol. func (s *LightEthereum) Stop() error { close(s.closeCh) diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 2f8a28b68fa3..a3a63e38c45a 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -35,9 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -98,7 +96,7 @@ func main() { for _, node := range nodes { var ethereum *eth.Ethereum - if err := node.Service(ðereum); err != nil { + if err := node.Lifecycle(ðereum); err != nil { // TODO does this work? panic(err) } if err := ethereum.StartMining(1); err != nil { @@ -191,25 +189,27 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) { if err != nil { return nil, err } - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return eth.New(ctx, ð.Config{ - Genesis: genesis, - NetworkId: genesis.Config.ChainID.Uint64(), - SyncMode: downloader.FullSync, - DatabaseCache: 256, - DatabaseHandles: 256, - TxPool: core.DefaultTxPoolConfig, - GPO: eth.DefaultConfig.GPO, - Miner: miner.Config{ - GasFloor: genesis.GasLimit * 9 / 10, - GasCeil: genesis.GasLimit * 11 / 10, - GasPrice: big.NewInt(1), - Recommit: time.Second, - }, - }) - }); err != nil { + // Create and register the backend + ethBackend, err := eth.New(ctx, ð.Config{ + Genesis: genesis, + NetworkId: genesis.Config.ChainID.Uint64(), + SyncMode: downloader.FullSync, + DatabaseCache: 256, + DatabaseHandles: 256, + TxPool: core.DefaultTxPoolConfig, + GPO: eth.DefaultConfig.GPO, + Miner: miner.Config{ + GasFloor: genesis.GasLimit * 9 / 10, + GasCeil: genesis.GasLimit * 11 / 10, + GasPrice: big.NewInt(1), + Recommit: time.Second, + }, + }) + if err != nil { return nil, err } + stack.RegisterLifecycle(ethBackend) + // Start the node and return if successful return stack, stack.Start() } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 94d5b30aa5b8..ed999f90cec4 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -94,7 +94,7 @@ func main() { for _, node := range nodes { var ethereum *eth.Ethereum - if err := node.Service(ðereum); err != nil { + if err := node.Lifecycle(ðereum); err != nil { // TODO does this work? panic(err) } if err := ethereum.StartMining(1); err != nil { @@ -165,31 +165,32 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) { NoUSB: true, UseLightweightKDF: true, } - // Start the node and configure a full Ethereum node on it + // Create the node and configure a full Ethereum node on it stack, err := node.New(config) if err != nil { return nil, err } - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return eth.New(ctx, ð.Config{ - Genesis: genesis, - NetworkId: genesis.Config.ChainID.Uint64(), - SyncMode: downloader.FullSync, - DatabaseCache: 256, - DatabaseHandles: 256, - TxPool: core.DefaultTxPoolConfig, - GPO: eth.DefaultConfig.GPO, - Ethash: eth.DefaultConfig.Ethash, - Miner: miner.Config{ - GasFloor: genesis.GasLimit * 9 / 10, - GasCeil: genesis.GasLimit * 11 / 10, - GasPrice: big.NewInt(1), - Recommit: time.Second, - }, - }) - }); err != nil { + ethBackend, err := eth.New(ctx, ð.Config{ + Genesis: genesis, + NetworkId: genesis.Config.ChainID.Uint64(), + SyncMode: downloader.FullSync, + DatabaseCache: 256, + DatabaseHandles: 256, + TxPool: core.DefaultTxPoolConfig, + GPO: eth.DefaultConfig.GPO, + Ethash: eth.DefaultConfig.Ethash, + Miner: miner.Config{ + GasFloor: genesis.GasLimit * 9 / 10, + GasCeil: genesis.GasLimit * 11 / 10, + GasPrice: big.NewInt(1), + Recommit: time.Second, + }, + }) + if err != nil { return nil, err } + stack.RegisterLifecycle(ethBackend) + // Start the node and return if successful return stack, stack.Start() } diff --git a/mobile/geth.go b/mobile/geth.go index f20243426b5f..d93b9f1cd0cc 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -175,28 +175,20 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return les.New(ctx, ðConf) - }); err != nil { + lesBackend, err := les.New(rawStack, ðConf) + if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } // If netstats reporting is requested, do it if config.EthereumNetStats != "" { - if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - var lesServ *les.LightEthereum - ctx.Service(&lesServ) - - return ethstats.New(config.EthereumNetStats, nil, lesServ) - }); err != nil { + if err := ethstats.New(rawStack, nil, lesBackend, config.EthereumNetStats); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } } } // Register the Whisper protocol if requested if config.WhisperEnabled { - if err := rawStack.Register(func(*node.ServiceContext) (node.Service, error) { - return whisper.New(&whisper.DefaultConfig), nil - }); err != nil { + if err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { return nil, fmt.Errorf("whisper init: %v", err) } } diff --git a/node/node_example_test.go b/node/node_example_test.go index 57b18855f1ed..ccaa3d990f53 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -21,24 +21,18 @@ import ( "log" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" ) -// SampleService is a trivial network service that can be attached to a node for +// SampleLifecycle is a trivial network service that can be attached to a node for // life cycle management. // -// The following methods are needed to implement a node.Service: -// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on -// - APIs() []rpc.API - api methods the service wants to expose on rpc channels +// The following methods are needed to implement a node.Lifecycle: // - Start() error - method invoked when the node is ready to start the service // - Stop() error - method invoked when the node terminates the service -type SampleService struct{} +type SampleLifecycle struct{} -func (s *SampleService) Protocols() []p2p.Protocol { return nil } -func (s *SampleService) APIs() []rpc.API { return nil } -func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil } -func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil } +func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil } +func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil } func ExampleService() { // Create a network node to run protocols with the default values. @@ -48,16 +42,10 @@ func ExampleService() { } defer stack.Close() - // Create and register a simple network service. This is done through the definition - // of a node.ServiceConstructor that will instantiate a node.Service. The reason for - // the factory method approach is to support service restarts without relying on the - // individual implementations' support for such operations. - constructor := func(context *node.ServiceContext) (node.Service, error) { - return new(SampleService), nil - } - if err := stack.Register(constructor); err != nil { - log.Fatalf("Failed to register service: %v", err) - } + // Create and register a simple network Lifecycle. + service := new(SampleLifecycle) + stack.RegisterLifecycle(service) + // Boot up the entire protocol stack, do a restart and terminate if err := stack.Start(); err != nil { log.Fatalf("Failed to start the protocol stack: %v", err) diff --git a/node/node_test.go b/node/node_test.go index d62194a87681..b5dc12f54f62 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -121,12 +121,8 @@ func TestServiceRegistry(t *testing.T) { defer stack.Close() // Register a batch of unique services and ensure they start successfully - services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} - for i, constructor := range services { - if err := stack.Register(constructor); err != nil { - t.Fatalf("service #%d: registration failed: %v", i, err) - } - } + noop, err := NewNoop(stack) + if err := stack.Start(); err != nil { t.Fatalf("failed to start original service stack: %v", err) } @@ -134,9 +130,10 @@ func TestServiceRegistry(t *testing.T) { t.Fatalf("failed to stop original service stack: %v", err) } // Duplicate one of the services and retry starting the node - if err := stack.Register(NewNoopServiceB); err != nil { - t.Fatalf("duplicate registration failed: %v", err) - } + stack.RegisterLifecycle(noop) // TODO how to test for a fatal err ? + //err != nil { + // t.Fatalf("duplicate registration failed: %v", err) + //} if err := stack.Start(); err == nil { t.Fatalf("duplicate service started") } else { diff --git a/node/utils_test.go b/node/utils_test.go index 9801b1ed4565..0484ee035708 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -20,31 +20,29 @@ package node import ( - "reflect" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" ) // NoopService is a trivial implementation of the Service interface. -type NoopService struct{} +type NoopLifecycle struct{} -func (s *NoopService) Protocols() []p2p.Protocol { return nil } -func (s *NoopService) APIs() []rpc.API { return nil } -func (s *NoopService) Start(*p2p.Server) error { return nil } -func (s *NoopService) Stop() error { return nil } +func (s *NoopLifecycle) Start() error { return nil } +func (s *NoopLifecycle) Stop() error { return nil } -func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil } +func NewNoop(stack *Node) (*Noop, error) { + noop := new(Noop) + stack.RegisterLifecycle(noop) + return noop, nil +} // Set of services all wrapping the base NoopService resulting in the same method // signatures but different outer types. -type NoopServiceA struct{ NoopService } -type NoopServiceB struct{ NoopService } -type NoopServiceC struct{ NoopService } +type Noop struct{ NoopLifecycle } -func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil } -func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil } -func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil } +//func NewNoopServiceA(*ServiceContext) (Lifecycle, error) { return new(NoopServiceA), nil } +//func NewNoopServiceB(*ServiceContext) (Lifecycle, error) { return new(NoopServiceB), nil } +//func NewNoopServiceC(*ServiceContext) (Lifecycle, error) { return new(NoopServiceC), nil } // InstrumentedService is an implementation of Service for which all interface // methods can be instrumented both return value as well as event hook wise. @@ -54,12 +52,17 @@ type InstrumentedService struct { start error stop error + server *p2p.Server + protocolsHook func() startHook func(*p2p.Server) stopHook func() } -func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil } +func NewInstrumentedService(server *p2p.Server) (Lifecycle, error) { + is := &InstrumentedService{ server: server } + return is, nil +} func (s *InstrumentedService) Protocols() []p2p.Protocol { if s.protocolsHook != nil { @@ -72,9 +75,9 @@ func (s *InstrumentedService) APIs() []rpc.API { return s.apis } -func (s *InstrumentedService) Start(server *p2p.Server) error { +func (s *InstrumentedService) Start() error { if s.startHook != nil { - s.startHook(server) + s.startHook(s.server) } return s.start } @@ -85,49 +88,3 @@ func (s *InstrumentedService) Stop() error { } return s.stop } - -// InstrumentingWrapper is a method to specialize a service constructor returning -// a generic InstrumentedService into one returning a wrapping specific one. -type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor - -func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor { - return func(ctx *ServiceContext) (Service, error) { - obj, err := base(ctx) - if err != nil { - return nil, err - } - wrapper := reflect.New(kind) - wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem()) - - return wrapper.Interface().(Service), nil - } -} - -// Set of services all wrapping the base InstrumentedService resulting in the -// same method signatures but different outer types. -type InstrumentedServiceA struct{ InstrumentedService } -type InstrumentedServiceB struct{ InstrumentedService } -type InstrumentedServiceC struct{ InstrumentedService } - -func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor { - return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{})) -} - -func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor { - return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{})) -} - -func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor { - return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{})) -} - -// OneMethodAPI is a single-method API handler to be returned by test services. -type OneMethodAPI struct { - fun func() -} - -func (api *OneMethodAPI) TheOneMethod() { - if api.fun != nil { - api.fun() - } -} diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 8613893eb4a8..af848d2fdc79 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -641,7 +641,7 @@ func (whisper *Whisper) Send(envelope *Envelope) error { return err } -// Start implements node.Service, starting the background data propagation thread +// Start implements node.Lifecycle, starting the background data propagation thread // of the Whisper protocol. func (whisper *Whisper) Start() error { log.Info("started whisper v." + ProtocolVersionStr) @@ -657,7 +657,7 @@ func (whisper *Whisper) Start() error { return nil } -// Stop implements node.Service, stopping the background data propagation thread +// Stop implements node.Lifecycle, stopping the background data propagation thread // of the Whisper protocol. func (whisper *Whisper) Stop() error { close(whisper.quit) From 5866e01c2b7cfcca4cc53405e07a1a1497029b4c Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 May 2020 14:55:24 +0200 Subject: [PATCH 035/160] fixes node tests (#18) --- node/errors.go | 11 - node/node.go | 69 ++--- node/node_example_test.go | 7 +- node/node_test.go | 540 +++++++++++--------------------------- node/rpcstack_test.go | 7 +- node/service.go | 20 +- node/service_test.go | 45 ++-- node/utils_test.go | 46 +--- p2p/server.go | 4 +- 9 files changed, 247 insertions(+), 502 deletions(-) diff --git a/node/errors.go b/node/errors.go index 2e0dadc4d6b3..67547bf691f1 100644 --- a/node/errors.go +++ b/node/errors.go @@ -39,17 +39,6 @@ func convertFileLockError(err error) error { return err } -// DuplicateServiceError is returned during Node startup if a registered service -// constructor returns a service of the same type that was already started. -type DuplicateServiceError struct { - Kind reflect.Type -} - -// Error generates a textual representation of the duplicate service error. -func (e *DuplicateServiceError) Error() string { - return fmt.Sprintf("duplicate service: %v", e.Kind) -} - // StopError is returned if a Node fails to stop either any of its registered // services or itself. type StopError struct { diff --git a/node/node.go b/node/node.go index 2b477c616f25..6a94f1c0dee7 100644 --- a/node/node.go +++ b/node/node.go @@ -53,7 +53,7 @@ type Node struct { ServiceContext *ServiceContext - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -107,8 +107,10 @@ func New(conf *Config) (*Node, error) { accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, + lifecycles: make(map[reflect.Type]Lifecycle), ServiceContext: &ServiceContext{ Config: *conf, + Lifecycles: make(map[reflect.Type]Lifecycle), }, httpServers: make([]*HTTPServer, 0), ipc: &HTTPServer{ @@ -199,12 +201,12 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - for _, existing := range n.lifecycles { - if existing == lifecycle { - Fatalf("Lifecycle cannot be registered more than once", lifecycle) - } + kind := reflect.TypeOf(lifecycle) + if _, exists := n.lifecycles[kind]; exists { + Fatalf("Lifecycle cannot be registered more than once", kind) } - n.lifecycles = append(n.lifecycles, lifecycle) + + n.lifecycles[kind] = lifecycle } // RegisterProtocols adds backend's protocols to the node's p2p server @@ -265,7 +267,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // running returns true if the node's p2p server is already running func (n *Node) running() bool { - return n.server.Listening() + return n.server.Running() } // Start creates a live P2P node and starts running it. @@ -289,9 +291,8 @@ func (n *Node) Start() error { // TODO running p2p server needs to somehow be added to the backend - // Start the configured RPC interfaces - if err := n.startRPC(); err != nil { - n.stopLifecycles(n.lifecycles) + // Configure the RPC interfaces + if err := n.configureRPC(); err != nil { n.server.Stop() return err } @@ -301,8 +302,11 @@ func (n *Node) Start() error { for _, lifecycle := range n.lifecycles { if err := lifecycle.Start(); err != nil { n.stopLifecycles(started) + n.server.Stop() + return err } started = append(started, lifecycle) + n.ServiceContext.Lifecycles[reflect.TypeOf(lifecycle)] = lifecycle } // Finish initializing the service context @@ -478,15 +482,7 @@ func (n *Node) stopServer(server *HTTPServer) { // removeLifecycle removes a stopped Lifecycle from the running node's Lifecycles func (n *Node) removeLifecycle(lifecycle Lifecycle) { - remainingLifecycles := make([]Lifecycle, len(n.lifecycles)-1) - index := 0 - for _, remaining := range n.lifecycles { - if remaining != lifecycle { - remainingLifecycles[index] = remaining - index ++ - } - } - n.lifecycles = remainingLifecycles + delete(n.lifecycles, reflect.TypeOf(lifecycle)) } // Stop terminates a running node along with all it's services. In the node was @@ -496,7 +492,7 @@ func (n *Node) Stop() error { defer n.lock.Unlock() // Short circuit if the node's not running - if n.server == nil { + if n.server == nil || !n.running() { return ErrNodeStopped } @@ -506,10 +502,11 @@ func (n *Node) Stop() error { failure := &StopError{ Services: make(map[reflect.Type]error), } - for _, lifecycle := range n.lifecycles { + for kind, lifecycle := range n.lifecycles { if err := lifecycle.Stop(); err != nil { failure.Services[reflect.TypeOf(lifecycle)] = err } + delete(n.lifecycles, kind) } n.server.Stop() n.server = nil @@ -554,18 +551,6 @@ func (n *Node) Wait() { <-stop } -// Restart terminates a running node and boots up a new one in its place. If the -// node isn't running, an error is returned. -func (n *Node) Restart() error { - if err := n.Stop(); err != nil { - return err - } - if err := n.Start(); err != nil { - return err - } - return nil -} - // Attach creates an RPC client attached to an in-process API handler. func (n *Node) Attach() (*rpc.Client, error) { n.lock.RLock() @@ -695,6 +680,24 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } +// Lifecycle retrieves a currently running Lifecycle registered of a specific type. +func (n *Node) Lifecycle(lifecycle interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if !n.running() { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(lifecycle).Elem() + if running, ok := n.lifecycles[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ diff --git a/node/node_example_test.go b/node/node_example_test.go index ccaa3d990f53..9363bbc18379 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -34,7 +34,7 @@ type SampleLifecycle struct{} func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil } func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil } -func ExampleService() { +func ExampleLifecycle() { // Create a network node to run protocols with the default values. stack, err := node.New(&node.Config{}) if err != nil { @@ -50,15 +50,10 @@ func ExampleService() { if err := stack.Start(); err != nil { log.Fatalf("Failed to start the protocol stack: %v", err) } - if err := stack.Restart(); err != nil { - log.Fatalf("Failed to restart the protocol stack: %v", err) - } if err := stack.Stop(); err != nil { log.Fatalf("Failed to stop the protocol stack: %v", err) } // Output: // Service starting... // Service stopping... - // Service starting... - // Service stopping... } diff --git a/node/node_test.go b/node/node_test.go index b5dc12f54f62..e8764f1ab383 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -23,11 +23,9 @@ import ( "os" "reflect" "testing" - "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" ) @@ -43,7 +41,7 @@ func testNodeConfig() *Config { } } -// Tests that an empty protocol stack can be started, restarted and stopped. +// Tests that an empty protocol stack can be started and stopped. func TestNodeLifeCycle(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { @@ -64,12 +62,6 @@ func TestNodeLifeCycle(t *testing.T) { if err := stack.Start(); err != ErrNodeRunning { t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) } - // Ensure that a node can be restarted arbitrarily many times - for i := 0; i < 3; i++ { - if err := stack.Restart(); err != nil { - t.Fatalf("iter %d: failed to restart node: %v", i, err) - } - } // Ensure that a node can be stopped, but only once if err := stack.Stop(); err != nil { t.Fatalf("failed to stop node: %v", err) @@ -112,71 +104,63 @@ func TestNodeUsedDataDir(t *testing.T) { } } -// Tests whether services can be registered and duplicates caught. -func TestServiceRegistry(t *testing.T) { +// Tests whether a Lifecycle can be registered. +func TestLifecycleRegistry(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of unique services and ensure they start successfully - noop, err := NewNoop(stack) + noop := NewNoop() + stack.RegisterLifecycle(noop) - if err := stack.Start(); err != nil { - t.Fatalf("failed to start original service stack: %v", err) - } - if err := stack.Stop(); err != nil { - t.Fatalf("failed to stop original service stack: %v", err) - } - // Duplicate one of the services and retry starting the node - stack.RegisterLifecycle(noop) // TODO how to test for a fatal err ? - //err != nil { - // t.Fatalf("duplicate registration failed: %v", err) - //} - if err := stack.Start(); err == nil { - t.Fatalf("duplicate service started") - } else { - if _, ok := err.(*DuplicateServiceError); !ok { - t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{}) - } + if _, exists := stack.lifecycles[reflect.TypeOf(noop)]; !exists { + t.Fatalf("lifecycle was not properly registered on the node, %v", err) } } -// Tests that registered services get started and stopped correctly. -func TestServiceLifeCycle(t *testing.T) { +// Tests that registered Lifecycles get started and stopped correctly. +func TestLifecycleLifeCycle(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of life-cycle instrumented services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } started := make(map[string]bool) stopped := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, + } + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) } // Start the node and check that all services are running if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) } - for id := range services { + for id := range lifecycles { if !started[id] { t.Fatalf("service %s: freshly started service not running", id) } @@ -188,157 +172,62 @@ func TestServiceLifeCycle(t *testing.T) { if err := stack.Stop(); err != nil { t.Fatalf("failed to stop protocol stack: %v", err) } - for id := range services { + for id := range lifecycles { if !stopped[id] { t.Fatalf("service %s: freshly terminated service still running", id) } } } -// Tests that services are restarted cleanly as new instances. -func TestServiceRestarts(t *testing.T) { +// Tests that if a Lifecycle fails to start, all others started before it will be +// shut down. +func TestLifecycleStartupAbortion(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Define a service that does not support restarts - var ( - running bool - started int - ) - constructor := func(*ServiceContext) (Service, error) { - running = false - - return &InstrumentedService{ - startHook: func(*p2p.Server) { - if running { - panic("already running") - } - running = true - started++ - }, - }, nil - } - // Register the service and start the protocol stack - if err := stack.Register(constructor); err != nil { - t.Fatalf("failed to register the service: %v", err) - } - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() + started := make(map[string]bool) + stopped := make(map[string]bool) - if !running || started != 1 { - t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started) - } - // Restart the stack a few times and check successful service restarts - for i := 0; i < 3; i++ { - if err := stack.Restart(); err != nil { - t.Fatalf("iter %d: failed to restart stack: %v", i, err) - } - } - if !running || started != 4 { - t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started) + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, } -} - -// Tests that if a service fails to initialize itself, none of the other services -// will be allowed to even start. -func TestServiceConstructionAbortion(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) } - defer stack.Close() - // Define a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } - started := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } // Register a service that fails to construct itself failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return nil, failure - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) - } - // Start the protocol stack and ensure none of the services get started - for i := 0; i < 100; i++ { - if err := stack.Start(); err != failure { - t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) - } - for id := range services { - if started[id] { - t.Fatalf("service %s: started should not have", id) - } - delete(started, id) - } - } -} + failer := &InstrumentedService{ start: failure } + stack.RegisterLifecycle(failer) -// Tests that if a service fails to start, all others started before it will be -// shut down. -func TestServiceStartupAbortion(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } - started := make(map[string]bool) - stopped := make(map[string]bool) - - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Register a service that fails to start - failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - start: failure, - }, nil - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) - } // Start the protocol stack and ensure all started services stop for i := 0; i < 100; i++ { if err := stack.Start(); err != failure { t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) } - for id := range services { + for id := range lifecycles { if started[id] && !stopped[id] { t.Fatalf("service %s: started but not stopped", id) } @@ -348,85 +237,89 @@ func TestServiceStartupAbortion(t *testing.T) { } } -// Tests that even if a registered service fails to shut down cleanly, it does +// Tests that even if a registered Lifecycle fails to shut down cleanly, it does // not influence the rest of the shutdown invocations. -func TestServiceTerminationGuarantee(t *testing.T) { +func TestLifecycleTerminationGuarantee(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() - // Register a batch of good services - services := map[string]InstrumentingWrapper{ - "A": InstrumentedServiceMakerA, - "B": InstrumentedServiceMakerB, - "C": InstrumentedServiceMakerC, - } started := make(map[string]bool) stopped := make(map[string]bool) - for id, maker := range services { - id := id // Closure for the constructor - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - startHook: func(*p2p.Server) { started[id] = true }, - stopHook: func() { stopped[id] = true }, - }, nil - } - if err := stack.Register(maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } + // Create a batch of instrumented services + lifecycles := map[string]Lifecycle{ + "A": &InstrumentedServiceA{ + InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, + }, + }, + "B": &InstrumentedServiceB{ + InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, + }, + }, + "C": &InstrumentedServiceC{ + InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, + }, + }, } + // register lifecycles on node + for _, lifecycle := range lifecycles { + stack.RegisterLifecycle(lifecycle) + } + // Register a service that fails to shot down cleanly failure := errors.New("fail") - failer := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - stop: failure, - }, nil - } - if err := stack.Register(failer); err != nil { - t.Fatalf("failer registration failed: %v", err) + failer := &InstrumentedService{ stop: failure } + stack.RegisterLifecycle(failer) + + // Start the protocol stack, and ensure that a failing shut down terminates all // TODO, deleting loop because constructors no longer stored on node. + // Start the stack and make sure all is online + if err := stack.Start(); err != nil { + t.Fatalf("failed to start protocol stack: %v", err) } - // Start the protocol stack, and ensure that a failing shut down terminates all - for i := 0; i < 100; i++ { - // Start the stack and make sure all is online - if err := stack.Start(); err != nil { - t.Fatalf("iter %d: failed to start protocol stack: %v", i, err) + for id := range lifecycles { + if !started[id] { + t.Fatalf("service %s: service not running", id) } - for id := range services { - if !started[id] { - t.Fatalf("iter %d, service %s: service not running", i, id) - } - if stopped[id] { - t.Fatalf("iter %d, service %s: service already stopped", i, id) - } + if stopped[id] { + t.Fatalf("service %s: service already stopped", id) } - // Stop the stack, verify failure and check all terminations - err := stack.Stop() - if err, ok := err.(*StopError); !ok { - t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err) - } else { - failer := reflect.TypeOf(&InstrumentedService{}) - if err.Services[failer] != failure { - t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure) - } - if len(err.Services) != 1 { - t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1) - } + } + // Stop the stack, verify failure and check all terminations + err = stack.Stop() + if err, ok := err.(*StopError); !ok { + t.Fatalf("termination failure mismatch: have %v, want StopError", err) + } else { + failer := reflect.TypeOf(&InstrumentedService{}) + if err.Services[failer] != failure { + t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure) } - for id := range services { - if !stopped[id] { - t.Fatalf("iter %d, service %s: service not terminated", i, id) - } - delete(started, id) - delete(stopped, id) + if len(err.Services) != 1 { + t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1) + } + } + for id := range lifecycles { + if !stopped[id] { + t.Fatalf("service %s: service not terminated", id) } + delete(started, id) + delete(stopped, id) } + + stack.server = &p2p.Server{} + stack.server.PrivateKey = testNodeKey } -// TestServiceRetrieval tests that individual services can be retrieved. -func TestServiceRetrieval(t *testing.T) { +// TestLifecycleRetrieval tests that individual services can be retrieved. +func TestLifecycleRetrieval(t *testing.T) { // Create a simple stack and register two service types stack, err := New(testNodeConfig()) if err != nil { @@ -434,19 +327,22 @@ func TestServiceRetrieval(t *testing.T) { } defer stack.Close() - if err := stack.Register(NewNoopService); err != nil { - t.Fatalf("noop service registration failed: %v", err) - } - if err := stack.Register(NewInstrumentedService); err != nil { - t.Fatalf("instrumented service registration failed: %v", err) + noop := NewNoop() + stack.RegisterLifecycle(noop) + + is, err := NewInstrumentedService() + if err != nil { + t.Fatalf("instrumented service creation failed: %v", err) } + stack.RegisterLifecycle(is) + // Make sure none of the services can be retrieved until started - var noopServ *NoopService - if err := stack.Service(&noopServ); err != ErrNodeStopped { + var noopServ *Noop + if err := stack.Lifecycle(&noopServ); err != ErrNodeStopped { t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } var instServ *InstrumentedService - if err := stack.Service(&instServ); err != ErrNodeStopped { + if err := stack.Lifecycle(&instServ); err != ErrNodeStopped { t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) } // Start the stack and ensure everything is retrievable now @@ -455,152 +351,18 @@ func TestServiceRetrieval(t *testing.T) { } defer stack.Stop() - if err := stack.Service(&noopServ); err != nil { + if err := stack.Lifecycle(&noopServ); err != nil { t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) } - if err := stack.Service(&instServ); err != nil { + if err := stack.Lifecycle(&instServ); err != nil { t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) } } -// Tests that all protocols defined by individual services get launched. -func TestProtocolGather(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of services with some configured number of protocols - services := map[string]struct { - Count int - Maker InstrumentingWrapper - }{ - "zero": {0, InstrumentedServiceMakerA}, - "one": {1, InstrumentedServiceMakerB}, - "many": {10, InstrumentedServiceMakerC}, - } - for id, config := range services { - protocols := make([]p2p.Protocol, config.Count) - for i := 0; i < len(protocols); i++ { - protocols[i].Name = id - protocols[i].Version = uint(i) - } - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{ - protocols: protocols, - }, nil - } - if err := stack.Register(config.Maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Start the services and ensure all protocols start successfully - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() - - protocols := stack.Server().Protocols - if len(protocols) != 11 { - t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26) - } - for id, config := range services { - for ver := 0; ver < config.Count; ver++ { - launched := false - for i := 0; i < len(protocols); i++ { - if protocols[i].Name == id && protocols[i].Version == uint(ver) { - launched = true - break - } - } - if !launched { - t.Errorf("configured protocol not launched: %s v%d", id, ver) - } - } - } -} - -// Tests that all APIs defined by individual services get exposed. -func TestAPIGather(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - // Register a batch of services with some configured APIs - calls := make(chan string, 1) - makeAPI := func(result string) *OneMethodAPI { - return &OneMethodAPI{fun: func() { calls <- result }} - } - services := map[string]struct { - APIs []rpc.API - Maker InstrumentingWrapper - }{ - "Zero APIs": { - []rpc.API{}, InstrumentedServiceMakerA}, - "Single API": { - []rpc.API{ - {Namespace: "single", Version: "1", Service: makeAPI("single.v1"), Public: true}, - }, InstrumentedServiceMakerB}, - "Many APIs": { - []rpc.API{ - {Namespace: "multi", Version: "1", Service: makeAPI("multi.v1"), Public: true}, - {Namespace: "multi.v2", Version: "2", Service: makeAPI("multi.v2"), Public: true}, - {Namespace: "multi.v2.nested", Version: "2", Service: makeAPI("multi.v2.nested"), Public: true}, - }, InstrumentedServiceMakerC}, - } - - for id, config := range services { - config := config - constructor := func(*ServiceContext) (Service, error) { - return &InstrumentedService{apis: config.APIs}, nil - } - if err := stack.Register(config.Maker(constructor)); err != nil { - t.Fatalf("service %s: registration failed: %v", id, err) - } - } - // Start the services and ensure all API start successfully - if err := stack.Start(); err != nil { - t.Fatalf("failed to start protocol stack: %v", err) - } - defer stack.Stop() - - // Connect to the RPC server and verify the various registered endpoints - client, err := stack.Attach() - if err != nil { - t.Fatalf("failed to connect to the inproc API server: %v", err) - } - defer client.Close() - - tests := []struct { - Method string - Result string - }{ - {"single_theOneMethod", "single.v1"}, - {"multi_theOneMethod", "multi.v1"}, - {"multi.v2_theOneMethod", "multi.v2"}, - {"multi.v2.nested_theOneMethod", "multi.v2.nested"}, - } - for i, test := range tests { - if err := client.Call(nil, test.Method); err != nil { - t.Errorf("test %d: API request failed: %v", i, err) - } - select { - case result := <-calls: - if result != test.Result { - t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result) - } - case <-time.After(time.Second): - t.Fatalf("test %d: rpc execution timeout", i) - } - } -} - +// Tests whether websocket requests can be handled on the same port as a regular http server func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { node := startHTTP(t) - defer node.stopHTTP() + defer node.Stop() wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -615,9 +377,10 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) } +// Tests whether http requests can be handled successfully func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { node := startHTTP(t) - defer node.stopHTTP() + defer node.Stop() httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -630,13 +393,17 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { } func startHTTP(t *testing.T) *Node { - conf := &Config{HTTPPort: 7453, WSPort: 7453} + conf := &Config{ + HTTPHost: "127.0.0.1", + HTTPPort: 7453, + WSHost: "127.0.0.1", + WSPort: 7453, + } node, err := New(conf) if err != nil { t.Error("could not create a new node ", err) } - - err = node.startHTTP("127.0.0.1:7453", []rpc.API{}, []string{}, []string{}, []string{}, rpc.HTTPTimeouts{}, []string{}) + err = node.Start() if err != nil { t.Error("could not start http service on node ", err) } @@ -648,7 +415,8 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the given endpoint", err) + t.Fatal("could not issue a GET request to the given endpoint", err) + } return resp } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 040e087dcbbc..ff887738df33 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -10,7 +10,10 @@ import ( ) func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { - h := &HTTPServer{Srv: rpc.NewServer()} + h := &HTTPServer{ + Srv: rpc.NewServer(), + WSAllowed: true, + } handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) ts := httptest.NewServer(handler) defer ts.Close() @@ -27,7 +30,7 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { resp, err := client.Do(req) if err != nil { - t.Error("could not issue a GET request to the test http server", err) + t.Fatalf("could not issue a GET request to the test http server %v", err) } responses <- resp }(responses) diff --git a/node/service.go b/node/service.go index c86372bb053e..53ed61017f39 100644 --- a/node/service.go +++ b/node/service.go @@ -18,6 +18,7 @@ package node import ( "path/filepath" + "reflect" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" @@ -30,6 +31,7 @@ import ( // as well as utility methods to operate on the service environment. type ServiceContext struct { Config Config + Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? EventMux *event.TypeMux // Event multiplexer used for decoupled notifications AccountManager *accounts.Manager // Account manager created by the node. } @@ -71,15 +73,15 @@ func (ctx *ServiceContext) ResolvePath(path string) string { return ctx.Config.ResolvePath(path) } -//// Service retrieves a currently running service registered of a specific type. -//func (ctx *ServiceContext) Service(service interface{}) error { -// element := reflect.ValueOf(service).Elem() -// if running, ok := ctx.services[element.Type()]; ok { -// element.Set(reflect.ValueOf(running)) -// return nil -// } -// return ErrServiceUnknown -//} +// Lifecycle retrieves a currently running lifecycle registered of a specific type. +func (ctx *ServiceContext) Lifecycle(lifecycle interface{}) error { + element := reflect.ValueOf(lifecycle).Elem() + if running, ok := ctx.Lifecycles[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). diff --git a/node/service_test.go b/node/service_test.go index 5da8e9e434f5..183ca4915330 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -17,7 +17,6 @@ package node import ( - "fmt" "io/ioutil" "os" "path/filepath" @@ -61,38 +60,42 @@ func TestContextDatabases(t *testing.T) { } } -// Tests that already constructed services can be retrieves by later ones. -func TestContextServices(t *testing.T) { +// Tests that already constructed Lifecycles can be retrieved by later ones. +func TestContextLifecycles(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } defer stack.Close() // Define a verifier that ensures a NoopA is before it and NoopB after - verifier := func(ctx *ServiceContext) (Service, error) { - var objA *NoopServiceA - if ctx.Service(&objA) != nil { - return nil, fmt.Errorf("former service not found") - } - var objB *NoopServiceB - if err := ctx.Service(&objB); err != ErrServiceUnknown { - return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) - } - return new(NoopService), nil - } - // Register the collection of services - if err := stack.Register(NewNoopServiceA); err != nil { - t.Fatalf("former failed to register service: %v", err) + + noop := NewNoop() + stack.RegisterLifecycle(noop) + + isC, err := NewInstrumentedService() + if err != nil { + t.Fatalf("could not create instrumented service %v", err) } - if err := stack.Register(verifier); err != nil { - t.Fatalf("failed to register service verifier: %v", err) + + isB, err := NewInstrumentedService() + if err != nil { + t.Fatalf("could not create instrumented service %v", err) + } - if err := stack.Register(NewNoopServiceB); err != nil { - t.Fatalf("latter failed to register service: %v", err) + isB.startHook = func() { + if err := stack.ServiceContext.Lifecycle(&noop); err != nil { + t.Errorf("former service not found: %v", err) + } + if err := stack.ServiceContext.Lifecycle(&isC); err != ErrServiceUnknown { + t.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) + } } + stack.RegisterLifecycle(isB) + // Start the protocol stack and ensure services are constructed in order if err := stack.Start(); err != nil { t.Fatalf("failed to start stack: %v", err) } + defer stack.Stop() } diff --git a/node/utils_test.go b/node/utils_test.go index 0484ee035708..0755e0411171 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -19,65 +19,47 @@ package node -import ( - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" -) +import "github.com/ethereum/go-ethereum/p2p" -// NoopService is a trivial implementation of the Service interface. +// NoopLifecycle is a trivial implementation of the Service interface. type NoopLifecycle struct{} func (s *NoopLifecycle) Start() error { return nil } func (s *NoopLifecycle) Stop() error { return nil } -func NewNoop(stack *Node) (*Noop, error) { +func NewNoop() *Noop { noop := new(Noop) - stack.RegisterLifecycle(noop) - return noop, nil + return noop } -// Set of services all wrapping the base NoopService resulting in the same method +// Set of services all wrapping the base NoopLifecycle resulting in the same method // signatures but different outer types. type Noop struct{ NoopLifecycle } -//func NewNoopServiceA(*ServiceContext) (Lifecycle, error) { return new(NoopServiceA), nil } -//func NewNoopServiceB(*ServiceContext) (Lifecycle, error) { return new(NoopServiceB), nil } -//func NewNoopServiceC(*ServiceContext) (Lifecycle, error) { return new(NoopServiceC), nil } -// InstrumentedService is an implementation of Service for which all interface +// InstrumentedService is an implementation of Lifecycle for which all interface // methods can be instrumented both return value as well as event hook wise. type InstrumentedService struct { - protocols []p2p.Protocol - apis []rpc.API start error stop error - server *p2p.Server - - protocolsHook func() - startHook func(*p2p.Server) + startHook func() stopHook func() -} -func NewInstrumentedService(server *p2p.Server) (Lifecycle, error) { - is := &InstrumentedService{ server: server } - return is, nil + protocols []p2p.Protocol } -func (s *InstrumentedService) Protocols() []p2p.Protocol { - if s.protocolsHook != nil { - s.protocolsHook() - } - return s.protocols -} +type InstrumentedServiceA struct { InstrumentedService } +type InstrumentedServiceB struct { InstrumentedService } +type InstrumentedServiceC struct { InstrumentedService } -func (s *InstrumentedService) APIs() []rpc.API { - return s.apis +func NewInstrumentedService() (*InstrumentedService, error) { + return new(InstrumentedService), nil } func (s *InstrumentedService) Start() error { if s.startHook != nil { - s.startHook(s.server) + s.startHook() } return s.start } diff --git a/p2p/server.go b/p2p/server.go index 2e7a9c0b14be..aae88260a91b 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1120,6 +1120,6 @@ func (srv *Server) PeersInfo() []*PeerInfo { return infos } -func (srv *Server) Listening() bool { - return srv.listener != nil +func (srv *Server) Running() bool { + return srv.running } From 1badd5daa62b6215f58282425f0bb799c85e59bd Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 May 2020 16:56:38 +0200 Subject: [PATCH 036/160] http server array now a map (#19) --- cmd/geth/config.go | 2 +- cmd/utils/flags.go | 4 ++-- graphql/service.go | 7 +++++-- node/api.go | 51 ++++++++++++++++++++++++++++++---------------- node/node.go | 38 +++++++++++++--------------------- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index df452b8b4fd8..3a4da2710858 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -166,7 +166,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, *eth.Ethereum, *les.LightEthere } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLHost, cfg.Node.GraphQLPort, cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 25faf1b91ec2..33d7b12038ad 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1727,8 +1727,8 @@ func RegisterEthStatsService(stack *node.Node, ethBackend *eth.Ethereum, lesBack } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - if err := graphql.New(stack, ethBackend, lesBackend, endpoint, cors, vhosts, timeouts); err != nil { +func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, host string, port int, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { + if err := graphql.New(stack, ethBackend, lesBackend, host, port, cors, vhosts, timeouts); err != nil { Fatalf("Failed to register the GraphQL service: %w", err) } } diff --git a/graphql/service.go b/graphql/service.go index b086315496aa..54f01c50ca96 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,6 +18,7 @@ package graphql import ( "errors" + "fmt" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les" "net/http" @@ -36,7 +37,7 @@ type Service struct { } // New constructs a new GraphQL service instance. -func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { +func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, host string, port int, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { service := new(Service) // add backend if ethBackend != nil { @@ -47,6 +48,8 @@ func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthere return errors.New("no Ethereum service") } + endpoint := fmt.Sprintf("%s:%d", host, port) + // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) if server != nil { @@ -78,7 +81,7 @@ func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthere Srv: rpc.NewServer(), } gqlServer.SetEndpoint(endpoint) - stack.RegisterHTTPServer(gqlServer) + stack.RegisterHTTPServer(endpoint, gqlServer) service.graphqlServer = gqlServer diff --git a/node/api.go b/node/api.go index 2effb4f2a48f..3badf6209073 100644 --- a/node/api.go +++ b/node/api.go @@ -146,13 +146,7 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - // check if HTTP server already exists - for _, httpServer := range api.node.httpServers { - if httpServer.RPCAllowed { - return false, fmt.Errorf("HTTP RPC already running on %v", httpServer.Listener.Addr()) - } - } - + // set host, port, and endpoint if host == nil { h := DefaultHTTPHost if api.node.config.HTTPHost != "" { @@ -164,6 +158,12 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis port = &api.node.config.HTTPPort } endpoint := fmt.Sprintf("%s:%d", *host, *port) + // check if HTTP server already exists + if server, exists := api.node.httpServerMap[endpoint]; exists { + if server.RPCAllowed { + return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) + } + } allowedOrigins := api.node.config.HTTPCors if cors != nil { @@ -210,7 +210,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), "vhosts", strings.Join(httpServer.Vhosts, ",")) - api.node.RegisterHTTPServer(httpServer) + api.node.RegisterHTTPServer(endpoint, httpServer) return true, nil } @@ -219,7 +219,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.httpServers { + for _, httpServer := range api.node.httpServerMap { if httpServer.RPCAllowed { api.node.stopServer(httpServer) return true, nil @@ -233,10 +233,10 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - // check if an existing HTTP server already handles websocket - for _, httpServer := range api.node.httpServers { - if httpServer.WSAllowed { - return false, fmt.Errorf("WebSocket RPC already running on %v", httpServer.Listener.Addr()) + // check if an existing WS server already exists + for _, server := range api.node.httpServerMap { + if server.WSAllowed { + return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) } } // set host, port and endpoint @@ -251,6 +251,24 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str port = &api.node.config.WSPort } endpoint := fmt.Sprintf("%s:%d", *host, *port) + // check if there is an existing server on the specified port, and if there is, enable ws on it + if server, exists := api.node.httpServerMap[endpoint]; exists { + // else configure ws on the existing server + server.WSAllowed = true + // configure origins + origins := api.node.config.WSOrigins + if allowedOrigins != nil { + origins = nil + for _, origin := range strings.Split(*allowedOrigins, ",") { + origins = append(origins, strings.TrimSpace(origin)) + } + } + server.WsOrigins = origins + + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", server.Listener.Addr())) + return true, nil + } + origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -264,8 +282,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if existingServer != nil { existingServer.WSAllowed = true existingServer.WsOrigins = origins - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.Listener.Addr())) - return true, nil + } modules := api.node.config.WSModules @@ -294,7 +311,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str wsServer.Start() api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.Listener.Addr())) - api.node.RegisterHTTPServer(wsServer) + api.node.RegisterHTTPServer(endpoint, wsServer) return true, nil } @@ -303,7 +320,7 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.httpServers { + for _, httpServer := range api.node.httpServerMap { if httpServer.WSAllowed { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down diff --git a/node/node.go b/node/node.go index 6a94f1c0dee7..e794d57721aa 100644 --- a/node/node.go +++ b/node/node.go @@ -58,7 +58,7 @@ type Node struct { rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - httpServers []*HTTPServer // Stores information about all http servers (if any), including http, ws, and graphql + httpServerMap map[string]*HTTPServer // Stores information about all http servers (if any) by their port, including http, ws, and graphql ipc *HTTPServer // Stores information about the ipc http server @@ -112,7 +112,7 @@ func New(conf *Config) (*Node, error) { Config: *conf, Lifecycles: make(map[reflect.Type]Lifecycle), }, - httpServers: make([]*HTTPServer, 0), + httpServerMap: make(map[string]*HTTPServer), ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, @@ -156,13 +156,13 @@ func New(conf *Config) (*Node, error) { httpServ.WSAllowed = true httpServ.WsOrigins = conf.WSOrigins httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) - node.httpServers = append(node.httpServers, httpServ) + node.httpServerMap[conf.HTTPEndpoint()] = httpServ return node, nil } - node.httpServers = append(node.httpServers, httpServ) + node.httpServerMap[conf.HTTPEndpoint()] = httpServ } if conf.WSHost != "" { - node.httpServers = append(node.httpServers, &HTTPServer{ + node.httpServerMap[conf.WSEndpoint()] = &HTTPServer{ WsOrigins: conf.WSOrigins, Whitelist: conf.WSModules, Srv: rpc.NewServer(), @@ -170,7 +170,7 @@ func New(conf *Config) (*Node, error) { host: conf.WSHost, port: conf.WSPort, WSAllowed: true, - }) + } } return node, nil @@ -221,16 +221,14 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { } // RegisterHTTPServer registers the given HTTP server on the node -func (n *Node) RegisterHTTPServer(server *HTTPServer) { - n.httpServers = append(n.httpServers, server) +func (n *Node) RegisterHTTPServer(endpoint string, server *HTTPServer) { + n.httpServerMap[endpoint] = server } // ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { - for _, httpServer := range n.httpServers { - if endpoint == httpServer.endpoint { - return httpServer - } + if server, exists := n.httpServerMap[endpoint]; exists { + return server } return nil } @@ -364,7 +362,7 @@ func (n *Node) configureRPC() error { return err } - for _, server := range n.httpServers { + for _, server := range n.httpServerMap { // configure the handlers if server.RPCAllowed { server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) @@ -467,15 +465,7 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv = nil } // remove stopped http server from node's http servers // TODO is this preferable? - remainingServers := make([]*HTTPServer, len(n.httpServers)-1) - index := 0 - for _, remaining := range n.httpServers { - if remaining.Server != nil && remaining.Srv != nil { - remainingServers[index] = remaining - index ++ - } - } - n.httpServers = remainingServers + delete(n.httpServerMap, server.endpoint) // remove stopped http server from node's lifecycles n.removeLifecycle(server) } @@ -609,7 +599,7 @@ func (n *Node) HTTPEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - for _, httpServer := range n.httpServers { + for _, httpServer := range n.httpServerMap { if httpServer.RPCAllowed { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() @@ -627,7 +617,7 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - for _, httpServer := range n.httpServers { + for _, httpServer := range n.httpServerMap { if httpServer.WSAllowed { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() From fbc9a6495f4f537de49bb3353e63d11c970fc60a Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 May 2020 17:02:45 +0200 Subject: [PATCH 037/160] removed unnecessary change to pass host, port instead of endpoint --- cmd/geth/config.go | 2 +- cmd/utils/flags.go | 4 ++-- graphql/service.go | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 3a4da2710858..df452b8b4fd8 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -166,7 +166,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, *eth.Ethereum, *les.LightEthere } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLHost, cfg.Node.GraphQLPort, cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 33d7b12038ad..25faf1b91ec2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1727,8 +1727,8 @@ func RegisterEthStatsService(stack *node.Node, ethBackend *eth.Ethereum, lesBack } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, host string, port int, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - if err := graphql.New(stack, ethBackend, lesBackend, host, port, cors, vhosts, timeouts); err != nil { +func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { + if err := graphql.New(stack, ethBackend, lesBackend, endpoint, cors, vhosts, timeouts); err != nil { Fatalf("Failed to register the GraphQL service: %w", err) } } diff --git a/graphql/service.go b/graphql/service.go index 54f01c50ca96..58897c9ffa24 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,7 +18,6 @@ package graphql import ( "errors" - "fmt" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les" "net/http" @@ -37,7 +36,7 @@ type Service struct { } // New constructs a new GraphQL service instance. -func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, host string, port int, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { +func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { service := new(Service) // add backend if ethBackend != nil { @@ -48,8 +47,6 @@ func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthere return errors.New("no Ethereum service") } - endpoint := fmt.Sprintf("%s:%d", host, port) - // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) if server != nil { From 60ced1c499c9bfd510900f04d73606f17c1bf28b Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 May 2020 17:39:15 +0200 Subject: [PATCH 038/160] added some more tests for http servers --- graphql/graphql_test.go | 39 +++++++++++++++++++++++++++++++++++++++ node/node_test.go | 19 +++++++++++++++++-- node/rpcstack_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 40b13187f496..6523d1ccbc01 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -17,6 +17,11 @@ package graphql import ( + "fmt" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/assert" "testing" ) @@ -26,3 +31,37 @@ func TestBuildSchema(t *testing.T) { t.Errorf("Could not construct GraphQL handler: %v", err) } } + +// Tests that a graphql handler can be added to an existing HTTPServer +func TestGQLAllowed(t *testing.T) { + stack, err := node.New(&node.Config{ + HTTPHost: node.DefaultHTTPHost, + HTTPPort: 9393, + }) + if err != nil { + t.Fatalf("could not create node: %v", err) + } + defer stack.Close() + // create backend + ethBackend, err := eth.New(stack, ð.DefaultConfig) + if err != nil { + t.Fatalf("could not create eth backend: %v", err) + } + // set endpoint and create new gql service + endpoint := fmt.Sprintf("%s:%d", node.DefaultHTTPHost, 9393) + err = New(stack,ethBackend,nil, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + if err != nil { + t.Fatalf("could not create graphql service: %v", err) + } + // start node + if err = stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // check that server was created + server := stack.ExistingHTTPServer(endpoint) + if server == nil { + t.Errorf("server was not created on the given endpoint: %v", err) + } + // assert that server allows GQL requests + assert.True(t, server.GQLAllowed) +} diff --git a/node/node_test.go b/node/node_test.go index e8764f1ab383..c81dfe0d009f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -362,7 +362,7 @@ func TestLifecycleRetrieval(t *testing.T) { // Tests whether websocket requests can be handled on the same port as a regular http server func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { node := startHTTP(t) - defer node.Stop() + defer node.Close() wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -377,10 +377,23 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) } +// Tests whether graphQL requests can be handled on the same port as a regular http server +func TestGraphQLHTTPOnSamePort_GQLRequest(t *testing.T) { + node := startHTTP(t) + defer node.Close() + + gqlReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453/graphql", nil) + if err != nil { + t.Error("could not issue new http request ", err) + } + resp := doHTTPRequest(t, gqlReq) + assert.Equal(t, resp.StatusCode, 200) +} + // Tests whether http requests can be handled successfully func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { node := startHTTP(t) - defer node.Stop() + defer node.Close() httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) if err != nil { @@ -398,6 +411,8 @@ func startHTTP(t *testing.T) *Node { HTTPPort: 7453, WSHost: "127.0.0.1", WSPort: 7453, + GraphQLHost: "127.0.0.1", + GraphQLPort: 7453, } node, err := New(conf) if err != nil { diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index ff887738df33..364bceb54094 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -1,6 +1,7 @@ package node import ( + "fmt" "net/http" "net/http/httptest" "testing" @@ -38,3 +39,30 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { response := <-responses assert.Equal(t, "websocket", response.Header.Get("Upgrade")) } + +// Tests that a ws handler can be added to and enabled on an existing HTTPServer +func TestWSAllowed(t *testing.T) { + stack, err := New(&Config{ + HTTPHost: DefaultHTTPHost, + HTTPPort: 9393, + WSHost: DefaultHTTPHost, + WSPort: 9393, + }) + if err != nil { + t.Fatalf("could not create node: %v", err) + } + defer stack.Close() + // start node + err = stack.Start() + if err != nil { + t.Fatalf("could not start node: %v", err) + } + // check that server was configured on the given endpoint + server := stack.ExistingHTTPServer(fmt.Sprintf("%s:%d", DefaultHTTPHost, 9393)) + if server == nil { + t.Fatalf("server was not started on the given endpoint: %v", err) + } + // assert that both RPC and WS are allowed on the HTTP Server + assert.True(t, server.RPCAllowed) + assert.True(t, server.WSAllowed) +} From 53a2c8f0f1fd5b3b6a6034cc0cc61bf9db077847 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 26 May 2020 14:12:03 +0200 Subject: [PATCH 039/160] moved some gql-specific tests to gql package --- graphql/graphql_test.go | 130 +++++++++++++++++++++++++++++++++++----- node/node_test.go | 17 +----- 2 files changed, 117 insertions(+), 30 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 6523d1ccbc01..66a589829dfa 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -22,9 +22,14 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" + "io/ioutil" + "net/http" + "strings" "testing" ) +var testEndpoint = "127.0.0.1:9393" + func TestBuildSchema(t *testing.T) { // Make sure the schema can be parsed and matched up to the object model. if _, err := newHandler(nil); err != nil { @@ -34,34 +39,129 @@ func TestBuildSchema(t *testing.T) { // Tests that a graphql handler can be added to an existing HTTPServer func TestGQLAllowed(t *testing.T) { + stack := createNode(t, true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // check that server was created + server := stack.ExistingHTTPServer(testEndpoint) + if server == nil { + t.Errorf("server was not created on the given endpoint") + } + // assert that server allows GQL requests + assert.True(t, server.GQLAllowed) +} + +// Tests to make sure an HTTPServer is created that handles for http, ws, and graphQL +func TestMultiplexedServer(t *testing.T) { + stack := createNode(t, true) + defer stack.Close() + // start the node + if err := stack.Start(); err != nil { + t.Error("could not start http service on node ", err) + } + server := stack.ExistingHTTPServer(testEndpoint) + if server == nil { + t.Fatalf("server was not configured on the given endpoint") + } + assert.True(t, server.RPCAllowed) + assert.True(t, server.WSAllowed) + assert.True(t, server.GQLAllowed) +} + +// Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint +func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { + stack := createNode(t,true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // create http request + body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", testEndpoint), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" + assert.Equal(t, expected,string(bodyBytes)) +} + +// Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint +func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { + stack := createNode(t, false) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // make sure GQL is not enabled + server := stack.ExistingHTTPServer(testEndpoint) + if server == nil { + t.Fatalf("server was not created on the given endpoint") + } + assert.False(t, server.GQLAllowed) + // create http request + body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", testEndpoint), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + // make sure the request is not handled successfully + expected := "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32600,\"message\":\"invalid request\"}}\n" + assert.Equal(t, string(bodyBytes), expected) +} + +func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ - HTTPHost: node.DefaultHTTPHost, + HTTPHost: "127.0.0.1", HTTPPort: 9393, + WSHost: "127.0.0.1", + WSPort: 9393, }) if err != nil { t.Fatalf("could not create node: %v", err) } - defer stack.Close() + if !gqlEnabled { + return stack + } // create backend ethBackend, err := eth.New(stack, ð.DefaultConfig) if err != nil { t.Fatalf("could not create eth backend: %v", err) } - // set endpoint and create new gql service - endpoint := fmt.Sprintf("%s:%d", node.DefaultHTTPHost, 9393) - err = New(stack,ethBackend,nil, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + + // create gql service + err = New(stack,ethBackend,nil, testEndpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } - // start node - if err = stack.Start(); err != nil { - t.Fatalf("could not start node: %v", err) - } - // check that server was created - server := stack.ExistingHTTPServer(endpoint) - if server == nil { - t.Errorf("server was not created on the given endpoint: %v", err) + + return stack +} + +func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + t.Fatal("could not issue a GET request to the given endpoint", err) + } - // assert that server allows GQL requests - assert.True(t, server.GQLAllowed) + return resp } diff --git a/node/node_test.go b/node/node_test.go index c81dfe0d009f..6e53630d4571 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -377,19 +377,6 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) } -// Tests whether graphQL requests can be handled on the same port as a regular http server -func TestGraphQLHTTPOnSamePort_GQLRequest(t *testing.T) { - node := startHTTP(t) - defer node.Close() - - gqlReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453/graphql", nil) - if err != nil { - t.Error("could not issue new http request ", err) - } - resp := doHTTPRequest(t, gqlReq) - assert.Equal(t, resp.StatusCode, 200) -} - // Tests whether http requests can be handled successfully func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { node := startHTTP(t) @@ -405,14 +392,14 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) } +// TODO why is GQL still served successfully if the server doesn't have GQL enabled + func startHTTP(t *testing.T) *Node { conf := &Config{ HTTPHost: "127.0.0.1", HTTPPort: 7453, WSHost: "127.0.0.1", WSPort: 7453, - GraphQLHost: "127.0.0.1", - GraphQLPort: 7453, } node, err := New(conf) if err != nil { From 331d0a64365de5ed7b9389ac3352da0f2a0c48d0 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 26 May 2020 14:12:17 +0200 Subject: [PATCH 040/160] removed todo --- node/node_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index 6e53630d4571..337b55525b6e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -392,8 +392,6 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) } -// TODO why is GQL still served successfully if the server doesn't have GQL enabled - func startHTTP(t *testing.T) *Node { conf := &Config{ HTTPHost: "127.0.0.1", From 45eecce340590acac9a77fa72988814b15ed86ee Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 3 Jun 2020 15:46:46 +0200 Subject: [PATCH 041/160] whisper tests working --- whisper/whisperv6/api_test.go | 5 +- whisper/whisperv6/filter_test.go | 31 ++++++-- whisper/whisperv6/whisper_test.go | 114 ++++++++++++++++++++++-------- 3 files changed, 116 insertions(+), 34 deletions(-) diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go index 6d7157f57fd2..9aceaf456d6b 100644 --- a/whisper/whisperv6/api_test.go +++ b/whisper/whisperv6/api_test.go @@ -23,7 +23,10 @@ import ( ) func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { - w := New(nil) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) keyID, err := w.GenerateSymKey() if err != nil { diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go index 55f505291977..49c2b7688361 100644 --- a/whisper/whisperv6/filter_test.go +++ b/whisper/whisperv6/filter_test.go @@ -92,7 +92,12 @@ func TestInstallFilters(t *testing.T) { InitSingleTest() const SizeTestFilters = 256 - w := New(&Config{}) + + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + filters := NewFilters(w) tst := generateTestCases(t, SizeTestFilters) @@ -130,7 +135,11 @@ func TestInstallFilters(t *testing.T) { func TestInstallSymKeyGeneratesHash(t *testing.T) { InitSingleTest() - w := New(&Config{}) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + filters := NewFilters(w) filter, _ := generateFilter(t, true) @@ -157,7 +166,11 @@ func TestInstallSymKeyGeneratesHash(t *testing.T) { func TestInstallIdenticalFilters(t *testing.T) { InitSingleTest() - w := New(&Config{}) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -227,7 +240,11 @@ func TestInstallIdenticalFilters(t *testing.T) { func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { InitSingleTest() - w := New(&Config{}) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -641,7 +658,11 @@ func TestWatchers(t *testing.T) { var x, firstID string var err error - w := New(&Config{}) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + filters := NewFilters(w) tst := generateTestCases(t, NumFilters) for i = 0; i < NumFilters; i++ { diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index ba2db975d14b..f1fa4dfdd27d 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/ecdsa" "crypto/sha256" + "github.com/ethereum/go-ethereum/node" mrand "math/rand" "testing" "time" @@ -29,9 +30,11 @@ import ( ) func TestWhisperBasic(t *testing.T) { - w := New(&DefaultConfig) - p := w.Protocols() - shh := p[0] + stack := newNode(t) + defer stack.Close() + // get whisper service from node + w := getWhisperFromNode(stack, t) + shh := w.Protocols()[0] if shh.Name != ProtocolName { t.Fatalf("failed Protocol Name: %v.", shh.Name) } @@ -111,11 +114,11 @@ func TestWhisperBasic(t *testing.T) { } func TestWhisperAsymmetricKeyImport(t *testing.T) { - var ( - w = New(&DefaultConfig) - privateKeys []*ecdsa.PrivateKey - ) + stack := newNode(t) + defer stack.Close() + var privateKeys []*ecdsa.PrivateKey + w := getWhisperFromNode(stack, t) for i := 0; i < 50; i++ { id, err := w.NewKeyPair() if err != nil { @@ -142,7 +145,10 @@ func TestWhisperAsymmetricKeyImport(t *testing.T) { } func TestWhisperIdentityManagement(t *testing.T) { - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) id1, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) @@ -261,12 +267,16 @@ func TestWhisperIdentityManagement(t *testing.T) { func TestWhisperSymKeyManagement(t *testing.T) { InitSingleTest() - var ( k1, k2 []byte - w = New(&DefaultConfig) id2 = string("arbitrary-string-2") ) + + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + id1, err := w.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) @@ -365,7 +375,7 @@ func TestWhisperSymKeyManagement(t *testing.T) { w.DeleteSymKey(id1) k1, err = w.GetSymKey(id1) if err == nil { - t.Fatalf("failed w.GetSymKey(id1): false positive.") + t.Fatal("failed w.GetSymKey(id1): false positive.") } if k1 != nil { t.Fatalf("failed GetSymKey(id1): false positive. key=%v", k1) @@ -451,11 +461,14 @@ func TestWhisperSymKeyManagement(t *testing.T) { func TestExpiry(t *testing.T) { InitSingleTest() - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + w.SetMinimumPowTest(0.0000001) defer w.SetMinimumPowTest(DefaultMinimumPoW) - w.Start(nil) - defer w.Stop() + w.Start() params, err := generateMessageParams() if err != nil { @@ -517,11 +530,15 @@ func TestExpiry(t *testing.T) { func TestCustomization(t *testing.T) { InitSingleTest() - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start(nil) - defer w.Stop() + w.Start() + const smallPoW = 0.00001 @@ -610,11 +627,15 @@ func TestCustomization(t *testing.T) { func TestSymmetricSendCycle(t *testing.T) { InitSingleTest() - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start(nil) - defer w.Stop() + w.Start() + filter1, err := generateFilter(t, true) if err != nil { @@ -701,11 +722,15 @@ func TestSymmetricSendCycle(t *testing.T) { func TestSymmetricSendWithoutAKey(t *testing.T) { InitSingleTest() - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start(nil) - defer w.Stop() + w.Start() + filter, err := generateFilter(t, true) if err != nil { @@ -771,11 +796,15 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { func TestSymmetricSendKeyMismatch(t *testing.T) { InitSingleTest() - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start(nil) - defer w.Stop() + w.Start() + filter, err := generateFilter(t, true) if err != nil { @@ -882,17 +911,46 @@ func TestBloom(t *testing.T) { t.Fatal("bloomFilterMatch false negative") } - w := New(&DefaultConfig) + stack := newNode(t) + defer stack.Close() + + w := getWhisperFromNode(stack, t) + f := w.BloomFilter() if f != nil { t.Fatal("wrong bloom on creation") } err = w.SetBloomFilter(x) if err != nil { - t.Fatalf("failed to set bloom filter: %s", err) + t.Fatalf("failed to set bloom filter: %v", err) } f = w.BloomFilter() if !BloomFilterMatch(f, x) || !BloomFilterMatch(x, f) { t.Fatal("retireved wrong bloom filter") } } + +func newNode(t *testing.T) *node.Node { + stack, err := node.New(&node.DefaultConfig) + if err != nil { + t.Fatalf("could not create new node: %v", err) + } + err = New(stack, &DefaultConfig) + if err != nil { + t.Fatalf("could not create new whisper service: %v", err) + } + err = stack.Start() + if err != nil { + t.Fatalf("could not start node: %v", err) + } + return stack +} + +func getWhisperFromNode(stack *node.Node, t *testing.T) *Whisper { + var w *Whisper + err := stack.Lifecycle(&w) + if err != nil { + t.Fatalf("could not get whisper service from node: %v", err) + } + return w +} From a380ff658e25e16746bd289b4d60b38338d3fc2a Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 3 Jun 2020 16:03:46 +0200 Subject: [PATCH 042/160] create a standalone whisper service for wnode purposes --- cmd/wnode/main.go | 7 +++--- whisper/whisperv6/whisper.go | 42 +++++++++++++++++++++++++++++++ whisper/whisperv6/whisper_test.go | 2 ++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 677cad7ef142..f20c00ceb183 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -221,8 +221,7 @@ func initialize() { MaxMessageSize: uint32(*argMaxSize), MinimumAcceptedPOW: *argPoW, } - - shh = whisper.New(cfg) + shh = whisper.StandaloneWhisperService(cfg) if *argPoW != whisper.DefaultMinimumPoW { err := shh.SetMinimumPoW(*argPoW) @@ -433,7 +432,7 @@ func run() { return } defer server.Stop() - shh.Start(nil) + shh.Start() defer shh.Stop() if !*forwarderMode { @@ -771,4 +770,4 @@ func obfuscateBloom(bloom []byte) { bloom[x/8] = 1 << uint(x%8) // set the bit number X } -} +} \ No newline at end of file diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index af848d2fdc79..3ded51010f63 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -1099,3 +1099,45 @@ func addBloom(a, b []byte) []byte { } return c } + +func StandaloneWhisperService(cfg *Config) *Whisper { + if cfg == nil { + cfg = &DefaultConfig + } + + whisper := &Whisper{ + privateKeys: make(map[string]*ecdsa.PrivateKey), + symKeys: make(map[string][]byte), + envelopes: make(map[common.Hash]*Envelope), + expirations: make(map[uint32]mapset.Set), + peers: make(map[*Peer]struct{}), + messageQueue: make(chan *Envelope, messageQueueLimit), + p2pMsgQueue: make(chan *Envelope, messageQueueLimit), + quit: make(chan struct{}), + syncAllowance: DefaultSyncAllowance, + } + + whisper.filters = NewFilters(whisper) + + whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW) + whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize) + whisper.settings.Store(overflowIdx, false) + whisper.settings.Store(restrictConnectionBetweenLightClientsIdx, cfg.RestrictConnectionBetweenLightClients) + + // p2p whisper sub protocol handler + whisper.protocol = p2p.Protocol{ + Name: ProtocolName, + Version: uint(ProtocolVersion), + Length: NumberOfMessageCodes, + Run: whisper.HandlePeer, + NodeInfo: func() interface{} { + return map[string]interface{}{ + "version": ProtocolVersionStr, + "maxMessageSize": whisper.MaxMessageSize(), + "minimumPoW": whisper.MinPow(), + } + }, + } + + return whisper +} diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index f1fa4dfdd27d..351ed6403ebe 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -930,6 +930,7 @@ func TestBloom(t *testing.T) { } } +// newNode creates a new node using a default config. func newNode(t *testing.T) *node.Node { stack, err := node.New(&node.DefaultConfig) if err != nil { @@ -946,6 +947,7 @@ func newNode(t *testing.T) *node.Node { return stack } +// getWhisperFromNode retrieves the Whisper service from the running node. func getWhisperFromNode(stack *node.Node, t *testing.T) *Whisper { var w *Whisper err := stack.Lifecycle(&w) From 076a9a5340f2e68a20525d5f67dfaf827e95a930 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 4 Jun 2020 21:45:03 +0200 Subject: [PATCH 043/160] p2p/simulations uses `node.Lifecycle` instead of `node.Service` (#21) --- cmd/p2psim/main.go | 2 +- les/api_test.go | 14 ++--- p2p/simulations/adapters/exec.go | 56 ++++++++----------- p2p/simulations/adapters/inproc.go | 78 ++++++++++++--------------- p2p/simulations/adapters/types.go | 38 ++++++------- p2p/simulations/connect_test.go | 4 +- p2p/simulations/examples/ping-pong.go | 15 ++++-- p2p/simulations/http_test.go | 11 ++-- p2p/simulations/network.go | 12 ++--- p2p/simulations/network_test.go | 28 +++++----- p2p/simulations/test.go | 2 +- 11 files changed, 125 insertions(+), 135 deletions(-) diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go index f2c1bf970350..812954a68029 100644 --- a/cmd/p2psim/main.go +++ b/cmd/p2psim/main.go @@ -289,7 +289,7 @@ func createNode(ctx *cli.Context) error { config.PrivateKey = privKey } if services := ctx.String("services"); services != "" { - config.Services = strings.Split(services, ",") + config.Lifecycles = strings.Split(services, ",") } node, err := client.CreateNode(config) if err != nil { diff --git a/les/api_test.go b/les/api_test.go index d6571fabe88f..72ed2f556ff3 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -55,7 +55,7 @@ func TestMain(m *testing.M) { log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) // register the Delivery service which will run as a devp2p // protocol when using the exec adapter - adapters.RegisterServices(services) + adapters.RegisterLifecycles(services) os.Exit(m.Run()) } @@ -392,7 +392,7 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min return } -var services = adapters.Services{ +var services = adapters.LifecycleConstructors{ "lesclient": newLesClientService, "lesserver": newLesServerService, } @@ -414,7 +414,7 @@ func NewNetwork() (*simulations.Network, func(), error) { return net, teardown, nil } -func NewAdapter(adapterType string, services adapters.Services) (adapter adapters.NodeAdapter, teardown func(), err error) { +func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) { teardown = func() {} switch adapterType { case "sim": @@ -454,7 +454,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] for i := range clients { clientconf := adapters.RandomNodeConfig() - clientconf.Services = []string{"lesclient"} + clientconf.Lifecycles = []string{"lesclient"} if len(clientDir) == clientCount { clientconf.DataDir = clientDir[i] } @@ -467,7 +467,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] for i := range servers { serverconf := adapters.RandomNodeConfig() - serverconf.Services = []string{"lesserver"} + serverconf.Lifecycles = []string{"lesserver"} if len(serverDir) == serverCount { serverconf.DataDir = serverDir[i] } @@ -492,14 +492,14 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] return test(ctx, net, servers, clients) } -func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (*LightEthereum, error) { +func newLesClientService(stack *node.Node) (node.Lifecycle, error) { config := eth.DefaultConfig config.SyncMode = downloader.LightSync config.Ethash.PowMode = ethash.ModeFake return New(stack, &config) } -func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (*eth.Ethereum, error) { +func newLesServerService(stack *node.Node) (node.Lifecycle, error) { config := eth.DefaultConfig config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 18ec9c69b813..2a6d3aeaaa7e 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -75,11 +75,11 @@ func (e *ExecAdapter) Name() string { // NewNode returns a new ExecNode using the given config func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { - if len(config.Services) == 0 { - return nil, errors.New("node must have at least one service") + if len(config.Lifecycles) == 0 { + return nil, errors.New("node must have at least one service lifecycle") } - for _, service := range config.Services { - if _, exists := serviceFuncs[service]; !exists { + for _, service := range config.Lifecycles { + if _, exists := lifecycleConstructorFuncs[service]; !exists { return nil, fmt.Errorf("unknown node service %q", service) } } @@ -263,7 +263,7 @@ func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeSta func (n *ExecNode) execCommand() *exec.Cmd { return &exec.Cmd{ Path: reexec.Self(), - Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()}, + Args: []string{"p2p-node", strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String()}, } } @@ -436,40 +436,30 @@ func startExecNodeStack() (*node.Node, error) { // register the services, collecting them into a map so we can wrap // them in a snapshot service - services := make(map[string]node.Service, len(serviceNames)) + services := make(map[string]node.Lifecycle, len(serviceNames)) for _, name := range serviceNames { - serviceFunc, exists := serviceFuncs[name] + lifecycleFunc, exists := lifecycleConstructorFuncs[name] if !exists { return nil, fmt.Errorf("unknown node service %q", err) } - constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) { - ctx := &ServiceContext{ - RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, - NodeContext: nodeCtx, - Config: conf.Node, - } - if conf.Snapshots != nil { - ctx.Snapshot = conf.Snapshots[name] - } - service, err := serviceFunc(ctx) - if err != nil { - return nil, err - } - services[name] = service - return service, nil + ctx := &ServiceContext{ + RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, + NodeContext: stack.ServiceContext, + Config: conf.Node, } - if err := stack.Register(constructor); err != nil { - return stack, fmt.Errorf("error registering service %q: %v", name, err) + if conf.Snapshots != nil { + ctx.Snapshot = conf.Snapshots[name] } + service, err := lifecycleFunc(ctx, stack) + if err != nil { + return nil, err + } + services[name] = service + stack.RegisterLifecycle(service) } // register the snapshot service - err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return &snapshotService{services}, nil - }) - if err != nil { - return stack, fmt.Errorf("error starting snapshot service: %v", err) - } + stack.RegisterLifecycle(&snapshotService{services}) // start the stack if err = stack.Start(); err != nil { @@ -493,7 +483,7 @@ type nodeStartupJSON struct { // snapshotService is a node.Service which wraps a list of services and // exposes an API to generate a snapshot of those services type snapshotService struct { - services map[string]node.Service + services map[string]node.Lifecycle } func (s *snapshotService) APIs() []rpc.API { @@ -508,7 +498,7 @@ func (s *snapshotService) Protocols() []p2p.Protocol { return nil } -func (s *snapshotService) Start(*p2p.Server) error { +func (s *snapshotService) Start() error { return nil } @@ -518,7 +508,7 @@ func (s *snapshotService) Stop() error { // SnapshotAPI provides an RPC method to create snapshots of services type SnapshotAPI struct { - services map[string]node.Service + services map[string]node.Lifecycle } func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 651d9546aec2..48c5bd9753f2 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -37,29 +37,21 @@ import ( // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and // connects them using net.Pipe type SimAdapter struct { - pipe func() (net.Conn, net.Conn, error) - mtx sync.RWMutex - nodes map[enode.ID]*SimNode - services map[string]ServiceFunc + pipe func() (net.Conn, net.Conn, error) + mtx sync.RWMutex + nodes map[enode.ID]*SimNode + lifecycles LifecycleConstructors } // NewSimAdapter creates a SimAdapter which is capable of running in-memory // simulation nodes running any of the given services (the services to run on a // particular node are passed to the NewNode function in the NodeConfig) // the adapter uses a net.Pipe for in-memory simulated network connections -func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter { +func NewSimAdapter(services LifecycleConstructors) *SimAdapter { return &SimAdapter{ - pipe: pipes.NetPipe, - nodes: make(map[enode.ID]*SimNode), - services: services, - } -} - -func NewTCPAdapter(services map[string]ServiceFunc) *SimAdapter { - return &SimAdapter{ - pipe: pipes.TCPPipe, - nodes: make(map[enode.ID]*SimNode), - services: services, + pipe: pipes.NetPipe, + nodes: make(map[enode.ID]*SimNode), + lifecycles: services, } } @@ -85,11 +77,11 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { } // check the services are valid - if len(config.Services) == 0 { + if len(config.Lifecycles) == 0 { return nil, errors.New("node must have at least one service") } - for _, service := range config.Services { - if _, exists := s.services[service]; !exists { + for _, service := range config.Lifecycles { + if _, exists := s.lifecycles[service]; !exists { return nil, fmt.Errorf("unknown node service %q", service) } } @@ -119,7 +111,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { config: config, node: n, adapter: s, - running: make(map[string]node.Service), + running: make(map[string]node.Lifecycle), } s.nodes[id] = simNode return simNode, nil @@ -179,7 +171,7 @@ type SimNode struct { config *NodeConfig adapter *SimAdapter node *node.Node - running map[string]node.Service + running map[string]node.Lifecycle client *rpc.Client registerOnce sync.Once } @@ -227,7 +219,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error { // simulation_snapshot RPC method func (sn *SimNode) Snapshots() (map[string][]byte, error) { sn.lock.RLock() - services := make(map[string]node.Service, len(sn.running)) + services := make(map[string]node.Lifecycle, len(sn.running)) for name, service := range sn.running { services[name] = service } @@ -252,35 +244,31 @@ func (sn *SimNode) Snapshots() (map[string][]byte, error) { // Start registers the services and starts the underlying devp2p node func (sn *SimNode) Start(snapshots map[string][]byte) error { - newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) { - return func(nodeCtx *node.ServiceContext) (node.Service, error) { + // ensure we only register the services once in the case of the node + // being stopped and then started again + var regErr error + sn.registerOnce.Do(func() { + for _, name := range sn.config.Lifecycles { ctx := &ServiceContext{ RPCDialer: sn.adapter, - NodeContext: nodeCtx, + NodeContext: sn.node.ServiceContext, Config: sn.config, } if snapshots != nil { ctx.Snapshot = snapshots[name] } - serviceFunc := sn.adapter.services[name] - service, err := serviceFunc(ctx) + serviceFunc := sn.adapter.lifecycles[name] + service, err := serviceFunc(ctx, sn.node) if err != nil { - return nil, err - } - sn.running[name] = service - return service, nil - } - } - - // ensure we only register the services once in the case of the node - // being stopped and then started again - var regErr error - sn.registerOnce.Do(func() { - for _, name := range sn.config.Services { - if err := sn.node.Register(newService(name)); err != nil { regErr = err break } + // if the service has already been registered, don't register it again. + if _, ok := sn.running[name]; ok { + continue + } + sn.running[name] = service + sn.node.RegisterLifecycle(service) } }) if regErr != nil { @@ -316,17 +304,17 @@ func (sn *SimNode) Stop() error { } // Service returns a running service by name -func (sn *SimNode) Service(name string) node.Service { +func (sn *SimNode) Service(name string) node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() return sn.running[name] } // Services returns a copy of the underlying services -func (sn *SimNode) Services() []node.Service { +func (sn *SimNode) Services() []node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() - services := make([]node.Service, 0, len(sn.running)) + services := make([]node.Lifecycle, 0, len(sn.running)) for _, service := range sn.running { services = append(services, service) } @@ -334,10 +322,10 @@ func (sn *SimNode) Services() []node.Service { } // ServiceMap returns a map by names of the underlying services -func (sn *SimNode) ServiceMap() map[string]node.Service { +func (sn *SimNode) ServiceMap() map[string]node.Lifecycle { sn.lock.RLock() defer sn.lock.RUnlock() - services := make(map[string]node.Service, len(sn.running)) + services := make(map[string]node.Lifecycle, len(sn.running)) for name, service := range sn.running { services[name] = service } diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 498723d1ac26..1ca60f4b9b58 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -96,11 +96,11 @@ type NodeConfig struct { // Use an existing database instead of a temporary one if non-empty DataDir string - // Services are the names of the services which should be run when - // starting the node (for SimNodes it should be the names of services - // contained in SimAdapter.services, for other nodes it should be - // services registered by calling the RegisterService function) - Services []string + // Lifecycles are the names of the service lifecycles which should be run when + // starting the node (for SimNodes it should be the names of service lifecycles + // contained in SimAdapter.lifecycles, for other nodes it should be + // service lifecycles registered by calling the RegisterLifecycle function) + Lifecycles []string // Properties are the names of the properties this node should hold // within running services (e.g. "bootnode", "lightnode" or any custom values) @@ -137,7 +137,7 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { confJSON := nodeConfigJSON{ ID: n.ID.String(), Name: n.Name, - Services: n.Services, + Services: n.Lifecycles, Properties: n.Properties, Port: n.Port, EnableMsgEvents: n.EnableMsgEvents, @@ -175,7 +175,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { } n.Name = confJSON.Name - n.Services = confJSON.Services + n.Lifecycles = confJSON.Services n.Properties = confJSON.Properties n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents @@ -245,27 +245,29 @@ type RPCDialer interface { DialRPC(id enode.ID) (*rpc.Client, error) } -// Services is a collection of services which can be run in a simulation -type Services map[string]ServiceFunc +// LifecycleConstructor allows a Lifecycle to be constructed during node start-up. +// While the service-specific package usually takes care of Lifecycle creation and registration, +// for testing purposes, it is useful to be able to construct a Lifecycle on spot. +type LifecycleConstructor func(ctx *ServiceContext, stack *node.Node) (node.Lifecycle, error) -// ServiceFunc returns a node.Service which can be used to boot a devp2p node -type ServiceFunc func(ctx *ServiceContext) (node.Service, error) +// LifecycleConstructors stores LifecycleConstructor functions to call during node start-up. +type LifecycleConstructors map[string]LifecycleConstructor -// serviceFuncs is a map of registered services which are used to boot devp2p +// lifecycleConstructorFuncs is a map of registered services which are used to boot devp2p // nodes -var serviceFuncs = make(Services) +var lifecycleConstructorFuncs = make(LifecycleConstructors) -// RegisterServices registers the given Services which can then be used to +// RegisterLifecycles registers the given Services which can then be used to // start devp2p nodes using either the Exec or Docker adapters. // // It should be called in an init function so that it has the opportunity to // execute the services before main() is called. -func RegisterServices(services Services) { - for name, f := range services { - if _, exists := serviceFuncs[name]; exists { +func RegisterLifecycles(lifecycles LifecycleConstructors) { + for name, f := range lifecycles { + if _, exists := lifecycleConstructorFuncs[name]; exists { panic(fmt.Sprintf("node service already exists: %q", name)) } - serviceFuncs[name] = f + lifecycleConstructorFuncs[name] = f } // now we have registered the services, run reexec.Init() which will diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go index 32d18347d83a..0154a18b030f 100644 --- a/p2p/simulations/connect_test.go +++ b/p2p/simulations/connect_test.go @@ -26,8 +26,8 @@ import ( func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) { t.Helper() - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index cde2f3a677e2..678236f3552a 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -45,12 +45,17 @@ func main() { log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) // register a single ping-pong service - services := map[string]adapters.ServiceFunc{ - "ping-pong": func(ctx *adapters.ServiceContext) (node.Service, error) { - return newPingPongService(ctx.Config.ID), nil + services := map[string]adapters.LifecycleConstructor{ + "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { + pps := newPingPongService(ctx.Config.ID) + if err := stack.RegisterProtocols(pps.Protocols()); err != nil { + return nil, err + } + stack.RegisterAPIs(pps.APIs()) + return pps, nil }, } - adapters.RegisterServices(services) + adapters.RegisterLifecycles(services) // create the NodeAdapter var adapter adapters.NodeAdapter @@ -114,7 +119,7 @@ func (p *pingPongService) APIs() []rpc.API { return nil } -func (p *pingPongService) Start(server *p2p.Server) error { +func (p *pingPongService) Start() error { p.log.Info("ping-pong service starting") return nil } diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index e88999f48b8c..1ecfb719fb96 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -64,12 +64,17 @@ type testService struct { state atomic.Value } -func newTestService(ctx *adapters.ServiceContext) (node.Service, error) { +func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { svc := &testService{ id: ctx.Config.ID, peers: make(map[enode.ID]*testPeer), } svc.state.Store(ctx.Snapshot) + + if err := stack.RegisterProtocols(svc.Protocols()); err != nil { + return nil, err + } + stack.RegisterAPIs(svc.APIs()) return svc, nil } @@ -126,7 +131,7 @@ func (t *testService) APIs() []rpc.API { }} } -func (t *testService) Start(server *p2p.Server) error { +func (t *testService) Start() error { return nil } @@ -288,7 +293,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) { return rpcSub, nil } -var testServices = adapters.Services{ +var testServices = adapters.LifecycleConstructors{ "test": newTestService, } diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index ef5451e77ec4..a54db4ea68d3 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -110,8 +110,8 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) } // if no services are configured, use the default service - if len(conf.Services) == 0 { - conf.Services = []string{net.DefaultService} + if len(conf.Lifecycles) == 0 { + conf.Lifecycles = []string{net.DefaultService} } // use the NodeAdapter to create the node @@ -913,19 +913,19 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn snap.Nodes[i].Snapshots = snapshots for _, addSvc := range addServices { haveSvc := false - for _, svc := range snap.Nodes[i].Node.Config.Services { + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { if svc == addSvc { haveSvc = true break } } if !haveSvc { - snap.Nodes[i].Node.Config.Services = append(snap.Nodes[i].Node.Config.Services, addSvc) + snap.Nodes[i].Node.Config.Lifecycles = append(snap.Nodes[i].Node.Config.Lifecycles, addSvc) } } if len(removeServices) > 0 { var cleanedServices []string - for _, svc := range snap.Nodes[i].Node.Config.Services { + for _, svc := range snap.Nodes[i].Node.Config.Lifecycles { haveSvc := false for _, rmSvc := range removeServices { if rmSvc == svc { @@ -938,7 +938,7 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn } } - snap.Nodes[i].Node.Config.Services = cleanedServices + snap.Nodes[i].Node.Config.Lifecycles = cleanedServices } } for _, conn := range net.Conns { diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index ac1b06a80ca5..d5651441a2fe 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -41,8 +41,8 @@ func TestSnapshot(t *testing.T) { // create snapshot from ring network // this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) @@ -165,8 +165,8 @@ OUTER: // PART II // load snapshot and verify that exactly same connections are formed - adapter = adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }) @@ -256,8 +256,8 @@ OuterTwo: t.Run("conns after load", func(t *testing.T) { // Create new network. n := NewNetwork( - adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return NewNoopService(nil), nil }, }), @@ -288,7 +288,7 @@ OuterTwo: // with each other and that a snapshot fully represents the desired topology func TestNetworkSimulation(t *testing.T) { // create simulation network with 20 testService nodes - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -437,7 +437,7 @@ func createTestNodesWithProperty(property string, count int, network *Network) ( // It then tests again whilst excluding a node ID from being returned. // If a node ID is not returned, or more node IDs than expected are returned, the test fails. func TestGetNodeIDs(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -486,7 +486,7 @@ func TestGetNodeIDs(t *testing.T) { // It then tests again whilst excluding a node from being returned. // If a node is not returned, or more nodes than expected are returned, the test fails. func TestGetNodes(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -534,7 +534,7 @@ func TestGetNodes(t *testing.T) { // TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID // If a node is not returned, or more nodes than expected are returned, the test fails. func TestGetNodesByID(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -579,7 +579,7 @@ func TestGetNodesByID(t *testing.T) { // GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created. // If a node with a property is not found, or more nodes than expected are returned, the test fails. func TestGetNodesByProperty(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -624,7 +624,7 @@ func TestGetNodesByProperty(t *testing.T) { // GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created. // If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails. func TestGetNodeIDsByProperty(t *testing.T) { - adapter := adapters.NewSimAdapter(adapters.Services{ + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ "test": newTestService, }) network := NewNetwork(adapter, &NetworkConfig{ @@ -705,8 +705,8 @@ func benchmarkMinimalServiceTmp(b *testing.B) { // this is a minimal service, whose protocol will close a channel upon run of protocol // making it possible to bench the time it takes for the service to start and protocol actually to be run protoCMap := make(map[enode.ID]map[enode.ID]chan struct{}) - adapter := adapters.NewSimAdapter(adapters.Services{ - "noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) { + adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{ + "noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{}) svc := NewNoopService(protoCMap[ctx.Config.ID]) return svc, nil diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go index 687be6d0b8e3..0edb07b127f8 100644 --- a/p2p/simulations/test.go +++ b/p2p/simulations/test.go @@ -66,7 +66,7 @@ func (t *NoopService) APIs() []rpc.API { return []rpc.API{} } -func (t *NoopService) Start(server *p2p.Server) error { +func (t *NoopService) Start() error { return nil } From 3eb4fc8a97607bb4442efabd8ac2037d2229cd9d Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 5 Jun 2020 13:56:33 +0200 Subject: [PATCH 044/160] fixed mailserver tests --- whisper/mailserver/server_test.go | 35 ++++++++++++++++++++++++++++++- whisper/whisperv6/whisper_test.go | 3 ++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 4f80e550aa22..c9820f5974f7 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" + "github.com/ethereum/go-ethereum/node" "io/ioutil" "math/rand" "testing" @@ -89,7 +90,11 @@ func TestMailServer(t *testing.T) { } var server WMailServer - shh = whisper.New(&whisper.DefaultConfig) + + stack := newNode(t) + defer stack.Close() + + shh = getWhisperFromNode(stack, t) shh.RegisterServer(&server) err = server.Init(shh, dir, password, powRequirement) @@ -210,3 +215,31 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { } return env } + +// newNode creates a new node using a default config and +// creates and registers a new Whisper service on it. +func newNode(t *testing.T) *node.Node { + stack, err := node.New(&node.DefaultConfig) + if err != nil { + t.Fatalf("could not create new node: %v", err) + } + err = whisper.New(stack, &whisper.DefaultConfig) + if err != nil { + t.Fatalf("could not create new whisper service: %v", err) + } + err = stack.Start() + if err != nil { + t.Fatalf("could not start node: %v", err) + } + return stack +} + +// getWhisperFromNode retrieves the Whisper service from the running node. +func getWhisperFromNode(stack *node.Node, t *testing.T) *whisper.Whisper { + var w *whisper.Whisper + err := stack.Lifecycle(&w) + if err != nil { + t.Fatalf("could not get whisper service from node: %v", err) + } + return w +} diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index 351ed6403ebe..27873ca6917c 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -930,7 +930,8 @@ func TestBloom(t *testing.T) { } } -// newNode creates a new node using a default config. +// newNode creates a new node using a default config and +// creates and registers a new Whisper service on it. func newNode(t *testing.T) *node.Node { stack, err := node.New(&node.DefaultConfig) if err != nil { From 6e41e4143067d23d1365ddac7e055351007852ff Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 5 Jun 2020 14:06:28 +0200 Subject: [PATCH 045/160] les tests passing --- les/api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/les/api_test.go b/les/api_test.go index 72ed2f556ff3..1fcef8edacc2 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -492,14 +492,14 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] return test(ctx, net, servers, clients) } -func newLesClientService(stack *node.Node) (node.Lifecycle, error) { +func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { config := eth.DefaultConfig config.SyncMode = downloader.LightSync config.Ethash.PowMode = ethash.ModeFake return New(stack, &config) } -func newLesServerService(stack *node.Node) (node.Lifecycle, error) { +func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { config := eth.DefaultConfig config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity From 2df58f7250004a8eca9fc50da36eb67945b07b85 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 5 Jun 2020 14:54:59 +0200 Subject: [PATCH 046/160] changes after rebasing on master, likely bc of serverpool pr --- eth/backend.go | 2 +- les/client.go | 5 +---- node/node.go | 10 ++++------ 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 82e5fb6bcd74..46d5fedcbede 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -232,7 +232,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandiates, err = eth.setupDiscovery(&stack.ServiceContext.Config.P2P) + eth.dialCandidates, err = eth.setupDiscovery(&stack.ServiceContext.Config.P2P) if err != nil { return nil, err } diff --git a/les/client.go b/les/client.go index 58d5606ae903..c25b505c08e3 100644 --- a/les/client.go +++ b/les/client.go @@ -114,7 +114,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery(&ctx.Config.P2P) + dnsdisc, err := leth.setupDiscovery(&stack.ServiceContext.Config.P2P) if err != nil { return nil, err } @@ -304,9 +304,6 @@ func (s *LightEthereum) Start() error { s.netRPCService = ethapi.NewPublicNetAPI(s.p2pServer, s.config.NetworkId) - // clients are searching for the first advertised protocol in the list - protocolVersion := AdvertiseProtocolVersions[0] - s.serverPool.start(s.p2pServer, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion)) return nil } diff --git a/node/node.go b/node/node.go index e794d57721aa..692b10846ac2 100644 --- a/node/node.go +++ b/node/node.go @@ -17,6 +17,7 @@ package node import ( + "context" "errors" "fmt" "io" @@ -51,7 +52,7 @@ type Node struct { server *p2p.Server // Currently running P2P networking layer - ServiceContext *ServiceContext + ServiceContext *ServiceContext // TODO lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle @@ -109,7 +110,7 @@ func New(conf *Config) (*Node, error) { config: conf, lifecycles: make(map[reflect.Type]Lifecycle), ServiceContext: &ServiceContext{ - Config: *conf, + Config: *conf, Lifecycles: make(map[reflect.Type]Lifecycle), }, httpServerMap: make(map[string]*HTTPServer), @@ -392,10 +393,7 @@ func (n *Node) configureRPC() error { return err } // start HTTP server - if err := n.RegisterLifecycle(server); err != nil { - return err - } - n.log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", server.ListenerAddr)) + n.RegisterLifecycle(server) } // All API endpoints started successfully return nil From fd6623e10c338ed7807f8d8f64dbf915f0edaa9c Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 5 Jun 2020 15:27:00 +0200 Subject: [PATCH 047/160] fixed p2p testing package --- p2p/testing/protocoltester.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index b80abcc264f5..159cd7d16d5e 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -53,11 +53,11 @@ type ProtocolTester struct { // it takes as argument the pivot node id, the number of dummy peers and the // protocol run function called on a peer connection by the p2p server func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { - services := adapters.Services{ - "test": func(ctx *adapters.ServiceContext) (node.Service, error) { + services := adapters.LifecycleConstructors{ + "test": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return &testNode{run}, nil }, - "mock": func(ctx *adapters.ServiceContext) (node.Service, error) { + "mock": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { return newMockNode(), nil }, } @@ -66,7 +66,7 @@ func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Pe nodeConfig := &adapters.NodeConfig{ PrivateKey: prvkey, EnableMsgEvents: true, - Services: []string{"test"}, + Lifecycles: []string{"test"}, } if _, err := net.NewNodeWithConfig(nodeConfig); err != nil { panic(err.Error()) @@ -80,7 +80,7 @@ func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Pe nodes := make([]*enode.Node, nodeCount) for i := 0; i < nodeCount; i++ { peers[i] = adapters.RandomNodeConfig() - peers[i].Services = []string{"mock"} + peers[i].Lifecycles = []string{"mock"} if _, err := net.NewNodeWithConfig(peers[i]); err != nil { panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err)) } @@ -142,7 +142,7 @@ func (t *testNode) APIs() []rpc.API { return nil } -func (t *testNode) Start(server *p2p.Server) error { +func (t *testNode) Start() error { return nil } From ebde331dc90987adcec9ff791a71b7174f12d36f Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 08:51:53 +0200 Subject: [PATCH 048/160] removing unnecessary methods and fixing todos --- cmd/geth/main.go | 6 +++++- node/node.go | 10 +++++----- node/rpcstack.go | 17 ++++------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 5f71eb5324ca..dfc928f5946a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -365,6 +365,10 @@ func geth(ctx *cli.Context) error { func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum) { debug.Memsize.Add("node", stack) + if ethBackend == nil && lesBackend == nil { + utils.Fatalf("No backend service found") // TODO is this error okay? + } + // Start up the node itself utils.StartNode(stack) @@ -462,7 +466,7 @@ func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, les if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - // Check if node's backend is eth and that it exists // TODO fix this section up -- not sure if it's doing what it's supposed to. + // Check if node's backend is eth and that it exists if ethBackend == nil { utils.Fatalf("Ethereum service not running: backend is not an eth backend") } diff --git a/node/node.go b/node/node.go index 692b10846ac2..de74545281ec 100644 --- a/node/node.go +++ b/node/node.go @@ -423,16 +423,16 @@ func (n *Node) stopInProc() { // startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC() error { - if n.ipc.Endpoint() == "" { + if n.ipc.endpoint == "" { return nil // IPC disabled. } - listener, handler, err := rpc.StartIPCEndpoint(n.ipc.Endpoint(), n.rpcAPIs) + listener, handler, err := rpc.StartIPCEndpoint(n.ipc.endpoint, n.rpcAPIs) if err != nil { return err } n.ipc.Listener = listener n.ipc.handler = handler - n.log.Info("IPC endpoint opened", "url", n.ipc.Endpoint()) + n.log.Info("IPC endpoint opened", "url", n.ipc.endpoint) return nil } @@ -442,7 +442,7 @@ func (n *Node) stopIPC() { n.ipc.Listener.Close() n.ipc.Listener = nil - n.log.Info("IPC endpoint closed", "url", n.ipc.Endpoint()) + n.log.Info("IPC endpoint closed", "url", n.ipc.endpoint) } if n.ipc.Srv != nil { n.ipc.Srv.Stop() @@ -589,7 +589,7 @@ func (n *Node) AccountManager() *accounts.Manager { // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { - return n.ipc.Endpoint() + return n.ipc.endpoint } // HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. diff --git a/node/rpcstack.go b/node/rpcstack.go index 3b439134a64d..1020a0d043bd 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -57,13 +57,14 @@ type HTTPServer struct { GQLHandler http.Handler } -// TODO document +// Start starts the HTTPServer's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } +// Stop shuts down the HTTPServer's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Stop() error { if h.Server != nil { url := fmt.Sprintf("http://%v/", h.Listener.Addr()) @@ -79,22 +80,12 @@ func (h *HTTPServer) Stop() error { return nil } -// Handler returns the handler of the HTTPServer -func (h *HTTPServer) Handler() http.Handler { - return h.handler -} - -// TODO document +// SetHandler assigns the given handler to the HTTPServer. func (h *HTTPServer) SetHandler(handler http.Handler) { h.handler = handler } -// TODO is this really necessary? -func (h *HTTPServer) Endpoint() string { - return h.endpoint -} - -// TODO is this necessary? +// SetEndpoints assigns the given endpoint to the HTTPServer. func (h *HTTPServer) SetEndpoint(endpoint string) { h.endpoint = endpoint } From 06bf009fb97913102f0995328e69496b7a4d4638 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:00:52 +0200 Subject: [PATCH 049/160] removing more unnecessary methods --- node/node.go | 60 ++++++++++++++++++---------------------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/node/node.go b/node/node.go index de74545281ec..ccb80abf7606 100644 --- a/node/node.go +++ b/node/node.go @@ -52,7 +52,7 @@ type Node struct { server *p2p.Server // Currently running P2P networking layer - ServiceContext *ServiceContext // TODO + ServiceContext *ServiceContext // TODO rename to LifecycleContext or just NodeContext? lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle @@ -592,25 +592,7 @@ func (n *Node) IPCEndpoint() string { return n.ipc.endpoint } -// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack. -func (n *Node) HTTPEndpoint() string { - n.lock.Lock() - defer n.lock.Unlock() - - for _, httpServer := range n.httpServerMap { - if httpServer.RPCAllowed { - if httpServer.Listener != nil { - return httpServer.Listener.Addr().String() - } - return httpServer.endpoint - } - } - - return "" // TODO should return an empty string if http server not configured? -} - -// WSEndpoint retrieves the current WS endpoint -// used by the protocol stack. +// WSEndpoint retrieves the current WS endpoint used by the protocol stack. func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() @@ -624,7 +606,7 @@ func (n *Node) WSEndpoint() string { } } - return "" // TODO should return an empty string if ws server not configured? + return n.config.WSEndpoint() // TODO should it return the endpoint from the node's config? Or just an empty string? } // EventMux retrieves the event multiplexer used by all the network services in @@ -633,6 +615,24 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } +// Lifecycle retrieves a currently running Lifecycle registered of a specific type. +func (n *Node) Lifecycle(lifecycle interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if !n.running() { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(lifecycle).Elem() + if running, ok := n.lifecycles[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + // OpenDatabase opens an existing database with the given name (or creates one if no // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. @@ -668,24 +668,6 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } -// Lifecycle retrieves a currently running Lifecycle registered of a specific type. -func (n *Node) Lifecycle(lifecycle interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if !n.running() { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(lifecycle).Elem() - if running, ok := n.lifecycles[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ From 4ce4c4097240952017cefc51593f564430a06fe2 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 14:23:57 +0200 Subject: [PATCH 050/160] instead of passing both les and eth explicitly, fetch backend from node --- cmd/geth/chaincmd.go | 14 +++++++------- cmd/geth/config.go | 11 +++++------ cmd/geth/consolecmd.go | 30 ++++++++++++++++++++++-------- cmd/geth/main.go | 14 ++++++++++---- cmd/utils/flags.go | 28 +++++++++++++++++++--------- ethstats/ethstats.go | 25 +++++++++++++++---------- graphql/graphql_test.go | 2 +- graphql/service.go | 15 ++++----------- node/node.go | 4 ++-- 9 files changed, 85 insertions(+), 58 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 07485306492e..247c202bca17 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -240,7 +240,7 @@ func initGenesis(ctx *cli.Context) error { utils.Fatalf("invalid genesis file: %v", err) } // Open an initialise both full and light databases - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { @@ -277,7 +277,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -371,7 +371,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -406,7 +406,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -424,7 +424,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -446,7 +446,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -554,7 +554,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack, _, _ := makeFullNode(ctx) + stack := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index df452b8b4fd8..60d9d0006689 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,7 +20,6 @@ import ( "bufio" "errors" "fmt" - "github.com/ethereum/go-ethereum/les" "os" "reflect" "unicode" @@ -145,9 +144,9 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context) (*node.Node, *eth.Ethereum, *les.LightEthereum) { +func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - ethBackend, lesBackend := utils.RegisterEthService(stack, &cfg.Eth) + utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode shhEnabled := enableWhisper(ctx) @@ -166,14 +165,14 @@ func makeFullNode(ctx *cli.Context) (*node.Node, *eth.Ethereum, *les.LightEthere } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, ethBackend, lesBackend, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { - utils.RegisterEthStatsService(stack, ethBackend, lesBackend, cfg.Ethstats.URL) + utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) } - return stack, ethBackend, lesBackend + return stack } // dumpConfig is the dumpconfig command. diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 226a2537bac4..7df26ff20f3b 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -18,6 +18,8 @@ package main import ( "fmt" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les" "os" "os/signal" "path/filepath" @@ -78,12 +80,18 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - node, ethBackend, lesBackend := makeFullNode(ctx) - startNode(ctx, node, ethBackend, lesBackend) - defer node.Close() + stack := makeFullNode(ctx) + // fetch backends + var ethBackend *eth.Ethereum + stack.Lifecycle(ðBackend) + var lesBackend *les.LightEthereum + stack.Lifecycle(&lesBackend) + + startNode(ctx, stack, ethBackend, lesBackend) + defer stack.Close() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() + client, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } @@ -190,12 +198,18 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - node, ethBackend, lesBackend := makeFullNode(ctx) - startNode(ctx, node, ethBackend, lesBackend) - defer node.Close() + stack := makeFullNode(ctx) + // fetch backends + var ethBackend *eth.Ethereum + stack.Lifecycle(ðBackend) + var lesBackend *les.LightEthereum + stack.Lifecycle(&lesBackend) + + startNode(ctx, stack, ethBackend, lesBackend) + defer stack.Close() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() + client, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index dfc928f5946a..4cd0b53bcb69 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -351,11 +351,17 @@ func geth(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } prepare(ctx) - node, ethBackend, lesBackend := makeFullNode(ctx) - defer node.Close() - startNode(ctx, node, ethBackend, lesBackend) + stack := makeFullNode(ctx) + // fetch backends + var ethBackend *eth.Ethereum + stack.ServiceContext.Lifecycle(ðBackend) + var lesBackend *les.LightEthereum + stack.ServiceContext.Lifecycle(&lesBackend) - node.Wait() + defer stack.Close() + startNode(ctx, stack, ethBackend, lesBackend) + + stack.Wait() return nil } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 25faf1b91ec2..e88d6a5b4300 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,6 +20,7 @@ package utils import ( "crypto/ecdsa" "fmt" + "github.com/ethereum/go-ethereum/internal/ethapi" "io" "io/ioutil" "math/big" @@ -1691,13 +1692,12 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) (*eth.Ethereum, *les.LightEthereum) { +func RegisterEthService(stack *node.Node, cfg *eth.Config) { if cfg.SyncMode == downloader.LightSync { - backend, err := les.New(stack, cfg) + _, err := les.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %w", err) } - return nil, backend } else { backend, err := eth.New(stack, cfg) if err != nil { @@ -1707,7 +1707,6 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) (*eth.Ethereum, *les. ls, _ := les.NewLesServer(backend, cfg) backend.AddLesServer(ls) } - return backend, nil } } @@ -1720,16 +1719,27 @@ func RegisterShhService(stack *node.Node, cfg *whisper.Config) { // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. -func RegisterEthStatsService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, url string) { - if err := ethstats.New(stack, ethBackend, lesBackend, url); err != nil { +func RegisterEthStatsService(stack *node.Node, url string) { + if err := ethstats.New(stack, url); err != nil { Fatalf("Failed to register the Ethereum Stats service: %w", err) } } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - if err := graphql.New(stack, ethBackend, lesBackend, endpoint, cors, vhosts, timeouts); err != nil { - Fatalf("Failed to register the GraphQL service: %w", err) +func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { + var backend ethapi.Backend + // fetch backend + var ethServ *eth.Ethereum + if err := stack.ServiceContext.Lifecycle(ðServ); err == nil { + backend = ethServ.APIBackend + } + var lesServ *les.LightEthereum + if err := stack.ServiceContext.Lifecycle(&lesServ); err == nil { + backend = lesServ.ApiBackend + } + // create new graphQL service + if err := graphql.New(stack, backend, endpoint, cors, vhosts, timeouts); err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) } } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 594c844d1e59..72c63cf33e49 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -83,31 +83,36 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, url string) error { // TODO, this thing receives the backend explicitly, does whatever, registers itself +func New(node *node.Node, url string) error { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) if len(parts) != 5 { return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) } - - // fetch type of Backend - if ethBackend == nil && lesBackend == nil { - return errors.New("no Ethereum service") // TODO is this okay to return? - } ethstats := &Service{ server: node.Server(), - eth: ethBackend, - les: lesBackend, - engine: ethBackend.Engine(), node: parts[1], pass: parts[3], host: parts[4], pongCh: make(chan struct{}), histCh: make(chan []uint64, 1), } - node.RegisterLifecycle(ethstats) + // fetch backend + var ethServ *eth.Ethereum + if err := node.ServiceContext.Lifecycle(ðServ); err == nil { + ethstats.eth = ethServ + ethstats.engine = ethServ.Engine() + } + var lesServ *les.LightEthereum + if err := node.ServiceContext.Lifecycle(&lesServ); err == nil { + ethstats.les = lesServ + ethstats.engine = lesServ.Engine() + } + // TODO check to make sure at least one backend is not nil? + + node.RegisterLifecycle(ethstats) return nil } diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 66a589829dfa..3a56f9556f88 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -148,7 +148,7 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { } // create gql service - err = New(stack,ethBackend,nil, testEndpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, ethBackend.APIBackend, testEndpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 58897c9ffa24..52c8ab1a7bc8 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,8 +18,6 @@ package graphql import ( "errors" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "net/http" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -36,17 +34,12 @@ type Service struct { } // New constructs a new GraphQL service instance. -func New(stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { +func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { service := new(Service) - // add backend - if ethBackend != nil { - service.backend = ethBackend.APIBackend - } else if lesBackend != nil { - service.backend = lesBackend.ApiBackend - } else { - return errors.New("no Ethereum service") + if backend == nil { + return errors.New("No backend found") // TODO should this be a fatal error? } - + service.backend = backend // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) if server != nil { diff --git a/node/node.go b/node/node.go index ccb80abf7606..b6fdf7404292 100644 --- a/node/node.go +++ b/node/node.go @@ -208,6 +208,7 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { } n.lifecycles[kind] = lifecycle + n.ServiceContext.Lifecycles[kind] = lifecycle } // RegisterProtocols adds backend's protocols to the node's p2p server @@ -305,7 +306,6 @@ func (n *Node) Start() error { return err } started = append(started, lifecycle) - n.ServiceContext.Lifecycles[reflect.TypeOf(lifecycle)] = lifecycle } // Finish initializing the service context @@ -620,7 +620,7 @@ func (n *Node) Lifecycle(lifecycle interface{}) error { n.lock.RLock() defer n.lock.RUnlock() - // Short circuit if the node's not running + // Short circuit if the node's not running // TODO can i ignore this? if !n.running() { return ErrNodeStopped } From cc3daf688fafec04bd10da20d3ed102e8bc28875 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:19:43 +0200 Subject: [PATCH 051/160] typo --- cmd/geth/consolecmd.go | 4 ++-- cmd/geth/main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 7df26ff20f3b..1e06b4a8b3ca 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -81,7 +81,7 @@ func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) stack := makeFullNode(ctx) - // fetch backends + // fetch backend var ethBackend *eth.Ethereum stack.Lifecycle(ðBackend) var lesBackend *les.LightEthereum @@ -199,7 +199,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) { func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags stack := makeFullNode(ctx) - // fetch backends + // fetch backend var ethBackend *eth.Ethereum stack.Lifecycle(ðBackend) var lesBackend *les.LightEthereum diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 4cd0b53bcb69..8cfbc4de310e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -352,7 +352,7 @@ func geth(ctx *cli.Context) error { } prepare(ctx) stack := makeFullNode(ctx) - // fetch backends + // fetch backend var ethBackend *eth.Ethereum stack.ServiceContext.Lifecycle(ðBackend) var lesBackend *les.LightEthereum From 3f132b5d8695223594ceff2b008c139efc2684b2 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 15:28:38 +0200 Subject: [PATCH 052/160] fetch backend from service context rather than node since node isnt started yet --- cmd/geth/consolecmd.go | 8 ++++---- miner/stress_clique.go | 2 +- miner/stress_ethash.go | 2 +- node/node.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 1e06b4a8b3ca..464a6cc7732e 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -83,9 +83,9 @@ func localConsole(ctx *cli.Context) error { stack := makeFullNode(ctx) // fetch backend var ethBackend *eth.Ethereum - stack.Lifecycle(ðBackend) + stack.ServiceContext.Lifecycle(ðBackend) var lesBackend *les.LightEthereum - stack.Lifecycle(&lesBackend) + stack.ServiceContext.Lifecycle(&lesBackend) startNode(ctx, stack, ethBackend, lesBackend) defer stack.Close() @@ -201,9 +201,9 @@ func ephemeralConsole(ctx *cli.Context) error { stack := makeFullNode(ctx) // fetch backend var ethBackend *eth.Ethereum - stack.Lifecycle(ðBackend) + stack.ServiceContext.Lifecycle(ðBackend) var lesBackend *les.LightEthereum - stack.Lifecycle(&lesBackend) + stack.ServiceContext.Lifecycle(&lesBackend) startNode(ctx, stack, ethBackend, lesBackend) defer stack.Close() diff --git a/miner/stress_clique.go b/miner/stress_clique.go index a3a63e38c45a..a69dfbd69d9d 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -96,7 +96,7 @@ func main() { for _, node := range nodes { var ethereum *eth.Ethereum - if err := node.Lifecycle(ðereum); err != nil { // TODO does this work? + if err := node.ServiceContext.Lifecycle(ðereum); err != nil { panic(err) } if err := ethereum.StartMining(1); err != nil { diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index ed999f90cec4..1cb5396ce323 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -94,7 +94,7 @@ func main() { for _, node := range nodes { var ethereum *eth.Ethereum - if err := node.Lifecycle(ðereum); err != nil { // TODO does this work? + if err := node.ServiceContext.Lifecycle(ðereum); err != nil { panic(err) } if err := ethereum.StartMining(1); err != nil { diff --git a/node/node.go b/node/node.go index b6fdf7404292..f5716f56916b 100644 --- a/node/node.go +++ b/node/node.go @@ -620,7 +620,7 @@ func (n *Node) Lifecycle(lifecycle interface{}) error { n.lock.RLock() defer n.lock.RUnlock() - // Short circuit if the node's not running // TODO can i ignore this? + // Short circuit if the node's not running if !n.running() { return ErrNodeStopped } From 306f390247d773e7e83cea1bd394c01b59ea628b Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 16:07:55 +0200 Subject: [PATCH 053/160] fixing some broken tests --- cmd/faucet/faucet.go | 4 ++-- mobile/geth.go | 4 ++-- node/service_test.go | 14 +++----------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 7f66eae87fe7..6f6cb39069f0 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,14 +241,14 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - lesBackend, err := les.New(stack, &cfg) + _, err = les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := ethstats.New(stack, nil, lesBackend, stats); err != nil { + if err := ethstats.New(stack, stats); err != nil { return nil, err } } diff --git a/mobile/geth.go b/mobile/geth.go index d93b9f1cd0cc..053c304f6738 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -175,13 +175,13 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - lesBackend, err := les.New(rawStack, ðConf) + _, err = les.New(rawStack, ðConf) if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } // If netstats reporting is requested, do it if config.EthereumNetStats != "" { - if err := ethstats.New(rawStack, nil, lesBackend, config.EthereumNetStats); err != nil { + if err := ethstats.New(rawStack, config.EthereumNetStats); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } } diff --git a/node/service_test.go b/node/service_test.go index 183ca4915330..80f40aeeb68b 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -72,25 +72,17 @@ func TestContextLifecycles(t *testing.T) { noop := NewNoop() stack.RegisterLifecycle(noop) - isC, err := NewInstrumentedService() - if err != nil { - t.Fatalf("could not create instrumented service %v", err) - } - - isB, err := NewInstrumentedService() + is, err := NewInstrumentedService() if err != nil { t.Fatalf("could not create instrumented service %v", err) } - isB.startHook = func() { + is.startHook = func() { if err := stack.ServiceContext.Lifecycle(&noop); err != nil { t.Errorf("former service not found: %v", err) } - if err := stack.ServiceContext.Lifecycle(&isC); err != ErrServiceUnknown { - t.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown) - } } - stack.RegisterLifecycle(isB) + stack.RegisterLifecycle(is) // Start the protocol stack and ensure services are constructed in order if err := stack.Start(); err != nil { From fddc6bd4228578b5a3d344b72284d1aae03c60ae Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 8 Jun 2020 17:40:16 +0200 Subject: [PATCH 054/160] linted --- cmd/geth/consolecmd.go | 4 ++-- cmd/utils/flags.go | 2 +- cmd/wnode/main.go | 2 +- console/console_test.go | 1 - ethstats/ethstats.go | 2 +- graphql/graphql_test.go | 24 ++++++++++++----------- graphql/service.go | 12 ++++++------ les/client.go | 3 +-- miner/miner.go | 2 +- node/api.go | 27 +++++++++++++------------- node/endpoints.go | 15 --------------- node/node_example_test.go | 4 ++-- node/node_test.go | 32 +++++++++++++++---------------- node/rpcstack.go | 6 +++--- node/rpcstack_test.go | 10 +++++----- node/service.go | 6 +++--- node/utils_test.go | 19 +++++++++--------- p2p/testing/protocoltester.go | 2 +- whisper/mailserver/server_test.go | 2 +- whisper/whisperv6/whisper.go | 2 +- whisper/whisperv6/whisper_test.go | 6 +----- 21 files changed, 81 insertions(+), 102 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 464a6cc7732e..c35863a1e157 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -18,8 +18,6 @@ package main import ( "fmt" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "os" "os/signal" "path/filepath" @@ -28,6 +26,8 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "gopkg.in/urfave/cli.v1" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e88d6a5b4300..fa7b13af45ba 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,7 +20,6 @@ package utils import ( "crypto/ecdsa" "fmt" - "github.com/ethereum/go-ethereum/internal/ethapi" "io" "io/ioutil" "math/big" @@ -50,6 +49,7 @@ import ( "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index f20c00ceb183..bdb0d306b760 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -770,4 +770,4 @@ func obfuscateBloom(bloom []byte) { bloom[x/8] = 1 << uint(x%8) // set the bit number X } -} \ No newline at end of file +} diff --git a/console/console_test.go b/console/console_test.go index c6fd90be7f14..80a2253997c0 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -114,7 +114,6 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { t.Fatalf("failed to register Ethereum protocol: %v", err) } - // Start the node and assemble the JavaScript console around it if err = stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 72c63cf33e49..e40e973cbb13 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -22,7 +22,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/node" "math/big" "net/http" "regexp" @@ -40,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/gorilla/websocket" ) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 3a56f9556f88..29b8c469cebf 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -18,14 +18,16 @@ package graphql import ( "fmt" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "strings" "testing" + + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/stretchr/testify/assert" ) var testEndpoint = "127.0.0.1:9393" @@ -73,7 +75,7 @@ func TestMultiplexedServer(t *testing.T) { // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { - stack := createNode(t,true) + stack := createNode(t, true) defer stack.Close() // start node if err := stack.Start(); err != nil { @@ -93,7 +95,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { t.Fatalf("could not read from response body: %v", err) } expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" - assert.Equal(t, expected,string(bodyBytes)) + assert.Equal(t, expected, string(bodyBytes)) } // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint @@ -130,10 +132,10 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ - HTTPHost: "127.0.0.1", - HTTPPort: 9393, - WSHost: "127.0.0.1", - WSPort: 9393, + HTTPHost: "127.0.0.1", + HTTPPort: 9393, + WSHost: "127.0.0.1", + WSPort: 9393, }) if err != nil { t.Fatalf("could not create node: %v", err) @@ -142,7 +144,7 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { return stack } // create backend - ethBackend, err := eth.New(stack, ð.DefaultConfig) + ethBackend, err := eth.New(stack, ð.DefaultConfig) if err != nil { t.Fatalf("could not create eth backend: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 52c8ab1a7bc8..eb62f2f0695a 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -29,7 +29,7 @@ import ( // Service encapsulates a GraphQL service. type Service struct { - backend ethapi.Backend // The backend that queries will operate on. + backend ethapi.Backend // The backend that queries will operate on. graphqlServer *node.HTTPServer } @@ -63,12 +63,12 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts } // create the http server gqlServer := &node.HTTPServer{ - Vhosts: vhosts, + Vhosts: vhosts, CorsAllowedOrigins: cors, - Timeouts: timeouts, - GQLAllowed: true, - GQLHandler: handler, - Srv: rpc.NewServer(), + Timeouts: timeouts, + GQLAllowed: true, + GQLHandler: handler, + Srv: rpc.NewServer(), } gqlServer.SetEndpoint(endpoint) stack.RegisterHTTPServer(endpoint, gqlServer) diff --git a/les/client.go b/les/client.go index c25b505c08e3..dd62aa82bcb7 100644 --- a/les/client.go +++ b/les/client.go @@ -110,7 +110,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), - p2pServer: stack.Server(), + p2pServer: stack.Server(), } peers.subscribe((*vtSubscription)(leth.valueTracker)) @@ -171,7 +171,6 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) - // Register the backend on the node stack.RegisterAPIs(leth.APIs()) if err := stack.RegisterProtocols(leth.Protocols()); err != nil { diff --git a/miner/miner.go b/miner/miner.go index d7d5872dba2f..5249118cae14 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -36,7 +36,7 @@ import ( ) // Backend wraps all methods required for mining. -type Backend interface { +type Backend interface { BlockChain() *core.BlockChain TxPool() *core.TxPool } diff --git a/node/api.go b/node/api.go index 3badf6209073..0f21156e75bd 100644 --- a/node/api.go +++ b/node/api.go @@ -190,13 +190,13 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } // configure http server httpServer := &HTTPServer{ - host: *host, - port: *port, - endpoint: endpoint, - Srv: rpc.NewServer(), + host: *host, + port: *port, + endpoint: endpoint, + Srv: rpc.NewServer(), CorsAllowedOrigins: allowedOrigins, - Vhosts: allowedVHosts, - Whitelist: modules, + Vhosts: allowedVHosts, + Whitelist: modules, } // create handler httpServer.handler = NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) @@ -269,7 +269,6 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str return true, nil } - origins := api.node.config.WSOrigins if allowedOrigins != nil { origins = nil @@ -294,13 +293,13 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } wsServer := &HTTPServer{ - Srv: rpc.NewServer(), - endpoint: endpoint, - host: *host, - port: *port, - Whitelist: modules, - WsOrigins: origins, - WSAllowed: true, + Srv: rpc.NewServer(), + endpoint: endpoint, + host: *host, + port: *port, + Whitelist: modules, + WsOrigins: origins, + WSAllowed: true, } wsServer.handler = wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) diff --git a/node/endpoints.go b/node/endpoints.go index 1baa1b5c417f..1f85a5213168 100644 --- a/node/endpoints.go +++ b/node/endpoints.go @@ -48,21 +48,6 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http. return httpSrv, listener.Addr(), err } -// startWSEndpoint starts a websocket endpoint. -func startWSEndpoint(endpoint string, handler http.Handler) (*http.Server, net.Addr, error) { - // start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, nil, err - } - wsSrv := &http.Server{Handler: handler} - go wsSrv.Serve(listener) - return wsSrv, listener.Addr(), err -} - // checkModuleAvailability checks that all names given in modules are actually // available API services. It assumes that the MetadataApi module ("rpc") is always available; // the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. diff --git a/node/node_example_test.go b/node/node_example_test.go index 9363bbc18379..6ad74aaca885 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -31,8 +31,8 @@ import ( // - Stop() error - method invoked when the node terminates the service type SampleLifecycle struct{} -func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil } -func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil } +func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil } +func (s *SampleLifecycle) Stop() error { fmt.Println("Service stopping..."); return nil } func ExampleLifecycle() { // Create a network node to run protocols with the default values. diff --git a/node/node_test.go b/node/node_test.go index 337b55525b6e..e8ac35318a59 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -132,23 +132,23 @@ func TestLifecycleLifeCycle(t *testing.T) { stopped := make(map[string]bool) // Create a batch of instrumented services - lifecycles := map[string]Lifecycle{ + lifecycles := map[string]Lifecycle{ "A": &InstrumentedServiceA{ InstrumentedService{ startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, }, "B": &InstrumentedServiceB{ InstrumentedService{ startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, }, "C": &InstrumentedServiceC{ InstrumentedService{ startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, }, } @@ -192,23 +192,23 @@ func TestLifecycleStartupAbortion(t *testing.T) { stopped := make(map[string]bool) // Create a batch of instrumented services - lifecycles := map[string]Lifecycle{ + lifecycles := map[string]Lifecycle{ "A": &InstrumentedServiceA{ InstrumentedService{ startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, }, "B": &InstrumentedServiceB{ InstrumentedService{ startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, }, "C": &InstrumentedServiceC{ InstrumentedService{ startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, }, } @@ -219,7 +219,7 @@ func TestLifecycleStartupAbortion(t *testing.T) { // Register a service that fails to construct itself failure := errors.New("fail") - failer := &InstrumentedService{ start: failure } + failer := &InstrumentedService{start: failure} stack.RegisterLifecycle(failer) // Start the protocol stack and ensure all started services stop @@ -250,23 +250,23 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stopped := make(map[string]bool) // Create a batch of instrumented services - lifecycles := map[string]Lifecycle{ + lifecycles := map[string]Lifecycle{ "A": &InstrumentedServiceA{ InstrumentedService{ startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, }, "B": &InstrumentedServiceB{ InstrumentedService{ startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, }, "C": &InstrumentedServiceC{ InstrumentedService{ startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, }, } @@ -277,7 +277,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { // Register a service that fails to shot down cleanly failure := errors.New("fail") - failer := &InstrumentedService{ stop: failure } + failer := &InstrumentedService{stop: failure} stack.RegisterLifecycle(failer) // Start the protocol stack, and ensure that a failing shut down terminates all // TODO, deleting loop because constructors no longer stored on node. @@ -396,8 +396,8 @@ func startHTTP(t *testing.T) *Node { conf := &Config{ HTTPHost: "127.0.0.1", HTTPPort: 7453, - WSHost: "127.0.0.1", - WSPort: 7453, + WSHost: "127.0.0.1", + WSPort: 7453, } node, err := New(conf) if err != nil { diff --git a/node/rpcstack.go b/node/rpcstack.go index 1020a0d043bd..d5b5168e9bdd 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -20,7 +20,6 @@ import ( "compress/gzip" "context" "fmt" - "github.com/ethereum/go-ethereum/rpc" "io" "io/ioutil" "net" @@ -29,15 +28,16 @@ import ( "sync" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/rs/cors" ) type HTTPServer struct { handler http.Handler Srv *rpc.Server - Server *http.Server + Server *http.Server - Listener net.Listener + Listener net.Listener endpoint string host string diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 364bceb54094..799b138a6917 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -12,7 +12,7 @@ import ( func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { h := &HTTPServer{ - Srv: rpc.NewServer(), + Srv: rpc.NewServer(), WSAllowed: true, } handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) @@ -43,10 +43,10 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { // Tests that a ws handler can be added to and enabled on an existing HTTPServer func TestWSAllowed(t *testing.T) { stack, err := New(&Config{ - HTTPHost: DefaultHTTPHost, - HTTPPort: 9393, - WSHost: DefaultHTTPHost, - WSPort: 9393, + HTTPHost: DefaultHTTPHost, + HTTPPort: 9393, + WSHost: DefaultHTTPHost, + WSPort: 9393, }) if err != nil { t.Fatalf("could not create node: %v", err) diff --git a/node/service.go b/node/service.go index 53ed61017f39..072933fe6050 100644 --- a/node/service.go +++ b/node/service.go @@ -31,9 +31,9 @@ import ( // as well as utility methods to operate on the service environment. type ServiceContext struct { Config Config - Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? - EventMux *event.TypeMux // Event multiplexer used for decoupled notifications - AccountManager *accounts.Manager // Account manager created by the node. + Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? + EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + AccountManager *accounts.Manager // Account manager created by the node. } // OpenDatabase opens an existing database with the given name (or creates one diff --git a/node/utils_test.go b/node/utils_test.go index 0755e0411171..17c45a238fd6 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -24,8 +24,8 @@ import "github.com/ethereum/go-ethereum/p2p" // NoopLifecycle is a trivial implementation of the Service interface. type NoopLifecycle struct{} -func (s *NoopLifecycle) Start() error { return nil } -func (s *NoopLifecycle) Stop() error { return nil } +func (s *NoopLifecycle) Start() error { return nil } +func (s *NoopLifecycle) Stop() error { return nil } func NewNoop() *Noop { noop := new(Noop) @@ -36,22 +36,21 @@ func NewNoop() *Noop { // signatures but different outer types. type Noop struct{ NoopLifecycle } - // InstrumentedService is an implementation of Lifecycle for which all interface // methods can be instrumented both return value as well as event hook wise. type InstrumentedService struct { - start error - stop error + start error + stop error - startHook func() - stopHook func() + startHook func() + stopHook func() protocols []p2p.Protocol } -type InstrumentedServiceA struct { InstrumentedService } -type InstrumentedServiceB struct { InstrumentedService } -type InstrumentedServiceC struct { InstrumentedService } +type InstrumentedServiceA struct{ InstrumentedService } +type InstrumentedServiceB struct{ InstrumentedService } +type InstrumentedServiceC struct{ InstrumentedService } func NewInstrumentedService() (*InstrumentedService, error) { return new(InstrumentedService), nil diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index 159cd7d16d5e..9dd53e71226a 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -66,7 +66,7 @@ func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Pe nodeConfig := &adapters.NodeConfig{ PrivateKey: prvkey, EnableMsgEvents: true, - Lifecycles: []string{"test"}, + Lifecycles: []string{"test"}, } if _, err := net.NewNodeWithConfig(nodeConfig); err != nil { panic(err.Error()) diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index c9820f5974f7..959a1e32f629 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -20,7 +20,6 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" - "github.com/ethereum/go-ethereum/node" "io/ioutil" "math/rand" "testing" @@ -28,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/node" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" ) diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 3ded51010f63..a58e1710c9f6 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -21,7 +21,6 @@ import ( "crypto/ecdsa" "crypto/sha256" "fmt" - "github.com/ethereum/go-ethereum/node" "math" "runtime" "sync" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index 27873ca6917c..a8056d6776e1 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -20,12 +20,12 @@ import ( "bytes" "crypto/ecdsa" "crypto/sha256" - "github.com/ethereum/go-ethereum/node" mrand "math/rand" "testing" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/node" "golang.org/x/crypto/pbkdf2" ) @@ -538,7 +538,6 @@ func TestCustomization(t *testing.T) { defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() - const smallPoW = 0.00001 @@ -635,7 +634,6 @@ func TestSymmetricSendCycle(t *testing.T) { defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() - filter1, err := generateFilter(t, true) if err != nil { @@ -730,7 +728,6 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() - filter, err := generateFilter(t, true) if err != nil { @@ -804,7 +801,6 @@ func TestSymmetricSendKeyMismatch(t *testing.T) { defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() - filter, err := generateFilter(t, true) if err != nil { From 6c91bb43f7d9ebfd4cea6de89a874cb35b274a3e Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 9 Jun 2020 13:00:15 +0200 Subject: [PATCH 055/160] not passing backends to startnode --- cmd/geth/consolecmd.go | 18 ++---------------- cmd/geth/main.go | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index c35863a1e157..5a88414a4230 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -26,8 +26,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "gopkg.in/urfave/cli.v1" @@ -81,13 +79,7 @@ func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) stack := makeFullNode(ctx) - // fetch backend - var ethBackend *eth.Ethereum - stack.ServiceContext.Lifecycle(ðBackend) - var lesBackend *les.LightEthereum - stack.ServiceContext.Lifecycle(&lesBackend) - - startNode(ctx, stack, ethBackend, lesBackend) + startNode(ctx, stack) defer stack.Close() // Attach to the newly started node and start the JavaScript console @@ -199,13 +191,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) { func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags stack := makeFullNode(ctx) - // fetch backend - var ethBackend *eth.Ethereum - stack.ServiceContext.Lifecycle(ðBackend) - var lesBackend *les.LightEthereum - stack.ServiceContext.Lifecycle(&lesBackend) - - startNode(ctx, stack, ethBackend, lesBackend) + startNode(ctx, stack) defer stack.Close() // Attach to the newly started node and start the JavaScript console diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 8cfbc4de310e..03c2755807a9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -352,14 +352,9 @@ func geth(ctx *cli.Context) error { } prepare(ctx) stack := makeFullNode(ctx) - // fetch backend - var ethBackend *eth.Ethereum - stack.ServiceContext.Lifecycle(ðBackend) - var lesBackend *les.LightEthereum - stack.ServiceContext.Lifecycle(&lesBackend) defer stack.Close() - startNode(ctx, stack, ethBackend, lesBackend) + startNode(ctx, stack) stack.Wait() return nil @@ -368,13 +363,9 @@ func geth(ctx *cli.Context) error { // startNode boots up the system node and all registered protocols, after which // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. -func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, lesBackend *les.LightEthereum) { +func startNode(ctx *cli.Context, stack *node.Node) { debug.Memsize.Add("node", stack) - if ethBackend == nil && lesBackend == nil { - utils.Fatalf("No backend service found") // TODO is this error okay? - } - // Start up the node itself utils.StartNode(stack) @@ -395,7 +386,8 @@ func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, les // Set contract backend for ethereum service if local node // is serving LES requests. if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { - if ethBackend == nil { + var ethBackend *eth.Ethereum + if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { utils.Fatalf("Failed to retrieve ethereum service: %v", err) } ethBackend.SetContractBackend(ethClient) @@ -403,7 +395,8 @@ func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, les // Set contract backend for les service if local node is // running as a light client. if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { - if lesBackend == nil { + var lesBackend *les.LightEthereum + if err := stack.ServiceContext.Lifecycle(&lesBackend); err != nil { utils.Fatalf("Failed to retrieve light ethereum service: %v", err) } lesBackend.SetContractBackend(ethClient) @@ -473,7 +466,8 @@ func startNode(ctx *cli.Context, stack *node.Node, ethBackend *eth.Ethereum, les utils.Fatalf("Light clients do not support mining") } // Check if node's backend is eth and that it exists - if ethBackend == nil { + var ethBackend *eth.Ethereum + if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { utils.Fatalf("Ethereum service not running: backend is not an eth backend") } // Set the gas price to the limits from the CLI and start mining From 5d67845206ed4e501ab732dc618969c7f5b5beb9 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 9 Jun 2020 14:56:16 +0200 Subject: [PATCH 056/160] fixing docs --- eth/backend.go | 4 ++-- les/client.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 46d5fedcbede..56f605bc98c3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -529,7 +529,7 @@ func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManage func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } -// Protocols implements node.Backend, returning all the currently configured +// Protocols returns all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { protos := make([]p2p.Protocol, len(ProtocolVersions)) @@ -544,7 +544,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { return protos } -// P2PServer implements node.Backend, registering the node's running p2p server with the Backend. +// P2PServer registers the node's running p2p server with the Backend. func (s *Ethereum) P2PServer(server *p2p.Server) error { if server == nil { return errors.New("p2p server is not running, cannot register with eth backend") // TODO is this error message okay? diff --git a/les/client.go b/les/client.go index dd62aa82bcb7..286fab7b2ede 100644 --- a/les/client.go +++ b/les/client.go @@ -281,7 +281,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { }, s.dialCandidates) } -// P2PServer implements node.Backend, registering the node's running p2p server with the Backend. +// P2PServer registers the node's running p2p server with the Backend. func (s *LightEthereum) P2PServer(server *p2p.Server) error { if server == nil { return errors.New("p2p server is not running, cannot register with les backend") // TODO is this error message okay? From b8c5dc484ec54c5bb8850b5835449ceaae78088a Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Jun 2020 10:05:09 +0200 Subject: [PATCH 057/160] fixed dao_test --- cmd/geth/chaincmd.go | 15 ++++++++------- cmd/geth/config.go | 7 ++++++- cmd/geth/consolecmd.go | 4 ++-- cmd/geth/dao_test.go | 6 +++--- cmd/geth/main.go | 2 +- core/rawdb/accessors_metadata.go | 2 +- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 247c202bca17..d16ec73cb6b2 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -239,8 +239,9 @@ func initGenesis(ctx *cli.Context) error { if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } + // Open an initialise both full and light databases - stack := makeFullNode(ctx) + stack, _ := makeConfigNode(ctx) defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { @@ -277,7 +278,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -371,7 +372,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -406,7 +407,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -424,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -446,7 +447,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -554,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 60d9d0006689..2444ed066947 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,6 +20,7 @@ import ( "bufio" "errors" "fmt" + "github.com/ethereum/go-ethereum/core" "os" "reflect" "unicode" @@ -129,6 +130,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } + utils.SetShhConfig(ctx, stack, &cfg.Shh) return stack, cfg @@ -144,8 +146,11 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context) *node.Node { +func makeFullNode(ctx *cli.Context, genesis *core.Genesis) *node.Node { stack, cfg := makeConfigNode(ctx) + if genesis != nil { + cfg.Eth.Genesis = genesis + } utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5a88414a4230..25529c7dbaa4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -78,7 +78,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) startNode(ctx, stack) defer stack.Close() @@ -190,7 +190,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) startNode(ctx, stack) defer stack.Close() diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index f63b0dc6c8f5..96778bc13291 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -17,6 +17,7 @@ package main import ( + "github.com/ethereum/go-ethereum/params" "io/ioutil" "math/big" "os" @@ -25,7 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/params" ) // Genesis block for nodes which don't care about the DAO fork (i.e. not configured) @@ -119,8 +119,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc } else { // Force chain initialization args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} - geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...) - geth.WaitExit() + runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database path := filepath.Join(datadir, "geth", "chaindata") @@ -134,6 +133,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if genesis != "" { genesisHash = daoGenesisHash } + config := rawdb.ReadChainConfig(db, genesisHash) if config == nil { t.Errorf("test %d: failed to retrieve chain config: %v", test, err) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 03c2755807a9..694d02bee331 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -351,7 +351,7 @@ func geth(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } prepare(ctx) - stack := makeFullNode(ctx) + stack := makeFullNode(ctx, nil) defer stack.Close() startNode(ctx, stack) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index f8d09fbddf2d..93df167e07dc 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -18,7 +18,7 @@ package rawdb import ( "encoding/json" - + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" From 885a7d428dc2c5c8b05158d1cef777254fe1a077 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Jun 2020 12:48:10 +0200 Subject: [PATCH 058/160] lint --- cmd/geth/config.go | 2 +- cmd/geth/dao_test.go | 2 +- core/rawdb/accessors_metadata.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 2444ed066947..b395e80fde15 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,7 +20,6 @@ import ( "bufio" "errors" "fmt" - "github.com/ethereum/go-ethereum/core" "os" "reflect" "unicode" @@ -28,6 +27,7 @@ import ( cli "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index 96778bc13291..a77cbec0bee5 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -17,7 +17,6 @@ package main import ( - "github.com/ethereum/go-ethereum/params" "io/ioutil" "math/big" "os" @@ -26,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/params" ) // Genesis block for nodes which don't care about the DAO fork (i.e. not configured) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 93df167e07dc..f8d09fbddf2d 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -18,7 +18,7 @@ package rawdb import ( "encoding/json" - + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" From abbe6f83cc66078fe1d9e27815d68603a57a8011 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Jun 2020 16:42:48 +0200 Subject: [PATCH 059/160] fixed current tests, but need to add more --- graphql/graphql_test.go | 46 ++++++++++++++++++++++++++++++++++++++--- node/api.go | 10 ++++----- node/node.go | 35 +++++++++++++++++-------------- node/rpcstack.go | 30 +++++++++++++++++++++++---- node/rpcstack_test.go | 2 +- 5 files changed, 95 insertions(+), 28 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 29b8c469cebf..4c103bc462a2 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -130,6 +130,41 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, string(bodyBytes), expected) } +func TestGraphqlOnSeparatePort(t *testing.T) { + stack, err := node.New(&node.Config{ + HTTPHost: "127.0.0.1", + HTTPPort: 9393, + }) + if err != nil { + t.Fatalf("could not create node: %v", err) + } + defer stack.Close() + + separateTestEndpoint := "127.0.0.1:7474" + + createGQLService(t, stack, separateTestEndpoint) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // create http request + body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", separateTestEndpoint), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" + assert.Equal(t, expected, string(bodyBytes)) + +} + func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", @@ -143,6 +178,13 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } + + createGQLService(t, stack, testEndpoint) + + return stack +} + +func createGQLService(t *testing.T, stack *node.Node, endpoint string) { // create backend ethBackend, err := eth.New(stack, ð.DefaultConfig) if err != nil { @@ -150,12 +192,10 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { } // create gql service - err = New(stack, ethBackend.APIBackend, testEndpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, ethBackend.APIBackend, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } - - return stack } func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { diff --git a/node/api.go b/node/api.go index 0f21156e75bd..0b56184feff9 100644 --- a/node/api.go +++ b/node/api.go @@ -159,7 +159,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if HTTP server already exists - if server, exists := api.node.httpServerMap[endpoint]; exists { + if server, exists := api.node.HTTPServers.servers[endpoint]; exists { if server.RPCAllowed { return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) } @@ -219,7 +219,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.httpServerMap { + for _, httpServer := range api.node.HTTPServers.servers { if httpServer.RPCAllowed { api.node.stopServer(httpServer) return true, nil @@ -234,7 +234,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() // check if an existing WS server already exists - for _, server := range api.node.httpServerMap { + for _, server := range api.node.HTTPServers.servers { if server.WSAllowed { return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) } @@ -252,7 +252,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if there is an existing server on the specified port, and if there is, enable ws on it - if server, exists := api.node.httpServerMap[endpoint]; exists { + if server, exists := api.node.HTTPServers.servers[endpoint]; exists { // else configure ws on the existing server server.WSAllowed = true // configure origins @@ -319,7 +319,7 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.httpServerMap { + for _, httpServer := range api.node.HTTPServers.servers { if httpServer.WSAllowed { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down diff --git a/node/node.go b/node/node.go index f5716f56916b..8bdbaeace3d8 100644 --- a/node/node.go +++ b/node/node.go @@ -56,11 +56,11 @@ type Node struct { lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + HTTPServers *HTTPServers // TODO document + rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - httpServerMap map[string]*HTTPServer // Stores information about all http servers (if any) by their port, including http, ws, and graphql - ipc *HTTPServer // Stores information about the ipc http server stop chan struct{} // Channel to wait for termination notifications @@ -113,7 +113,9 @@ func New(conf *Config) (*Node, error) { Config: *conf, Lifecycles: make(map[reflect.Type]Lifecycle), }, - httpServerMap: make(map[string]*HTTPServer), + HTTPServers: &HTTPServers{ + servers: make(map[string]*HTTPServer), + }, ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, @@ -157,13 +159,13 @@ func New(conf *Config) (*Node, error) { httpServ.WSAllowed = true httpServ.WsOrigins = conf.WSOrigins httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) - node.httpServerMap[conf.HTTPEndpoint()] = httpServ + node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ return node, nil } - node.httpServerMap[conf.HTTPEndpoint()] = httpServ + node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ } if conf.WSHost != "" { - node.httpServerMap[conf.WSEndpoint()] = &HTTPServer{ + node.HTTPServers.servers[conf.WSEndpoint()] = &HTTPServer{ WsOrigins: conf.WSOrigins, Whitelist: conf.WSModules, Srv: rpc.NewServer(), @@ -224,18 +226,18 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { // RegisterHTTPServer registers the given HTTP server on the node func (n *Node) RegisterHTTPServer(endpoint string, server *HTTPServer) { - n.httpServerMap[endpoint] = server + n.HTTPServers.servers[endpoint] = server } // ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { - if server, exists := n.httpServerMap[endpoint]; exists { + if server, exists := n.HTTPServers.servers[endpoint]; exists { return server } return nil } -// CreateHTTPServer creates an http.Server and adds it to the given HTTPServer // TODO improve? +// CreateHTTPServer creates an http.Server and adds it to the given HTTPServers // TODO improve? func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // register apis and create handler stack err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) @@ -258,7 +260,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { httpSrv.IdleTimeout = h.Timeouts.IdleTimeout } - // complete the HTTPServer + // complete the HTTPServers h.Listener = listener h.Server = httpSrv @@ -293,6 +295,7 @@ func (n *Node) Start() error { // Configure the RPC interfaces if err := n.configureRPC(); err != nil { + n.HTTPServers.Stop() n.server.Stop() return err } @@ -363,7 +366,7 @@ func (n *Node) configureRPC() error { return err } - for _, server := range n.httpServerMap { + for _, server := range n.HTTPServers.servers { // configure the handlers if server.RPCAllowed { server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) @@ -392,8 +395,10 @@ func (n *Node) configureRPC() error { if err := n.CreateHTTPServer(server, false); err != nil { return err } - // start HTTP server - n.RegisterLifecycle(server) + } + // only register http server as a lifecycle if it has not already been registered + if _, exists := n.lifecycles[reflect.TypeOf(n.HTTPServers)]; !exists { + n.RegisterLifecycle(n.HTTPServers) } // All API endpoints started successfully return nil @@ -463,7 +468,7 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv = nil } // remove stopped http server from node's http servers // TODO is this preferable? - delete(n.httpServerMap, server.endpoint) + delete(n.HTTPServers.servers, server.endpoint) // remove stopped http server from node's lifecycles n.removeLifecycle(server) } @@ -597,7 +602,7 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - for _, httpServer := range n.httpServerMap { + for _, httpServer := range n.HTTPServers.servers { if httpServer.WSAllowed { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() diff --git a/node/rpcstack.go b/node/rpcstack.go index d5b5168e9bdd..3e4ea8ca4687 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -32,6 +32,10 @@ import ( "github.com/rs/cors" ) +type HTTPServers struct { + servers map[string]*HTTPServer // Stores information about all http servers (if any) by their port, including http, ws, and graphql +} + type HTTPServer struct { handler http.Handler Srv *rpc.Server @@ -57,14 +61,32 @@ type HTTPServer struct { GQLHandler http.Handler } -// Start starts the HTTPServer's HTTP server. // TODO I don't like the way this is written +func (h *HTTPServers) Start() error { + for _, server := range h.servers { + if err := server.Start(); err != nil { + return h.Stop() + } + } + return nil +} + +func (h *HTTPServers) Stop() error { + for _, server := range h.servers { + if err := server.Stop(); err != nil { + return err + } + } + return nil +} + +// Start starts the HTTPServers's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } -// Stop shuts down the HTTPServer's HTTP server. // TODO I don't like the way this is written +// Stop shuts down the HTTPServers's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Stop() error { if h.Server != nil { url := fmt.Sprintf("http://%v/", h.Listener.Addr()) @@ -80,12 +102,12 @@ func (h *HTTPServer) Stop() error { return nil } -// SetHandler assigns the given handler to the HTTPServer. +// SetHandler assigns the given handler to the HTTPServers. func (h *HTTPServer) SetHandler(handler http.Handler) { h.handler = handler } -// SetEndpoints assigns the given endpoint to the HTTPServer. +// SetEndpoints assigns the given endpoint to the HTTPServers. func (h *HTTPServer) SetEndpoint(endpoint string) { h.endpoint = endpoint } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 799b138a6917..dc75afa93438 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -40,7 +40,7 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { assert.Equal(t, "websocket", response.Header.Get("Upgrade")) } -// Tests that a ws handler can be added to and enabled on an existing HTTPServer +// Tests that a ws handler can be added to and enabled on an existing HTTPServers func TestWSAllowed(t *testing.T) { stack, err := New(&Config{ HTTPHost: DefaultHTTPHost, From ae92dee0f2cc10d0d0e415ccfe724e4305bedcea Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Jun 2020 17:14:25 +0200 Subject: [PATCH 060/160] new test to check for proper creation of http servers on node --- node/node_test.go | 59 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index e8ac35318a59..824653c697ab 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -359,9 +359,48 @@ func TestLifecycleRetrieval(t *testing.T) { } } -// Tests whether websocket requests can be handled on the same port as a regular http server +// Tests whether a node can successfully create and register HTTP server +// lifecycles on the node. +func TestHTTPServerCreation(t *testing.T) { + // test on same ports + node1 := startHTTP(t, 7453, 7453) + if len(node1.HTTPServers.servers) != 1 { + t.Fatalf("node has more than 1 http server") + } + // check to make sure http servers are registered + var httpSrv1 *HTTPServers + if err := node1.Lifecycle(&httpSrv1); err != nil { + t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + } + for _, server := range node1.HTTPServers.servers { + if !(server.WSAllowed && server.RPCAllowed) { + t.Fatalf("node's http server is not configured to handle both rpc and ws") + } + } + node1.Close() + + // test on separate ports + node2 := startHTTP(t, 7453, 9393) + if len(node2.HTTPServers.servers) != 2 { + t.Fatalf("amount of http servers on the node is not equal to 2") + } + // check to make sure http servers are registered + var httpSrv2 *HTTPServers + if err := node2.Lifecycle(&httpSrv2); err != nil { + t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + } + // check that neither http server has both ws and rpc enabled + for _, server := range node2.HTTPServers.servers { + if server.WSAllowed && server.RPCAllowed { + t.Fatalf("both rpc and ws allowed on a single http server") + } + } + node2.Close() +} + +// Tests whether websocket requests can be handled on the same port as a regular http server. func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { - node := startHTTP(t) + node := startHTTP(t, 7453, 7453) defer node.Close() wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) @@ -377,9 +416,9 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) } -// Tests whether http requests can be handled successfully +// Tests whether http requests can be handled successfully. func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { - node := startHTTP(t) + node := startHTTP(t, 7453, 7453) defer node.Close() httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) @@ -392,20 +431,20 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) } -func startHTTP(t *testing.T) *Node { +func startHTTP(t *testing.T, httpPort, wsPort int) *Node { conf := &Config{ HTTPHost: "127.0.0.1", - HTTPPort: 7453, + HTTPPort: httpPort, WSHost: "127.0.0.1", - WSPort: 7453, + WSPort: wsPort, } node, err := New(conf) if err != nil { - t.Error("could not create a new node ", err) + t.Fatalf("could not create a new node: %v", err) } err = node.Start() if err != nil { - t.Error("could not start http service on node ", err) + t.Fatalf("could not start http service on node: %v", err) } return node @@ -415,7 +454,7 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(req) if err != nil { - t.Fatal("could not issue a GET request to the given endpoint", err) + t.Fatalf("could not issue a GET request to the given endpoint: %v", err) } return resp From f2fcf692ebe95d8e3af9a9db5b201f02494daa91 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 17 Jun 2020 16:16:35 +0200 Subject: [PATCH 061/160] added more test coverage, removed some useless code --- eth/backend.go | 4 +- les/client.go | 4 +- node/node.go | 12 +--- node/node_test.go | 98 ++++++++++++++++++++++++++- node/utils_test.go | 52 +++++++++++++- p2p/simulations/examples/ping-pong.go | 4 +- p2p/simulations/http_test.go | 4 +- whisper/whisperv6/whisper.go | 5 +- 8 files changed, 154 insertions(+), 29 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 56f605bc98c3..9da88d42dadc 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -239,9 +239,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { // Register the backend on the node stack.RegisterAPIs(eth.APIs()) - if err := stack.RegisterProtocols(eth.Protocols()); err != nil { - return nil, err - } + stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) return eth, nil diff --git a/les/client.go b/les/client.go index 286fab7b2ede..3ed45c124b99 100644 --- a/les/client.go +++ b/les/client.go @@ -173,9 +173,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { // Register the backend on the node stack.RegisterAPIs(leth.APIs()) - if err := stack.RegisterProtocols(leth.Protocols()); err != nil { - return nil, err - } + stack.RegisterProtocols(leth.Protocols()) stack.RegisterLifecycle(leth) return leth, nil diff --git a/node/node.go b/node/node.go index 8bdbaeace3d8..baaf172f315c 100644 --- a/node/node.go +++ b/node/node.go @@ -214,9 +214,8 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { } // RegisterProtocols adds backend's protocols to the node's p2p server -func (n *Node) RegisterProtocols(protocols []p2p.Protocol) error { +func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { n.server.Protocols = append(n.server.Protocols, protocols...) - return nil } // RegisterAPIs registers the APIs a service provides on the node @@ -237,7 +236,7 @@ func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { return nil } -// CreateHTTPServer creates an http.Server and adds it to the given HTTPServers // TODO improve? +// CreateHTTPServer creates an http.Server and adds it to the given HTTPServer func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { // register apis and create handler stack err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) @@ -469,13 +468,6 @@ func (n *Node) stopServer(server *HTTPServer) { } // remove stopped http server from node's http servers // TODO is this preferable? delete(n.HTTPServers.servers, server.endpoint) - // remove stopped http server from node's lifecycles - n.removeLifecycle(server) -} - -// removeLifecycle removes a stopped Lifecycle from the running node's Lifecycles -func (n *Node) removeLifecycle(lifecycle Lifecycle) { - delete(n.lifecycles, reflect.TypeOf(lifecycle)) } // Stop terminates a running node along with all it's services. In the node was diff --git a/node/node_test.go b/node/node_test.go index 824653c697ab..446895aff4c7 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -18,6 +18,8 @@ package node import ( "errors" + "fmt" + "github.com/ethereum/go-ethereum/rpc" "io/ioutil" "net/http" "os" @@ -105,7 +107,7 @@ func TestNodeUsedDataDir(t *testing.T) { } // Tests whether a Lifecycle can be registered. -func TestLifecycleRegistry(t *testing.T) { +func TestLifecycleRegistry_Successful(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) @@ -120,6 +122,50 @@ func TestLifecycleRegistry(t *testing.T) { } } +// Tests whether a service's protocols can be registered properly on the node's p2p server. +func TestRegisterProtocols(t *testing.T) { + stack, err := New(testNodeConfig()) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + defer stack.Close() + + fs, err := NewFullService(stack) + if err != nil { + t.Fatalf("could not create full service: %v", err) + } + + for _, protocol := range fs.Protocols() { + if !containsProtocol(stack.server.Protocols, protocol) { + t.Fatalf("protocol %v was not successfully registered", protocol) + } + } + + for _, api := range fs.APIs() { + if !containsAPI(stack.rpcAPIs, api) { + t.Fatalf("api %v was not successfully registered", api) + } + } +} + +func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool { + for _, a := range stackProtocols { + if reflect.DeepEqual(a, protocol) { + return true + } + } + return false +} + +func containsAPI(stackAPIs []rpc.API, api rpc.API) bool { + for _, a := range stackAPIs { + if reflect.DeepEqual(a, api) { + return true + } + } + return false +} + // Tests that registered Lifecycles get started and stopped correctly. func TestLifecycleLifeCycle(t *testing.T) { stack, err := New(testNodeConfig()) @@ -359,9 +405,48 @@ func TestLifecycleRetrieval(t *testing.T) { } } +// Tests whether a given HTTPServer can be registered on the node +func TestRegisterHTTPServer(t *testing.T) { + stack, err := New(testNodeConfig()) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + defer stack.Close() + + srv1 := &HTTPServer{ + host: "test1", + port: 0001, + } + endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) + stack.RegisterHTTPServer(endpoint1, srv1) + + srv2 := &HTTPServer{ + host: "test2", + port: 0002, + } + endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) + stack.RegisterHTTPServer(endpoint2, srv2) + + noop := &HTTPServer{ + host: "test", + port: 0000, + } + endpointNoop := fmt.Sprintf("%s:%d", noop.host, noop.port) + + if srv1 != stack.ExistingHTTPServer(endpoint1) { + t.Fatalf("server %v was not properly registered on the given endpoint %s", srv1, endpoint1) + } + if srv2 != stack.ExistingHTTPServer(endpoint2) { + t.Fatalf("server %v was not properly registered on the given endpoint %s", srv2, endpoint2) + } + if noop == stack.ExistingHTTPServer(endpointNoop) { + t.Fatalf("server %v was incorrectly registered on the given endpoint %s", noop, endpointNoop) + } +} + // Tests whether a node can successfully create and register HTTP server // lifecycles on the node. -func TestHTTPServerCreation(t *testing.T) { +func TestHTTPServerCreateAndStop(t *testing.T) { // test on same ports node1 := startHTTP(t, 7453, 7453) if len(node1.HTTPServers.servers) != 1 { @@ -376,7 +461,12 @@ func TestHTTPServerCreation(t *testing.T) { if !(server.WSAllowed && server.RPCAllowed) { t.Fatalf("node's http server is not configured to handle both rpc and ws") } + node1.stopServer(server) + if node1.ExistingHTTPServer(server.endpoint) != nil { + t.Fatalf("failed to remove server %v from node after stopping it", server) + } } + node1.Close() // test on separate ports @@ -394,6 +484,10 @@ func TestHTTPServerCreation(t *testing.T) { if server.WSAllowed && server.RPCAllowed { t.Fatalf("both rpc and ws allowed on a single http server") } + node2.stopServer(server) + if node2.ExistingHTTPServer(server.endpoint) != nil { + t.Fatalf("failed to remove server %v from node after stopping it", server) + } } node2.Close() } diff --git a/node/utils_test.go b/node/utils_test.go index 17c45a238fd6..0df20f5db440 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -19,7 +19,10 @@ package node -import "github.com/ethereum/go-ethereum/p2p" +import ( + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) // NoopLifecycle is a trivial implementation of the Service interface. type NoopLifecycle struct{} @@ -69,3 +72,50 @@ func (s *InstrumentedService) Stop() error { } return s.stop } + +type FullService struct{} + +func NewFullService(stack *Node) (*FullService, error) { + fs := new(FullService) + + stack.RegisterProtocols(fs.Protocols()) + stack.RegisterAPIs(fs.APIs()) + stack.RegisterLifecycle(fs) + return fs, nil +} + +func (f *FullService) Start() error { return nil } + +func (f *FullService) Stop() error { return nil } + +func (f *FullService) Protocols() []p2p.Protocol { + return []p2p.Protocol{ + p2p.Protocol{ + Name: "test1", + Version: uint(1), + }, + p2p.Protocol{ + Name: "test2", + Version: uint(2), + }, + } +} + +func (f *FullService) APIs() []rpc.API { + return []rpc.API{ + { + Namespace: "admin", + Version: "1.0", + }, + { + Namespace: "debug", + Version: "1.0", + Public: true, + }, + { + Namespace: "net", + Version: "1.0", + Public: true, + }, + } +} \ No newline at end of file diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index 678236f3552a..13b6873bfea3 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -48,9 +48,7 @@ func main() { services := map[string]adapters.LifecycleConstructor{ "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { pps := newPingPongService(ctx.Config.ID) - if err := stack.RegisterProtocols(pps.Protocols()); err != nil { - return nil, err - } + stack.RegisterProtocols(pps.Protocols()) stack.RegisterAPIs(pps.APIs()) return pps, nil }, diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index 1ecfb719fb96..6d7f0b6d7a31 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -71,9 +71,7 @@ func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecy } svc.state.Store(ctx.Snapshot) - if err := stack.RegisterProtocols(svc.Protocols()); err != nil { - return nil, err - } + stack.RegisterProtocols(svc.Protocols()) stack.RegisterAPIs(svc.APIs()) return svc, nil } diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index a58e1710c9f6..5799919db967 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -134,11 +134,8 @@ func New(stack *node.Node, cfg *Config) error { } stack.RegisterAPIs(whisper.APIs()) - if err := stack.RegisterProtocols(whisper.Protocols()); err != nil { - return err - } + stack.RegisterProtocols(whisper.Protocols()) stack.RegisterLifecycle(whisper) - return nil } From 204514c2de1741e21414aedce4e37d1235c72f15 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 17 Jun 2020 18:29:12 +0200 Subject: [PATCH 062/160] minor changes, removing spaces, cleaninng up --- cmd/geth/config.go | 2 -- cmd/geth/dao_test.go | 1 - cmd/geth/main.go | 8 ++++---- cmd/utils/flags.go | 6 +++--- console/console_test.go | 1 - eth/backend.go | 4 +--- ethclient/ethclient_test.go | 3 --- 7 files changed, 8 insertions(+), 17 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index b395e80fde15..8495af7e2fc1 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -130,7 +130,6 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } - utils.SetShhConfig(ctx, stack, &cfg.Shh) return stack, cfg @@ -176,7 +175,6 @@ func makeFullNode(ctx *cli.Context, genesis *core.Genesis) *node.Node { if cfg.Ethstats.URL != "" { utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) } - return stack } diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index a77cbec0bee5..6c36771e9726 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -133,7 +133,6 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if genesis != "" { genesisHash = daoGenesisHash } - config := rawdb.ReadChainConfig(db, genesisHash) if config == nil { t.Errorf("test %d: failed to retrieve chain config: %v", test, err) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 694d02bee331..c5e16e74ec14 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -350,12 +350,12 @@ func geth(ctx *cli.Context) error { if args := ctx.Args(); len(args) > 0 { return fmt.Errorf("invalid command: %q", args[0]) } + prepare(ctx) stack := makeFullNode(ctx, nil) - defer stack.Close() - startNode(ctx, stack) + startNode(ctx, stack) stack.Wait() return nil } @@ -465,10 +465,10 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - // Check if node's backend is eth and that it exists + // Check if node's backend is eth var ethBackend *eth.Ethereum if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { - utils.Fatalf("Ethereum service not running: backend is not an eth backend") + utils.Fatalf("Ethereum service not running: %v", err) } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fa7b13af45ba..27937da36ed7 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1696,7 +1696,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { if cfg.SyncMode == downloader.LightSync { _, err := les.New(stack, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %w", err) + Fatalf("Failed to register the Ethereum service: %v", err) } } else { backend, err := eth.New(stack, cfg) @@ -1713,7 +1713,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { if err := whisper.New(stack, cfg); err != nil { - Fatalf("Failed to register the Whisper service: %w", err) + Fatalf("Failed to register the Whisper service: %v", err) } } @@ -1721,7 +1721,7 @@ func RegisterShhService(stack *node.Node, cfg *whisper.Config) { // the given node. func RegisterEthStatsService(stack *node.Node, url string) { if err := ethstats.New(stack, url); err != nil { - Fatalf("Failed to register the Ethereum Stats service: %w", err) + Fatalf("Failed to register the Ethereum Stats service: %v", err) } } diff --git a/console/console_test.go b/console/console_test.go index 80a2253997c0..68c03d108d00 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -113,7 +113,6 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { if err != nil { t.Fatalf("failed to register Ethereum protocol: %v", err) } - // Start the node and assemble the JavaScript console around it if err = stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) diff --git a/eth/backend.go b/eth/backend.go index 9da88d42dadc..cfd90d4fe3fd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -221,7 +221,6 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil { return nil, err } - eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) @@ -241,7 +240,6 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) - return eth, nil } @@ -545,7 +543,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // P2PServer registers the node's running p2p server with the Backend. func (s *Ethereum) P2PServer(server *p2p.Server) error { if server == nil { - return errors.New("p2p server is not running, cannot register with eth backend") // TODO is this error message okay? + return node.ErrNodeStopped // TODO is this error okay to return? } s.p2pServer = server return nil diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index c1d98f020279..b49abe917732 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -187,13 +187,11 @@ var ( func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Generate test chain. genesis, blocks := generateTestChain() - // Create node n, err := node.New(&node.Config{}) if err != nil { t.Fatalf("can't create new node: %v", err) } - // Create Ethereum Service config := ð.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake @@ -201,7 +199,6 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { if err != nil { t.Fatalf("can't create new ethereum service: %v", err) } - // Import the test chain. if err := n.Start(); err != nil { t.Fatalf("can't start test node: %v", err) From 41a25c362760a4a2d73ca66ba433b39a2bbbd022 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 18 Jun 2020 16:53:46 +0200 Subject: [PATCH 063/160] some changes to graphql and other misc --- cmd/utils/flags.go | 13 +------------ ethstats/ethstats.go | 6 +++--- graphql/graphql_test.go | 13 ++++--------- graphql/service.go | 42 +++++++++++++++-------------------------- 4 files changed, 23 insertions(+), 51 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 27937da36ed7..6622781f478b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -49,7 +49,6 @@ import ( "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -1727,18 +1726,8 @@ func RegisterEthStatsService(stack *node.Node, url string) { // RegisterGraphQLService is a utility function to construct a new service and register it against a node. func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - var backend ethapi.Backend - // fetch backend - var ethServ *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðServ); err == nil { - backend = ethServ.APIBackend - } - var lesServ *les.LightEthereum - if err := stack.ServiceContext.Lifecycle(&lesServ); err == nil { - backend = lesServ.ApiBackend - } // create new graphQL service - if err := graphql.New(stack, backend, endpoint, cors, vhosts, timeouts); err != nil { + if err := graphql.New(stack, endpoint, cors, vhosts, timeouts); err != nil { Fatalf("Failed to register the GraphQL service: %v", err) } } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index e40e973cbb13..e29fadd17838 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -110,7 +110,9 @@ func New(node *node.Node, url string) error { ethstats.les = lesServ ethstats.engine = lesServ.Engine() } - // TODO check to make sure at least one backend is not nil? + if ethstats.engine == nil {// TODO check to make sure at least one backend is not nil? + return fmt.Errorf("Ethereum service not found") + } node.RegisterLifecycle(ethstats) return nil @@ -130,8 +132,6 @@ func (s *Service) Stop() error { return nil } -func (s *Service) Server() *node.HTTPServer { return nil } - // loop keeps trying to connect to the netstats server, reporting chain events // until termination. func (s *Service) loop() { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 4c103bc462a2..1c2a56ab0877 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -130,14 +130,9 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, string(bodyBytes), expected) } +// Tests that graphql can be successfully enabled on a separate port than rpc and ws. func TestGraphqlOnSeparatePort(t *testing.T) { - stack, err := node.New(&node.Config{ - HTTPHost: "127.0.0.1", - HTTPPort: 9393, - }) - if err != nil { - t.Fatalf("could not create node: %v", err) - } + stack := createNode(t, false) defer stack.Close() separateTestEndpoint := "127.0.0.1:7474" @@ -186,13 +181,13 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { func createGQLService(t *testing.T, stack *node.Node, endpoint string) { // create backend - ethBackend, err := eth.New(stack, ð.DefaultConfig) + _, err := eth.New(stack, ð.DefaultConfig) if err != nil { t.Fatalf("could not create eth backend: %v", err) } // create gql service - err = New(stack, ethBackend.APIBackend, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index eb62f2f0695a..1ba7bdf582f0 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,6 +18,8 @@ package graphql import ( "errors" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/les" "net/http" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -27,19 +29,21 @@ import ( "github.com/graph-gophers/graphql-go/relay" ) -// Service encapsulates a GraphQL service. -type Service struct { - backend ethapi.Backend // The backend that queries will operate on. - graphqlServer *node.HTTPServer -} - // New constructs a new GraphQL service instance. -func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { - service := new(Service) +func New(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { + // fetch backend + var backend ethapi.Backend + var ethServ *eth.Ethereum + if err := stack.ServiceContext.Lifecycle(ðServ); err == nil { + backend = ethServ.APIBackend + } + var lesServ *les.LightEthereum + if err := stack.ServiceContext.Lifecycle(&lesServ); err == nil { + backend = lesServ.ApiBackend + } if backend == nil { return errors.New("No backend found") // TODO should this be a fatal error? } - service.backend = backend // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) if server != nil { @@ -48,7 +52,7 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts server.CorsAllowedOrigins = append(server.CorsAllowedOrigins, cors...) server.Timeouts = timeouts // create handler - handler, err := createHandler(service.backend, cors, vhosts) + handler, err := createHandler(backend, cors, vhosts) if err != nil { return err } @@ -57,7 +61,7 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts return nil } // otherwise create a new server - handler, err := createHandler(service.backend, cors, vhosts) + handler, err := createHandler(backend, cors, vhosts) if err != nil { return err } @@ -73,7 +77,6 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts gqlServer.SetEndpoint(endpoint) stack.RegisterHTTPServer(endpoint, gqlServer) - service.graphqlServer = gqlServer return nil } @@ -106,18 +109,3 @@ func newHandler(backend ethapi.Backend) (http.Handler, error) { mux.Handle("/graphql/", h) return mux, nil } - -// Start is called after all services have been constructed and the networking -// layer was also initialized to spawn any goroutines required by the service. -func (s *Service) Start() error { - - return nil -} - -// Stop terminates all goroutines belonging to the service, blocking until they -// are all terminated. -func (s *Service) Stop() error { return nil } - -func (s *Service) Server() *node.HTTPServer { - return s.graphqlServer -} From 666db04bd9c62ff63b82e26425553e4069ca5cce Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:44:19 +0200 Subject: [PATCH 064/160] linted --- ethstats/ethstats.go | 2 +- graphql/service.go | 5 ++--- node/node_test.go | 2 +- node/utils_test.go | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index e29fadd17838..0989a9862b12 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -110,7 +110,7 @@ func New(node *node.Node, url string) error { ethstats.les = lesServ ethstats.engine = lesServ.Engine() } - if ethstats.engine == nil {// TODO check to make sure at least one backend is not nil? + if ethstats.engine == nil { // TODO check to make sure at least one backend is not nil? return fmt.Errorf("Ethereum service not found") } diff --git a/graphql/service.go b/graphql/service.go index 1ba7bdf582f0..8bec768ef6ef 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,11 +18,11 @@ package graphql import ( "errors" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "net/http" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" @@ -77,7 +77,6 @@ func New(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc. gqlServer.SetEndpoint(endpoint) stack.RegisterHTTPServer(endpoint, gqlServer) - return nil } diff --git a/node/node_test.go b/node/node_test.go index 446895aff4c7..e00c686058e4 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -19,7 +19,6 @@ package node import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/rpc" "io/ioutil" "net/http" "os" @@ -28,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" ) diff --git a/node/utils_test.go b/node/utils_test.go index 0df20f5db440..481edd6d4822 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -91,12 +91,12 @@ func (f *FullService) Stop() error { return nil } func (f *FullService) Protocols() []p2p.Protocol { return []p2p.Protocol{ p2p.Protocol{ - Name: "test1", + Name: "test1", Version: uint(1), }, p2p.Protocol{ - Name: "test2", - Version: uint(2), + Name: "test2", + Version: uint(2), }, } } @@ -118,4 +118,4 @@ func (f *FullService) APIs() []rpc.API { Public: true, }, } -} \ No newline at end of file +} From 828646c35ec4ba6762236c00d1163c3c5f183401 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 19 Jun 2020 16:22:24 +0200 Subject: [PATCH 065/160] no need to pass gen state to makefullnode --- cmd/geth/chaincmd.go | 12 ++++++------ cmd/geth/config.go | 7 ++----- cmd/geth/consolecmd.go | 4 ++-- cmd/geth/main.go | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d16ec73cb6b2..147587004060 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -278,7 +278,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -372,7 +372,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -407,7 +407,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -425,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -447,7 +447,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -555,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 8495af7e2fc1..af08b7788611 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -27,7 +27,6 @@ import ( cli "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -145,11 +144,9 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context, genesis *core.Genesis) *node.Node { +func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - if genesis != nil { - cfg.Eth.Genesis = genesis - } + utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 25529c7dbaa4..5a88414a4230 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -78,7 +78,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) startNode(ctx, stack) defer stack.Close() @@ -190,7 +190,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) startNode(ctx, stack) defer stack.Close() diff --git a/cmd/geth/main.go b/cmd/geth/main.go index c5e16e74ec14..b0fb861016c0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -352,7 +352,7 @@ func geth(ctx *cli.Context) error { } prepare(ctx) - stack := makeFullNode(ctx, nil) + stack := makeFullNode(ctx) defer stack.Close() startNode(ctx, stack) From 344320270d7a61083c88b5e406a473dbe3580fe1 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 22 Jun 2020 13:10:34 +0200 Subject: [PATCH 066/160] missing register apis and protocols --- miner/stress_clique.go | 2 ++ miner/stress_ethash.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/miner/stress_clique.go b/miner/stress_clique.go index a69dfbd69d9d..6f65ddddfca2 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -208,6 +208,8 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) { if err != nil { return nil, err } + stack.RegisterAPIs(ethBackend.APIs()) + stack.RegisterProtocols(ethBackend.Protocols()) stack.RegisterLifecycle(ethBackend) // Start the node and return if successful diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 1cb5396ce323..6893dff6e273 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -189,6 +189,8 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) { if err != nil { return nil, err } + stack.RegisterAPIs(ethBackend.APIs()) + stack.RegisterProtocols(ethBackend.Protocols()) stack.RegisterLifecycle(ethBackend) // Start the node and return if successful From 9ed169edd0161ace72844c6d0a1dfc74fead3b11 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 22 Jun 2020 14:44:19 +0200 Subject: [PATCH 067/160] register http before starting --- node/api.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/node/api.go b/node/api.go index 0b56184feff9..ec9b836d7203 100644 --- a/node/api.go +++ b/node/api.go @@ -204,13 +204,14 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis if err := api.node.CreateHTTPServer(httpServer, false); err != nil { return false, err } + // register the HTTP server + api.node.RegisterHTTPServer(endpoint, httpServer) // start the HTTP server httpServer.Start() api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.Listener.Addr()), "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), "vhosts", strings.Join(httpServer.Vhosts, ",")) - api.node.RegisterHTTPServer(endpoint, httpServer) return true, nil } @@ -291,7 +292,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str modules = append(modules, strings.TrimSpace(m)) } } - + // configure http server wsServer := &HTTPServer{ Srv: rpc.NewServer(), endpoint: endpoint, @@ -301,16 +302,18 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str WsOrigins: origins, WSAllowed: true, } - + // create handler wsServer.handler = wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) + // create the HTTP server if err := api.node.CreateHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { return false, err } - + // register the HTTP server + api.node.RegisterHTTPServer(endpoint, wsServer) + // start the HTTP server wsServer.Start() api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.Listener.Addr())) - api.node.RegisterHTTPServer(endpoint, wsServer) return true, nil } From fb8e5b0f909d5d9a7e0da406c561d7eff0279797 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 22 Jun 2020 15:20:27 +0200 Subject: [PATCH 068/160] added some documentation --- node/node.go | 2 +- node/rpcstack.go | 4 ++-- node/service.go | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index baaf172f315c..f4c35254ac67 100644 --- a/node/node.go +++ b/node/node.go @@ -56,7 +56,7 @@ type Node struct { lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - HTTPServers *HTTPServers // TODO document + HTTPServers *HTTPServers // HTTPServers stores information about the node's rpc, ws, and graphQL http servers. rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests diff --git a/node/rpcstack.go b/node/rpcstack.go index 3e4ea8ca4687..3ceab681e687 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -241,7 +241,7 @@ func isWebsocket(r *http.Request) bool { strings.ToLower(r.Header.Get("Connection")) == "upgrade" } -// TODO document +// NewGQLUpgradeHandler wraps the given handler, h, in the given graphQL handler. func NewGQLUpgradeHandler(h http.Handler, gql http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if isGQL(r) { @@ -254,7 +254,7 @@ func NewGQLUpgradeHandler(h http.Handler, gql http.Handler) http.Handler { }) } -// TODO document +// isGQL checks if the given request is a graphQL request. func isGQL(r *http.Request) bool { return r.URL.Path == "/graphql" || r.URL.Path == "/graphql/" } diff --git a/node/service.go b/node/service.go index 072933fe6050..3cb1b978ade7 100644 --- a/node/service.go +++ b/node/service.go @@ -89,7 +89,10 @@ func (ctx *ServiceContext) ExtRPCEnabled() bool { return ctx.Config.ExtRPCEnabled() } -// TODO document +// Lifecycle encompasses the behavior of services that can be started and stopped +// on the node. Lifecycle management is delegated to the node, but it is the +// responsibility of the service-specific package to configure and register the +// service on the node using the `RegisterLifecycle` method. type Lifecycle interface { // Start is called after all services have been constructed and the networking // layer was also initialized to spawn any goroutines required by the service. From e897df9ebda3201843b7206679ea31e4e784aef2 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 22 Jun 2020 15:34:31 +0200 Subject: [PATCH 069/160] minor fixes --- node/service.go | 2 +- p2p/server.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/node/service.go b/node/service.go index 3cb1b978ade7..e7746e92262c 100644 --- a/node/service.go +++ b/node/service.go @@ -73,7 +73,7 @@ func (ctx *ServiceContext) ResolvePath(path string) string { return ctx.Config.ResolvePath(path) } -// Lifecycle retrieves a currently running lifecycle registered of a specific type. +// Lifecycle retrieves a currently running Lifecycle registered of a specific type. func (ctx *ServiceContext) Lifecycle(lifecycle interface{}) error { element := reflect.ValueOf(lifecycle).Elem() if running, ok := ctx.Lifecycles[element.Type()]; ok { diff --git a/p2p/server.go b/p2p/server.go index aae88260a91b..58a41729861e 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1120,6 +1120,7 @@ func (srv *Server) PeersInfo() []*PeerInfo { return infos } +// Running returns whether the server is running. func (srv *Server) Running() bool { return srv.running } From cfae6edded511a44c382253e320fca722dcf9d4e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 29 Jun 2020 15:59:33 +0200 Subject: [PATCH 070/160] Changes requested from PR review (#23) --- cmd/faucet/faucet.go | 4 +- cmd/geth/chaincmd.go | 12 +-- cmd/geth/config.go | 11 ++- cmd/geth/consolecmd.go | 8 +- cmd/geth/main.go | 34 +++---- cmd/utils/flags.go | 19 ++-- eth/api_backend.go | 31 ++++++ eth/backend.go | 27 ++---- ethclient/ethclient.go | 8 ++ ethclient/ethclient_test.go | 33 +++---- ethstats/ethstats.go | 147 +++++++++-------------------- graphql/graphql_test.go | 4 +- graphql/service.go | 17 +--- internal/ethapi/backend.go | 15 ++- les/api_backend.go | 33 +++++++ les/client.go | 14 +-- miner/stress_clique.go | 41 ++++---- miner/stress_ethash.go | 39 ++++---- mobile/geth.go | 6 +- node/api.go | 10 +- node/lifecycle.go | 31 ++++++ node/node.go | 70 ++++---------- node/node_test.go | 73 +++++--------- node/rpcstack.go | 25 +++-- node/rpcstack_test.go | 2 +- node/service.go | 104 -------------------- node/service_test.go | 93 ------------------ p2p/simulations/adapters/exec.go | 5 +- p2p/simulations/adapters/inproc.go | 5 +- p2p/simulations/adapters/types.go | 5 +- whisper/mailserver/server_test.go | 20 +--- whisper/whisperv6/api_test.go | 4 +- whisper/whisperv6/filter_test.go | 20 +--- whisper/whisperv6/whisper.go | 4 +- whisper/whisperv6/whisper_test.go | 57 +++-------- 35 files changed, 381 insertions(+), 650 deletions(-) create mode 100644 node/lifecycle.go delete mode 100644 node/service.go delete mode 100644 node/service_test.go diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 6f6cb39069f0..65c71ce4eb03 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -241,14 +241,14 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis - _, err = les.New(stack, &cfg) + lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := ethstats.New(stack, stats); err != nil { + if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil { return nil, err } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 147587004060..6bc595566f15 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -278,7 +278,7 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack, false) @@ -372,7 +372,7 @@ func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack, true) @@ -407,7 +407,7 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -425,7 +425,7 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack) @@ -447,7 +447,7 @@ func copyDb(ctx *cli.Context) error { utils.Fatalf("Source ancient chain directory path argument missing") } // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, false) @@ -555,7 +555,7 @@ func confirmAndRemoveDB(database string, kind string) { } func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx) + stack, _ := makeFullNode(ctx) defer stack.Close() chain, chainDb := utils.MakeChain(ctx, stack, true) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index af08b7788611..a777f37050e5 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" @@ -144,10 +145,10 @@ func enableWhisper(ctx *cli.Context) bool { return false } -func makeFullNode(ctx *cli.Context) *node.Node { +func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - utils.RegisterEthService(stack, &cfg.Eth) + backend := utils.RegisterEthService(stack, &cfg.Eth) // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode shhEnabled := enableWhisper(ctx) @@ -166,13 +167,13 @@ func makeFullNode(ctx *cli.Context) *node.Node { } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, backend, cfg.Node) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { - utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) + utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL) } - return stack + return stack, backend } // dumpConfig is the dumpconfig command. diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5a88414a4230..e2f733f844a4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -78,8 +78,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - stack := makeFullNode(ctx) - startNode(ctx, stack) + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) defer stack.Close() // Attach to the newly started node and start the JavaScript console @@ -190,8 +190,8 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - stack := makeFullNode(ctx) - startNode(ctx, stack) + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) defer stack.Close() // Attach to the newly started node and start the JavaScript console diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b0fb861016c0..bbbc1b70d4ea 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -32,12 +32,11 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" @@ -352,10 +351,10 @@ func geth(ctx *cli.Context) error { } prepare(ctx) - stack := makeFullNode(ctx) + stack, backend := makeFullNode(ctx) defer stack.Close() - startNode(ctx, stack) + startNode(ctx, stack, backend) stack.Wait() return nil } @@ -363,7 +362,7 @@ func geth(ctx *cli.Context) error { // startNode boots up the system node and all registered protocols, after which // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. -func startNode(ctx *cli.Context, stack *node.Node) { +func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { debug.Memsize.Add("node", stack) // Start up the node itself @@ -386,20 +385,12 @@ func startNode(ctx *cli.Context, stack *node.Node) { // Set contract backend for ethereum service if local node // is serving LES requests. if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { - var ethBackend *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { - utils.Fatalf("Failed to retrieve ethereum service: %v", err) - } - ethBackend.SetContractBackend(ethClient) + backend.SetContractBackend(ethClient) } // Set contract backend for les service if local node is // running as a light client. if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { - var lesBackend *les.LightEthereum - if err := stack.ServiceContext.Lifecycle(&lesBackend); err != nil { - utils.Fatalf("Failed to retrieve light ethereum service: %v", err) - } - lesBackend.SetContractBackend(ethClient) + backend.SetContractBackend(ethClient) } go func() { @@ -465,24 +456,23 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - // Check if node's backend is eth - var ethBackend *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðBackend); err != nil { - utils.Fatalf("Ethereum service not running: %v", err) - } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) } - ethBackend.TxPool().SetGasPrice(gasprice) + txpool := backend.TxPool() + if txpool == nil { + utils.Fatalf("Ethereum service not running: %v", err) + } + txpool.SetGasPrice(gasprice) threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") } - if err := ethBackend.StartMining(threads); err != nil { + if err := backend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6622781f478b..e177df569261 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -49,6 +49,7 @@ import ( "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -62,7 +63,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" pcsclite "github.com/gballet/go-libpcsclite" cli "gopkg.in/urfave/cli.v1" @@ -1691,12 +1691,13 @@ func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) { +func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { if cfg.SyncMode == downloader.LightSync { - _, err := les.New(stack, cfg) + backend, err := les.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + return backend.ApiBackend } else { backend, err := eth.New(stack, cfg) if err != nil { @@ -1706,28 +1707,28 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { ls, _ := les.NewLesServer(backend, cfg) backend.AddLesServer(ls) } + return backend.APIBackend } } // RegisterShhService configures Whisper and adds it to the given node. func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - if err := whisper.New(stack, cfg); err != nil { + if _, err := whisper.New(stack, cfg); err != nil { Fatalf("Failed to register the Whisper service: %v", err) } } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. -func RegisterEthStatsService(stack *node.Node, url string) { - if err := ethstats.New(stack, url); err != nil { +func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) { + if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil { Fatalf("Failed to register the Ethereum Stats service: %v", err) } } // RegisterGraphQLService is a utility function to construct a new service and register it against a node. -func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) { - // create new graphQL service - if err := graphql.New(stack, endpoint, cors, vhosts, timeouts); err != nil { +func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) { + if err := graphql.New(stack, backend, cfg.GraphQLEndpoint(), cfg.GraphQLCors, cfg.GraphQLVirtualHosts, cfg.HTTPTimeouts); err != nil { Fatalf("Failed to register the GraphQL service: %v", err) } } diff --git a/eth/api_backend.go b/eth/api_backend.go index a7122da2cba8..46f41e7cd678 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -31,8 +32,10 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -257,6 +260,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, return b.eth.TxPool().Content() } +func (b *EthAPIBackend) TxPool() *core.TxPool { + return b.eth.TxPool() +} + func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { return b.eth.TxPool().SubscribeNewTxsEvent(ch) } @@ -307,3 +314,27 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *EthAPIBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *EthAPIBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { + return b.eth.blockchain.GetBlockByNumber(number), nil +} + +func (b *EthAPIBackend) CurrentHeader() *types.Header { + return b.eth.blockchain.CurrentHeader() +} + +func (b *EthAPIBackend) Miner() *miner.Miner { + return b.eth.Miner() +} + +func (b *EthAPIBackend) StartMining(threads int) error { + return b.eth.StartMining(threads) +} + +func (b *EthAPIBackend) SetContractBackend(client *ethclient.Client) { + b.eth.SetContractBackend(client) +} diff --git a/eth/backend.go b/eth/backend.go index cfd90d4fe3fd..b9b356d20bee 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -138,7 +138,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) // Assemble the Ethereum object - chainDb, err := stack.ServiceContext.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") if err != nil { return nil, err } @@ -151,9 +151,9 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, chainDb: chainDb, - eventMux: stack.ServiceContext.EventMux, - accountManager: stack.ServiceContext.AccountManager, - engine: CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + eventMux: stack.EventMux(), + accountManager: stack.AccountManager(), + engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -208,7 +208,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth.bloomIndexer.Start(eth.blockchain) if config.TxPool.Journal != "" { - config.TxPool.Journal = stack.ServiceContext.ResolvePath(config.TxPool.Journal) + config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) @@ -224,14 +224,14 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.ServiceContext.ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&stack.ServiceContext.Config.P2P) + eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) if err != nil { return nil, err } @@ -261,7 +261,7 @@ func makeExtraData(extra []byte) []byte { } // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { +func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { // If proof-of-authority is requested, set it up if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) @@ -279,7 +279,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo return ethash.NewShared() default: engine := ethash.New(ethash.Config{ - CacheDir: ctx.ResolvePath(config.CacheDir), + CacheDir: stack.ResolvePath(config.CacheDir), CachesInMem: config.CachesInMem, CachesOnDisk: config.CachesOnDisk, CachesLockMmap: config.CachesLockMmap, @@ -540,15 +540,6 @@ func (s *Ethereum) Protocols() []p2p.Protocol { return protos } -// P2PServer registers the node's running p2p server with the Backend. -func (s *Ethereum) P2PServer(server *p2p.Server) error { - if server == nil { - return node.ErrNodeStopped // TODO is this error okay to return? - } - s.p2pServer = server - return nil -} - // Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index bc0305fc229d..e869f492e575 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -397,6 +397,14 @@ func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuer return ec.c.EthSubscribe(ctx, ch, "logs", arg) } +// ToFilterArg is for testing purposes only. +func ToFilterArg(q ethereum.FilterQuery, test bool) (interface{}, error) { + if test { + return toFilterArg(q) + } + return nil, fmt.Errorf("functionality reserved for testing") // TODO is this okay? +} + func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { arg := map[string]interface{}{ "address": q.Addresses, diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index b49abe917732..09cbdefa7e72 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package ethclient +package ethclient_test import ( "context" @@ -33,23 +33,24 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) // Verify that Client implements the ethereum interfaces. var ( - _ = ethereum.ChainReader(&Client{}) - _ = ethereum.TransactionReader(&Client{}) - _ = ethereum.ChainStateReader(&Client{}) - _ = ethereum.ChainSyncReader(&Client{}) - _ = ethereum.ContractCaller(&Client{}) - _ = ethereum.GasEstimator(&Client{}) - _ = ethereum.GasPricer(&Client{}) - _ = ethereum.LogFilterer(&Client{}) - _ = ethereum.PendingStateReader(&Client{}) + _ = ethereum.ChainReader(ðclient.Client{}) + _ = ethereum.TransactionReader(ðclient.Client{}) + _ = ethereum.ChainStateReader(ðclient.Client{}) + _ = ethereum.ChainSyncReader(ðclient.Client{}) + _ = ethereum.ContractCaller(ðclient.Client{}) + _ = ethereum.GasEstimator(ðclient.Client{}) + _ = ethereum.GasPricer(ðclient.Client{}) + _ = ethereum.LogFilterer(ðclient.Client{}) + _ = ethereum.PendingStateReader(ðclient.Client{}) // _ = ethereum.PendingStateEventer(&Client{}) - _ = ethereum.PendingContractCaller(&Client{}) + _ = ethereum.PendingContractCaller(ðclient.Client{}) ) func TestToFilterArg(t *testing.T) { @@ -163,7 +164,7 @@ func TestToFilterArg(t *testing.T) { }, } { t.Run(testCase.name, func(t *testing.T) { - output, err := toFilterArg(testCase.input) + output, err := ethclient.ToFilterArg(testCase.input, true) if (testCase.err == nil) != (err == nil) { t.Fatalf("expected error %v but got %v", testCase.err, err) } @@ -255,7 +256,7 @@ func TestHeader(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -304,7 +305,7 @@ func TestBalanceAt(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -325,7 +326,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) { defer backend.Stop() defer client.Close() - ec := NewClient(client) + ec := ethclient.NewClient(client) ctx, cancel := context.WithCancel(context.Background()) cancel() tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) @@ -342,7 +343,7 @@ func TestChainID(t *testing.T) { client, _ := backend.Attach() defer backend.Stop() defer client.Close() - ec := NewClient(client) + ec := ethclient.NewClient(client) id, err := ec.ChainID(context.Background()) if err != nil { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 0989a9862b12..2f8766ef8cca 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -36,7 +36,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -56,23 +56,15 @@ const ( chainHeadChanSize = 10 ) -type txPool interface { - // SubscribeNewTxsEvent should return an event subscription of - // NewTxsEvent and send events to the given channel. - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription -} - -type blockChain interface { - SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription -} +// Backend encompasses the backend behaviors needed for the ethstats service. +type Backend ethapi.Backend // Service implements an Ethereum netstats reporting daemon that pushes local // chain statistics up to a monitoring server. type Service struct { - server *p2p.Server // Peer-to-peer server to retrieve networking infos - eth *eth.Ethereum // Full Ethereum service if monitoring a full node - les *les.LightEthereum // Light Ethereum service if monitoring a light node - engine consensus.Engine // Consensus engine to retrieve variadic block fields + server *p2p.Server // Peer-to-peer server to retrieve networking infos + backend Backend + engine consensus.Engine // Consensus engine to retrieve variadic block fields node string // Name of the node to display on the monitoring page pass string // Password to authorize access to the monitoring page @@ -83,7 +75,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, url string) error { +func New(node *node.Node, backend Backend, engine consensus.Engine, url string) error { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) @@ -91,27 +83,14 @@ func New(node *node.Node, url string) error { return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) } ethstats := &Service{ - server: node.Server(), - node: parts[1], - pass: parts[3], - host: parts[4], - pongCh: make(chan struct{}), - histCh: make(chan []uint64, 1), - } - - // fetch backend - var ethServ *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðServ); err == nil { - ethstats.eth = ethServ - ethstats.engine = ethServ.Engine() - } - var lesServ *les.LightEthereum - if err := node.ServiceContext.Lifecycle(&lesServ); err == nil { - ethstats.les = lesServ - ethstats.engine = lesServ.Engine() - } - if ethstats.engine == nil { // TODO check to make sure at least one backend is not nil? - return fmt.Errorf("Ethereum service not found") + backend: backend, + engine: engine, + server: node.Server(), + node: parts[1], + pass: parts[3], + host: parts[4], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), } node.RegisterLifecycle(ethstats) @@ -136,22 +115,12 @@ func (s *Service) Stop() error { // until termination. func (s *Service) loop() { // Subscribe to chain events to execute updates on - var blockchain blockChain - var txpool txPool - if s.eth != nil { - blockchain = s.eth.BlockChain() - txpool = s.eth.TxPool() - } else { - blockchain = s.les.BlockChain() - txpool = s.les.TxPool() - } - chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) - headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh) + headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh) defer headSub.Unsubscribe() txEventCh := make(chan core.NewTxsEvent, txChanSize) - txSub := txpool.SubscribeNewTxsEvent(txEventCh) + txSub := s.backend.SubscribeNewTxsEvent(txEventCh) defer txSub.Unsubscribe() // Start a goroutine that exhausts the subscriptions to avoid events piling up @@ -560,29 +529,20 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { txs []txStats uncles []*types.Header ) - if s.eth != nil { - // Full nodes have all needed information available - if block == nil { - block = s.eth.BlockChain().CurrentBlock() - } - header = block.Header() - td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - txs = make([]txStats, len(block.Transactions())) - for i, tx := range block.Transactions() { - txs[i].Hash = tx.Hash() - } - uncles = block.Uncles() - } else { - // Light nodes would need on-demand lookups for transactions/uncles, skip - if block != nil { - header = block.Header() - } else { - header = s.les.BlockChain().CurrentHeader() - } - td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - txs = []txStats{} + // Full nodes have all needed information available + if block == nil { + block = s.backend.CurrentBlock() + } + header = block.Header() + td = s.backend.GetTd(header.Hash()) // TODO is it okay to just call `GetTD` with just the hash and not number? + + txs = make([]txStats, len(block.Transactions())) + for i, tx := range block.Transactions() { + txs[i].Hash = tx.Hash() } + uncles = block.Uncles() + // Assemble and return the block stats author, _ := s.engine.Author(header) @@ -613,12 +573,7 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { indexes = append(indexes, list...) } else { // No indexes requested, send back the top ones - var head int64 - if s.eth != nil { - head = s.eth.BlockChain().CurrentHeader().Number.Int64() - } else { - head = s.les.BlockChain().CurrentHeader().Number.Int64() - } + head := s.backend.CurrentHeader().Number.Int64() start := head - historyUpdateRange + 1 if start < 0 { start = 0 @@ -631,14 +586,11 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { history := make([]*blockStats, len(indexes)) for i, number := range indexes { // Retrieve the next block if it's known to us - var block *types.Block - if s.eth != nil { - block = s.eth.BlockChain().GetBlockByNumber(number) - } else { - if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil { - block = types.NewBlockWithHeader(header) - } + block, err := s.backend.GetBlockByNumber(context.Background(), number) + if err != nil { + return err } + // If we do have the block, add to the history and continue if block != nil { history[len(history)-1-i] = s.assembleBlockStats(block) @@ -673,12 +625,7 @@ type pendStats struct { // it to the stats server. func (s *Service) reportPending(conn *websocket.Conn) error { // Retrieve the pending count from the local blockchain - var pending int - if s.eth != nil { - pending, _ = s.eth.TxPool().Stats() - } else { - pending = s.les.TxPool().Stats() - } + pending, _ := s.backend.Stats() // Assemble the transaction stats and send it to the server log.Trace("Sending pending transactions to ethstats", "count", pending) @@ -712,22 +659,20 @@ func (s *Service) reportStats(conn *websocket.Conn) error { var ( mining bool hashrate int - syncing bool - gasprice int ) - if s.eth != nil { - mining = s.eth.Miner().Mining() - hashrate = int(s.eth.Miner().HashRate()) - sync := s.eth.Downloader().Progress() - syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock - - price, _ := s.eth.APIBackend.SuggestPrice(context.Background()) - gasprice = int(price.Uint64()) - } else { - sync := s.les.Downloader().Progress() - syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock + mine := s.backend.Miner() + if mine != nil { + mining = mine.Mining() + hashrate = int(s.backend.Miner().HashRate()) } + + sync := s.backend.Downloader().Progress() + syncing := s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock + + price, _ := s.backend.SuggestPrice(context.Background()) + gasprice := int(price.Uint64()) + // Assemble the node stats and send it to the server log.Trace("Sending node details to ethstats") diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 1c2a56ab0877..3b18cb5e670a 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -181,13 +181,13 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { func createGQLService(t *testing.T, stack *node.Node, endpoint string) { // create backend - _, err := eth.New(stack, ð.DefaultConfig) + ethBackend, err := eth.New(stack, ð.DefaultConfig) if err != nil { t.Fatalf("could not create eth backend: %v", err) } // create gql service - err = New(stack, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, ethBackend.APIBackend, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 8bec768ef6ef..f93f12a545e3 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,12 +17,9 @@ package graphql import ( - "errors" "net/http" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" @@ -30,19 +27,9 @@ import ( ) // New constructs a new GraphQL service instance. -func New(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { - // fetch backend - var backend ethapi.Backend - var ethServ *eth.Ethereum - if err := stack.ServiceContext.Lifecycle(ðServ); err == nil { - backend = ethServ.APIBackend - } - var lesServ *les.LightEthereum - if err := stack.ServiceContext.Lifecycle(&lesServ); err == nil { - backend = lesServ.ApiBackend - } +func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { if backend == nil { - return errors.New("No backend found") // TODO should this be a fatal error? + stack.Fatalf("missing backend") } // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 074cd794a65a..6d8374eb53c9 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -23,14 +23,17 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -53,9 +56,12 @@ type Backend interface { HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) + CurrentHeader() *types.Header + CurrentBlock() *types.Block BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) + GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) @@ -73,8 +79,13 @@ type Backend interface { GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) Stats() (pending int, queued int) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) + TxPool() *core.TxPool SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + // Mining API + Miner() *miner.Miner + StartMining(threads int) error + // Filter API BloomStatus() (uint64, uint64) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) @@ -84,7 +95,9 @@ type Backend interface { SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription ChainConfig() *params.ChainConfig - CurrentBlock() *types.Block + Engine() consensus.Engine + + SetContractBackend(client *ethclient.Client) } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/les/api_backend.go b/les/api_backend.go index 448260a19802..d905081077a6 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -19,10 +19,12 @@ package les import ( "context" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -31,9 +33,11 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -282,3 +286,32 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *LesApiBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *LesApiBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { + header := b.eth.blockchain.GetHeaderByNumber(number) + return types.NewBlockWithHeader(header), nil // TODO is this okay? +} + +func (b *LesApiBackend) CurrentHeader() *types.Header { + return b.eth.blockchain.CurrentHeader() +} + +func (b *LesApiBackend) Miner() *miner.Miner { + return nil +} + +func (b *LesApiBackend) StartMining(threads int) error { + return fmt.Errorf("Light clients do not support mining") // TODO is this okay? +} + +func (b *LesApiBackend) SetContractBackend(client *ethclient.Client) { + b.eth.SetContractBackend(client) +} + +func (b *LesApiBackend) TxPool() *core.TxPool { + return nil // TODO is this okay? +} diff --git a/les/client.go b/les/client.go index 3ed45c124b99..0ae6700d0bb4 100644 --- a/les/client.go +++ b/les/client.go @@ -78,11 +78,11 @@ type LightEthereum struct { } func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { - chainDb, err := stack.ServiceContext.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") + chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { return nil, err } - lespayDb, err := stack.ServiceContext.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + lespayDb, err := stack.OpenDatabase("lespay", 0, 0, "eth/db/lespay") if err != nil { return nil, err } @@ -103,10 +103,10 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { closeCh: make(chan struct{}), }, peers: peers, - eventMux: stack.ServiceContext.EventMux, + eventMux: stack.EventMux(), reqDist: newRequestDistributor(peers, &mclock.System{}), - accountManager: stack.ServiceContext.AccountManager, - engine: eth.CreateConsensusEngine(stack.ServiceContext, chainConfig, &config.Ethash, nil, false, chainDb), + accountManager: stack.AccountManager(), + engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), @@ -114,7 +114,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery(&stack.ServiceContext.Config.P2P) + dnsdisc, err := leth.setupDiscovery(&stack.Config().P2P) if err != nil { return nil, err } @@ -164,7 +164,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - leth.ApiBackend = &LesApiBackend{stack.ServiceContext.ExtRPCEnabled(), leth, nil} + leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 6f65ddddfca2..143ce3f5eb2e 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -22,6 +22,7 @@ package main import ( "bytes" "crypto/ecdsa" + "github.com/ethereum/go-ethereum/internal/ethapi" "io/ioutil" "math/big" "math/rand" @@ -59,12 +60,16 @@ func main() { genesis := makeGenesis(faucets, sealers) var ( - nodes []*node.Node + nodes []struct{ + node *node.Node + backend ethapi.Backend + } enodes []*enode.Node ) + for _, sealer := range sealers { // Start the node and wait until it's up - node, err := makeSealer(genesis) + node, ethBackend, err := makeSealer(genesis) if err != nil { panic(err) } @@ -78,7 +83,13 @@ func main() { node.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, node) + nodes = append(nodes, struct{ + node *node.Node + backend ethapi.Backend + }{ + node, + ethBackend, + }) enodes = append(enodes, node.Server().Self()) // Inject the signer key and start sealing with it @@ -95,11 +106,7 @@ func main() { time.Sleep(3 * time.Second) for _, node := range nodes { - var ethereum *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðereum); err != nil { - panic(err) - } - if err := ethereum.StartMining(1); err != nil { + if err := node.backend.StartMining(1); err != nil { panic(err) } } @@ -111,22 +118,24 @@ func main() { index := rand.Intn(len(faucets)) // Fetch the accessor for the relevant signer - var ethereum *eth.Ethereum - if err := nodes[index%len(nodes)].Service(ðereum); err != nil { - panic(err) - } + backend := nodes[index%len(nodes)].backend + // Create a self transaction and inject into the pool tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) if err != nil { panic(err) } - if err := ethereum.TxPool().AddLocal(tx); err != nil { + txpool := backend.TxPool() + if txpool == nil { + panic(fmt.Errorf("Ethereum service not running")) + } + if err := txpool.AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { + if pend, _ := txpool.Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } @@ -169,7 +178,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core return genesis } -func makeSealer(genesis *core.Genesis) (*node.Node, error) { +func makeSealer(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -213,5 +222,5 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) { stack.RegisterLifecycle(ethBackend) // Start the node and return if successful - return stack, stack.Start() + return stack, ethBackend, stack.Start() } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 6893dff6e273..4e0a75174d3f 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -61,12 +61,15 @@ func main() { genesis := makeGenesis(faucets) var ( - nodes []*node.Node + nodes []struct{ + node *node.Node + backend ethapi.Backend + } enodes []*enode.Node ) for i := 0; i < 4; i++ { // Start the node and wait until it's up - node, err := makeMiner(genesis) + node, ethBackend, err := makeMiner(genesis) if err != nil { panic(err) } @@ -80,7 +83,13 @@ func main() { node.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, node) + nodes = append(nodes, struct{ + node *node.Node + backend ethapi.Backend + }{ + node, + ethBackend, + }) enodes = append(enodes, node.Server().Self()) // Inject the signer key and start sealing with it @@ -93,11 +102,7 @@ func main() { time.Sleep(3 * time.Second) for _, node := range nodes { - var ethereum *eth.Ethereum - if err := node.ServiceContext.Lifecycle(ðereum); err != nil { - panic(err) - } - if err := ethereum.StartMining(1); err != nil { + if err := node.backend.StartMining(1); err != nil { panic(err) } } @@ -107,24 +112,24 @@ func main() { nonces := make([]uint64, len(faucets)) for { index := rand.Intn(len(faucets)) - // Fetch the accessor for the relevant signer - var ethereum *eth.Ethereum - if err := nodes[index%len(nodes)].Service(ðereum); err != nil { - panic(err) - } + backend := nodes[index%len(nodes)].backend // Create a self transaction and inject into the pool tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index]) if err != nil { panic(err) } - if err := ethereum.TxPool().AddLocal(tx); err != nil { + txpool := backend.TxPool() + if txpool == nil { + panic(fmt.Errorf("Ethereum service not running")) + } + if err := txpool.AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := ethereum.TxPool().Stats(); pend > 2048 { + if pend, _ := txpool.Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } @@ -149,7 +154,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { return genesis } -func makeMiner(genesis *core.Genesis) (*node.Node, error) { +func makeMiner(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -194,5 +199,5 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) { stack.RegisterLifecycle(ethBackend) // Start the node and return if successful - return stack, stack.Start() + return stack, ethBackend, stack.Start() } diff --git a/mobile/geth.go b/mobile/geth.go index 053c304f6738..22567a5d107b 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -175,20 +175,20 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - _, err = les.New(rawStack, ðConf) + lesBackend, err := les.New(rawStack, ðConf) if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } // If netstats reporting is requested, do it if config.EthereumNetStats != "" { - if err := ethstats.New(rawStack, config.EthereumNetStats); err != nil { + if err := ethstats.New(rawStack, lesBackend.ApiBackend, lesBackend.Engine(), config.EthereumNetStats); err != nil { return nil, fmt.Errorf("netstats init: %v", err) } } } // Register the Whisper protocol if requested if config.WhisperEnabled { - if err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { + if _, err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { return nil, fmt.Errorf("whisper init: %v", err) } } diff --git a/node/api.go b/node/api.go index ec9b836d7203..d6f1901907aa 100644 --- a/node/api.go +++ b/node/api.go @@ -159,7 +159,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if HTTP server already exists - if server, exists := api.node.HTTPServers.servers[endpoint]; exists { + if server, exists := api.node.httpServers[endpoint]; exists { if server.RPCAllowed { return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) } @@ -220,7 +220,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.HTTPServers.servers { + for _, httpServer := range api.node.httpServers { if httpServer.RPCAllowed { api.node.stopServer(httpServer) return true, nil @@ -235,7 +235,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() // check if an existing WS server already exists - for _, server := range api.node.HTTPServers.servers { + for _, server := range api.node.httpServers { if server.WSAllowed { return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) } @@ -253,7 +253,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if there is an existing server on the specified port, and if there is, enable ws on it - if server, exists := api.node.HTTPServers.servers[endpoint]; exists { + if server, exists := api.node.httpServers[endpoint]; exists { // else configure ws on the existing server server.WSAllowed = true // configure origins @@ -322,7 +322,7 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() - for _, httpServer := range api.node.HTTPServers.servers { + for _, httpServer := range api.node.httpServers { if httpServer.WSAllowed { httpServer.WSAllowed = false // if RPC is not enabled on the WS http server, shut it down diff --git a/node/lifecycle.go b/node/lifecycle.go new file mode 100644 index 000000000000..0d5f9a0680a0 --- /dev/null +++ b/node/lifecycle.go @@ -0,0 +1,31 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node + +// Lifecycle encompasses the behavior of services that can be started and stopped +// on the node. Lifecycle management is delegated to the node, but it is the +// responsibility of the service-specific package to configure and register the +// service on the node using the `RegisterLifecycle` method. +type Lifecycle interface { + // Start is called after all services have been constructed and the networking + // layer was also initialized to spawn any goroutines required by the service. + Start() error + + // Stop terminates all goroutines belonging to the service, blocking until they + // are all terminated. + Stop() error +} diff --git a/node/node.go b/node/node.go index f4c35254ac67..d347c7fbaa69 100644 --- a/node/node.go +++ b/node/node.go @@ -52,11 +52,9 @@ type Node struct { server *p2p.Server // Currently running P2P networking layer - ServiceContext *ServiceContext // TODO rename to LifecycleContext or just NodeContext? - lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - HTTPServers *HTTPServers // HTTPServers stores information about the node's rpc, ws, and graphQL http servers. + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. rpcAPIs []rpc.API // List of APIs currently provided by the node inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -109,13 +107,7 @@ func New(conf *Config) (*Node, error) { ephemeralKeystore: ephemeralKeystore, config: conf, lifecycles: make(map[reflect.Type]Lifecycle), - ServiceContext: &ServiceContext{ - Config: *conf, - Lifecycles: make(map[reflect.Type]Lifecycle), - }, - HTTPServers: &HTTPServers{ - servers: make(map[string]*HTTPServer), - }, + httpServers: make(serverMap), ipc: &HTTPServer{ endpoint: conf.IPCEndpoint(), }, @@ -138,9 +130,7 @@ func New(conf *Config) (*Node, error) { if node.server.Config.NodeDatabase == "" { node.server.Config.NodeDatabase = node.config.NodeDB() } - // Configure service context - node.ServiceContext.EventMux = node.eventmux - node.ServiceContext.AccountManager = node.accman + // Configure HTTP server(s) if conf.HTTPHost != "" { httpServ := &HTTPServer{ @@ -159,13 +149,14 @@ func New(conf *Config) (*Node, error) { httpServ.WSAllowed = true httpServ.WsOrigins = conf.WSOrigins httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) - node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ + + node.httpServers[conf.HTTPEndpoint()] = httpServ return node, nil } - node.HTTPServers.servers[conf.HTTPEndpoint()] = httpServ + node.httpServers[conf.HTTPEndpoint()] = httpServ } if conf.WSHost != "" { - node.HTTPServers.servers[conf.WSEndpoint()] = &HTTPServer{ + node.httpServers[conf.WSEndpoint()] = &HTTPServer{ WsOrigins: conf.WSOrigins, Whitelist: conf.WSModules, Srv: rpc.NewServer(), @@ -206,11 +197,10 @@ func (n *Node) Close() error { func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { kind := reflect.TypeOf(lifecycle) if _, exists := n.lifecycles[kind]; exists { - Fatalf("Lifecycle cannot be registered more than once", kind) + n.Fatalf("Lifecycle cannot be registered more than once", kind) } n.lifecycles[kind] = lifecycle - n.ServiceContext.Lifecycles[kind] = lifecycle } // RegisterProtocols adds backend's protocols to the node's p2p server @@ -225,12 +215,12 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { // RegisterHTTPServer registers the given HTTP server on the node func (n *Node) RegisterHTTPServer(endpoint string, server *HTTPServer) { - n.HTTPServers.servers[endpoint] = server + n.httpServers[endpoint] = server } // ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { - if server, exists := n.HTTPServers.servers[endpoint]; exists { + if server, exists := n.httpServers[endpoint]; exists { return server } return nil @@ -258,8 +248,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { httpSrv.WriteTimeout = h.Timeouts.WriteTimeout httpSrv.IdleTimeout = h.Timeouts.IdleTimeout } - - // complete the HTTPServers + // add listener and http.Server to HTTPServer h.Listener = listener h.Server = httpSrv @@ -294,7 +283,7 @@ func (n *Node) Start() error { // Configure the RPC interfaces if err := n.configureRPC(); err != nil { - n.HTTPServers.Stop() + n.httpServers.Stop() n.server.Stop() return err } @@ -310,10 +299,6 @@ func (n *Node) Start() error { started = append(started, lifecycle) } - // Finish initializing the service context - n.ServiceContext.AccountManager = n.accman - n.ServiceContext.EventMux = n.eventmux - // Finish initializing the startup n.stop = make(chan struct{}) return nil @@ -365,7 +350,7 @@ func (n *Node) configureRPC() error { return err } - for _, server := range n.HTTPServers.servers { + for _, server := range n.httpServers { // configure the handlers if server.RPCAllowed { server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) @@ -396,8 +381,8 @@ func (n *Node) configureRPC() error { } } // only register http server as a lifecycle if it has not already been registered - if _, exists := n.lifecycles[reflect.TypeOf(n.HTTPServers)]; !exists { - n.RegisterLifecycle(n.HTTPServers) + if _, exists := n.lifecycles[reflect.TypeOf(n.httpServers)]; !exists { + n.RegisterLifecycle(n.httpServers) } // All API endpoints started successfully return nil @@ -467,7 +452,7 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv = nil } // remove stopped http server from node's http servers // TODO is this preferable? - delete(n.HTTPServers.servers, server.endpoint) + delete(n.httpServers, server.endpoint) } // Stop terminates a running node along with all it's services. In the node was @@ -594,7 +579,7 @@ func (n *Node) WSEndpoint() string { n.lock.Lock() defer n.lock.Unlock() - for _, httpServer := range n.HTTPServers.servers { + for _, httpServer := range n.httpServers { if httpServer.WSAllowed { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() @@ -612,24 +597,6 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } -// Lifecycle retrieves a currently running Lifecycle registered of a specific type. -func (n *Node) Lifecycle(lifecycle interface{}) error { - n.lock.RLock() - defer n.lock.RUnlock() - - // Short circuit if the node's not running - if !n.running() { - return ErrNodeStopped - } - // Otherwise try to find the service to return - element := reflect.ValueOf(lifecycle).Elem() - if running, ok := n.lifecycles[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - // OpenDatabase opens an existing database with the given name (or creates one if no // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. @@ -712,11 +679,10 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server return nil } -// TODO change this when you figure out how else to do a nice fatal err // Fatalf formats a message to standard error and exits the program. // The message is also printed to standard output if standard error // is redirected to a different file. -func Fatalf(format string, args ...interface{}) { +func (n *Node) Fatalf(format string, args ...interface{}) { w := io.MultiWriter(os.Stdout, os.Stderr) if runtime.GOOS == "windows" { // The SameFile check below doesn't work on Windows. diff --git a/node/node_test.go b/node/node_test.go index e00c686058e4..d87a5612a5ce 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -364,47 +364,6 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stack.server.PrivateKey = testNodeKey } -// TestLifecycleRetrieval tests that individual services can be retrieved. -func TestLifecycleRetrieval(t *testing.T) { - // Create a simple stack and register two service types - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - noop := NewNoop() - stack.RegisterLifecycle(noop) - - is, err := NewInstrumentedService() - if err != nil { - t.Fatalf("instrumented service creation failed: %v", err) - } - stack.RegisterLifecycle(is) - - // Make sure none of the services can be retrieved until started - var noopServ *Noop - if err := stack.Lifecycle(&noopServ); err != ErrNodeStopped { - t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) - } - var instServ *InstrumentedService - if err := stack.Lifecycle(&instServ); err != ErrNodeStopped { - t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped) - } - // Start the stack and ensure everything is retrievable now - if err := stack.Start(); err != nil { - t.Fatalf("failed to start stack: %v", err) - } - defer stack.Stop() - - if err := stack.Lifecycle(&noopServ); err != nil { - t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil) - } - if err := stack.Lifecycle(&instServ); err != nil { - t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil) - } -} - // Tests whether a given HTTPServer can be registered on the node func TestRegisterHTTPServer(t *testing.T) { stack, err := New(testNodeConfig()) @@ -449,15 +408,21 @@ func TestRegisterHTTPServer(t *testing.T) { func TestHTTPServerCreateAndStop(t *testing.T) { // test on same ports node1 := startHTTP(t, 7453, 7453) - if len(node1.HTTPServers.servers) != 1 { + if len(node1.httpServers) != 1 { t.Fatalf("node has more than 1 http server") } // check to make sure http servers are registered - var httpSrv1 *HTTPServers - if err := node1.Lifecycle(&httpSrv1); err != nil { - t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + var exists bool + for _, lifecycle := range node1.lifecycles { + if reflect.DeepEqual(node1.httpServers, lifecycle) { + exists = true + } + } + if !exists { + t.Fatal("HTTP servers not registered as lifecycles on the node") } - for _, server := range node1.HTTPServers.servers { + // check to make sure http servers are configured properly + for _, server := range node1.httpServers { if !(server.WSAllowed && server.RPCAllowed) { t.Fatalf("node's http server is not configured to handle both rpc and ws") } @@ -466,21 +431,25 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("failed to remove server %v from node after stopping it", server) } } - node1.Close() // test on separate ports node2 := startHTTP(t, 7453, 9393) - if len(node2.HTTPServers.servers) != 2 { + if len(node2.httpServers) != 2 { t.Fatalf("amount of http servers on the node is not equal to 2") } // check to make sure http servers are registered - var httpSrv2 *HTTPServers - if err := node2.Lifecycle(&httpSrv2); err != nil { - t.Fatalf("HTTP servers not registered as lifecycles on the node: %v", err) + exists = false + for _, lifecycle := range node2.lifecycles { + if reflect.DeepEqual(node2.httpServers, lifecycle) { + exists = true + } + } + if !exists { + t.Fatal("HTTP servers not registered as lifecycles on the node") } // check that neither http server has both ws and rpc enabled - for _, server := range node2.HTTPServers.servers { + for _, server := range node2.httpServers { if server.WSAllowed && server.RPCAllowed { t.Fatalf("both rpc and ws allowed on a single http server") } diff --git a/node/rpcstack.go b/node/rpcstack.go index 3ceab681e687..d12089274b8d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -32,9 +32,7 @@ import ( "github.com/rs/cors" ) -type HTTPServers struct { - servers map[string]*HTTPServer // Stores information about all http servers (if any) by their port, including http, ws, and graphql -} +type serverMap map[string]*HTTPServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql type HTTPServer struct { handler http.Handler @@ -55,23 +53,23 @@ type HTTPServer struct { Timeouts rpc.HTTPTimeouts RPCAllowed bool - WSAllowed bool + WSAllowed bool // TODO discuss this later bc possible race condition GQLAllowed bool GQLHandler http.Handler } -func (h *HTTPServers) Start() error { - for _, server := range h.servers { +func (sm serverMap) Start() error { + for _, server := range sm { if err := server.Start(); err != nil { - return h.Stop() + return sm.Stop() } } return nil } -func (h *HTTPServers) Stop() error { - for _, server := range h.servers { +func (sm serverMap) Stop() error { + for _, server := range sm { if err := server.Stop(); err != nil { return err } @@ -79,14 +77,14 @@ func (h *HTTPServers) Stop() error { return nil } -// Start starts the HTTPServers's HTTP server. // TODO I don't like the way this is written +// Start starts the serverMap's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Start() error { go h.Server.Serve(h.Listener) log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } -// Stop shuts down the HTTPServers's HTTP server. // TODO I don't like the way this is written +// Stop shuts down the serverMap's HTTP server. // TODO I don't like the way this is written func (h *HTTPServer) Stop() error { if h.Server != nil { url := fmt.Sprintf("http://%v/", h.Listener.Addr()) @@ -102,12 +100,12 @@ func (h *HTTPServer) Stop() error { return nil } -// SetHandler assigns the given handler to the HTTPServers. +// SetHandler assigns the given handler to the serverMap. func (h *HTTPServer) SetHandler(handler http.Handler) { h.handler = handler } -// SetEndpoints assigns the given endpoint to the HTTPServers. +// SetEndpoints assigns the given endpoint to the serverMap. func (h *HTTPServer) SetEndpoint(endpoint string) { h.endpoint = endpoint } @@ -224,6 +222,7 @@ func newGzipHandler(next http.Handler) http.Handler { // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { + // TODO make sure you protect the pointer return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if hs.WSAllowed && isWebsocket(r) { ws.ServeHTTP(w, r) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index dc75afa93438..799b138a6917 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -40,7 +40,7 @@ func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { assert.Equal(t, "websocket", response.Header.Get("Upgrade")) } -// Tests that a ws handler can be added to and enabled on an existing HTTPServers +// Tests that a ws handler can be added to and enabled on an existing HTTPServer func TestWSAllowed(t *testing.T) { stack, err := New(&Config{ HTTPHost: DefaultHTTPHost, diff --git a/node/service.go b/node/service.go deleted file mode 100644 index e7746e92262c..000000000000 --- a/node/service.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node - -import ( - "path/filepath" - "reflect" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" -) - -// ServiceContext is a collection of service independent options inherited from -// the protocol stack, that is passed to all constructors to be optionally used; -// as well as utility methods to operate on the service environment. -type ServiceContext struct { - Config Config - Lifecycles map[reflect.Type]Lifecycle // TODO should this be in the service context or should it be on the node itself .. ? - EventMux *event.TypeMux // Event multiplexer used for decoupled notifications - AccountManager *accounts.Manager // Account manager created by the node. -} - -// OpenDatabase opens an existing database with the given name (or creates one -// if no previous can be found) from within the node's data directory. If the -// node is an ephemeral one, a memory database is returned. -func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) { - if ctx.Config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace) -} - -// OpenDatabaseWithFreezer opens an existing database with the given name (or -// creates one if no previous can be found) from within the node's data directory, -// also attaching a chain freezer to it that moves ancient chain data from the -// database to immutable append-only files. If the node is an ephemeral one, a -// memory database is returned. -func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { - if ctx.Config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - root := ctx.Config.ResolvePath(name) - - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = ctx.Config.ResolvePath(freezer) - } - return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) -} - -// ResolvePath resolves a user path into the data directory if that was relative -// and if the user actually uses persistent storage. It will return an empty string -// for emphemeral storage and the user's own input for absolute paths. -func (ctx *ServiceContext) ResolvePath(path string) string { - return ctx.Config.ResolvePath(path) -} - -// Lifecycle retrieves a currently running Lifecycle registered of a specific type. -func (ctx *ServiceContext) Lifecycle(lifecycle interface{}) error { - element := reflect.ValueOf(lifecycle).Elem() - if running, ok := ctx.Lifecycles[element.Type()]; ok { - element.Set(reflect.ValueOf(running)) - return nil - } - return ErrServiceUnknown -} - -// ExtRPCEnabled returns the indicator whether node enables the external -// RPC(http, ws or graphql). -func (ctx *ServiceContext) ExtRPCEnabled() bool { - return ctx.Config.ExtRPCEnabled() -} - -// Lifecycle encompasses the behavior of services that can be started and stopped -// on the node. Lifecycle management is delegated to the node, but it is the -// responsibility of the service-specific package to configure and register the -// service on the node using the `RegisterLifecycle` method. -type Lifecycle interface { - // Start is called after all services have been constructed and the networking - // layer was also initialized to spawn any goroutines required by the service. - Start() error - - // Stop terminates all goroutines belonging to the service, blocking until they - // are all terminated. - Stop() error -} diff --git a/node/service_test.go b/node/service_test.go deleted file mode 100644 index 80f40aeeb68b..000000000000 --- a/node/service_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package node - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" -) - -// Tests that databases are correctly created persistent or ephemeral based on -// the configured service context. -func TestContextDatabases(t *testing.T) { - // Create a temporary folder and ensure no database is contained within - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("failed to create temporary data directory: %v", err) - } - defer os.RemoveAll(dir) - - if _, err := os.Stat(filepath.Join(dir, "database")); err == nil { - t.Fatalf("non-created database already exists") - } - // Request the opening/creation of a database and ensure it persists to disk - ctx := &ServiceContext{Config: Config{Name: "unit-test", DataDir: dir}} - db, err := ctx.OpenDatabase("persistent", 0, 0, "") - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - db.Close() - - if _, err := os.Stat(filepath.Join(dir, "unit-test", "persistent")); err != nil { - t.Fatalf("persistent database doesn't exists: %v", err) - } - // Request th opening/creation of an ephemeral database and ensure it's not persisted - ctx = &ServiceContext{Config: Config{DataDir: ""}} - db, err = ctx.OpenDatabase("ephemeral", 0, 0, "") - if err != nil { - t.Fatalf("failed to open ephemeral database: %v", err) - } - db.Close() - - if _, err := os.Stat(filepath.Join(dir, "ephemeral")); err == nil { - t.Fatalf("ephemeral database exists") - } -} - -// Tests that already constructed Lifecycles can be retrieved by later ones. -func TestContextLifecycles(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - // Define a verifier that ensures a NoopA is before it and NoopB after - - noop := NewNoop() - stack.RegisterLifecycle(noop) - - is, err := NewInstrumentedService() - if err != nil { - t.Fatalf("could not create instrumented service %v", err) - - } - is.startHook = func() { - if err := stack.ServiceContext.Lifecycle(&noop); err != nil { - t.Errorf("former service not found: %v", err) - } - } - stack.RegisterLifecycle(is) - - // Start the protocol stack and ensure services are constructed in order - if err := stack.Start(); err != nil { - t.Fatalf("failed to start stack: %v", err) - } - - defer stack.Stop() -} diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 2a6d3aeaaa7e..1f7a21470db0 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -443,9 +443,8 @@ func startExecNodeStack() (*node.Node, error) { return nil, fmt.Errorf("unknown node service %q", err) } ctx := &ServiceContext{ - RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, - NodeContext: stack.ServiceContext, - Config: conf.Node, + RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, + Config: conf.Node, } if conf.Snapshots != nil { ctx.Snapshot = conf.Snapshots[name] diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 48c5bd9753f2..c786b9f9ddc1 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -250,9 +250,8 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { sn.registerOnce.Do(func() { for _, name := range sn.config.Lifecycles { ctx := &ServiceContext{ - RPCDialer: sn.adapter, - NodeContext: sn.node.ServiceContext, - Config: sn.config, + RPCDialer: sn.adapter, + Config: sn.config, } if snapshots != nil { ctx.Snapshot = snapshots[name] diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 1ca60f4b9b58..716cde6a6c7d 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -233,9 +233,8 @@ func assignTCPPort() (uint16, error) { type ServiceContext struct { RPCDialer - NodeContext *node.ServiceContext - Config *NodeConfig - Snapshot []byte + Config *NodeConfig + Snapshot []byte } // RPCDialer is used when initialising services which need to connect to diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 959a1e32f629..069ec97d09b3 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -91,10 +91,10 @@ func TestMailServer(t *testing.T) { var server WMailServer - stack := newNode(t) + stack, w := newNode(t) defer stack.Close() + shh = w - shh = getWhisperFromNode(stack, t) shh.RegisterServer(&server) err = server.Init(shh, dir, password, powRequirement) @@ -218,12 +218,12 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { // newNode creates a new node using a default config and // creates and registers a new Whisper service on it. -func newNode(t *testing.T) *node.Node { +func newNode(t *testing.T) (*node.Node, *whisper.Whisper) { stack, err := node.New(&node.DefaultConfig) if err != nil { t.Fatalf("could not create new node: %v", err) } - err = whisper.New(stack, &whisper.DefaultConfig) + w, err := whisper.New(stack, &whisper.DefaultConfig) if err != nil { t.Fatalf("could not create new whisper service: %v", err) } @@ -231,15 +231,5 @@ func newNode(t *testing.T) *node.Node { if err != nil { t.Fatalf("could not start node: %v", err) } - return stack -} - -// getWhisperFromNode retrieves the Whisper service from the running node. -func getWhisperFromNode(stack *node.Node, t *testing.T) *whisper.Whisper { - var w *whisper.Whisper - err := stack.Lifecycle(&w) - if err != nil { - t.Fatalf("could not get whisper service from node: %v", err) - } - return w + return stack, w } diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go index 9aceaf456d6b..759ef221ed8d 100644 --- a/whisper/whisperv6/api_test.go +++ b/whisper/whisperv6/api_test.go @@ -23,11 +23,9 @@ import ( ) func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - keyID, err := w.GenerateSymKey() if err != nil { t.Fatalf("Error generating symmetric key: %v", err) diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go index 49c2b7688361..c95e506972dd 100644 --- a/whisper/whisperv6/filter_test.go +++ b/whisper/whisperv6/filter_test.go @@ -93,11 +93,9 @@ func TestInstallFilters(t *testing.T) { const SizeTestFilters = 256 - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) tst := generateTestCases(t, SizeTestFilters) @@ -135,11 +133,9 @@ func TestInstallFilters(t *testing.T) { func TestInstallSymKeyGeneratesHash(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter, _ := generateFilter(t, true) @@ -166,11 +162,9 @@ func TestInstallSymKeyGeneratesHash(t *testing.T) { func TestInstallIdenticalFilters(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -240,11 +234,9 @@ func TestInstallIdenticalFilters(t *testing.T) { func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) filter1, _ := generateFilter(t, true) @@ -658,11 +650,9 @@ func TestWatchers(t *testing.T) { var x, firstID string var err error - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - filters := NewFilters(w) tst := generateTestCases(t, NumFilters) for i = 0; i < NumFilters; i++ { diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index 5799919db967..ac610367050c 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -94,7 +94,7 @@ type Whisper struct { } // New creates a Whisper client ready to communicate through the Ethereum P2P network. -func New(stack *node.Node, cfg *Config) error { +func New(stack *node.Node, cfg *Config) (*Whisper, error) { if cfg == nil { cfg = &DefaultConfig } @@ -136,7 +136,7 @@ func New(stack *node.Node, cfg *Config) error { stack.RegisterAPIs(whisper.APIs()) stack.RegisterProtocols(whisper.Protocols()) stack.RegisterLifecycle(whisper) - return nil + return whisper, nil } // MinPow returns the PoW value required by this node. diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go index a8056d6776e1..7fb8f7c1cd85 100644 --- a/whisper/whisperv6/whisper_test.go +++ b/whisper/whisperv6/whisper_test.go @@ -30,10 +30,9 @@ import ( ) func TestWhisperBasic(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - // get whisper service from node - w := getWhisperFromNode(stack, t) + shh := w.Protocols()[0] if shh.Name != ProtocolName { t.Fatalf("failed Protocol Name: %v.", shh.Name) @@ -114,11 +113,10 @@ func TestWhisperBasic(t *testing.T) { } func TestWhisperAsymmetricKeyImport(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() var privateKeys []*ecdsa.PrivateKey - w := getWhisperFromNode(stack, t) for i := 0; i < 50; i++ { id, err := w.NewKeyPair() if err != nil { @@ -145,10 +143,9 @@ func TestWhisperAsymmetricKeyImport(t *testing.T) { } func TestWhisperIdentityManagement(t *testing.T) { - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) id1, err := w.NewKeyPair() if err != nil { t.Fatalf("failed to generate new key pair: %s.", err) @@ -272,11 +269,9 @@ func TestWhisperSymKeyManagement(t *testing.T) { id2 = string("arbitrary-string-2") ) - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - id1, err := w.GenerateSymKey() if err != nil { t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) @@ -461,11 +456,9 @@ func TestWhisperSymKeyManagement(t *testing.T) { func TestExpiry(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - w.SetMinimumPowTest(0.0000001) defer w.SetMinimumPowTest(DefaultMinimumPoW) w.Start() @@ -530,11 +523,9 @@ func TestExpiry(t *testing.T) { func TestCustomization(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -626,11 +617,9 @@ func TestCustomization(t *testing.T) { func TestSymmetricSendCycle(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -720,11 +709,9 @@ func TestSymmetricSendCycle(t *testing.T) { func TestSymmetricSendWithoutAKey(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -793,11 +780,9 @@ func TestSymmetricSendWithoutAKey(t *testing.T) { func TestSymmetricSendKeyMismatch(t *testing.T) { InitSingleTest() - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - defer w.SetMinimumPowTest(DefaultMinimumPoW) defer w.SetMaxMessageSize(DefaultMaxMessageSize) w.Start() @@ -907,11 +892,9 @@ func TestBloom(t *testing.T) { t.Fatal("bloomFilterMatch false negative") } - stack := newNode(t) + stack, w := newNodeWithWhisper(t) defer stack.Close() - w := getWhisperFromNode(stack, t) - f := w.BloomFilter() if f != nil { t.Fatal("wrong bloom on creation") @@ -926,14 +909,14 @@ func TestBloom(t *testing.T) { } } -// newNode creates a new node using a default config and +// newNodeWithWhisper creates a new node using a default config and // creates and registers a new Whisper service on it. -func newNode(t *testing.T) *node.Node { +func newNodeWithWhisper(t *testing.T) (*node.Node, *Whisper) { stack, err := node.New(&node.DefaultConfig) if err != nil { t.Fatalf("could not create new node: %v", err) } - err = New(stack, &DefaultConfig) + w, err := New(stack, &DefaultConfig) if err != nil { t.Fatalf("could not create new whisper service: %v", err) } @@ -941,15 +924,5 @@ func newNode(t *testing.T) *node.Node { if err != nil { t.Fatalf("could not start node: %v", err) } - return stack -} - -// getWhisperFromNode retrieves the Whisper service from the running node. -func getWhisperFromNode(stack *node.Node, t *testing.T) *Whisper { - var w *Whisper - err := stack.Lifecycle(&w) - if err != nil { - t.Fatalf("could not get whisper service from node: %v", err) - } - return w + return stack, w } From d49a5be06105b2ee819fbd117687919af7aa04c5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 30 Jun 2020 14:57:32 +0200 Subject: [PATCH 071/160] attempt at fixing http test (#24) --- go.sum | 14 -------------- graphql/graphql_test.go | 14 ++++++-------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/go.sum b/go.sum index 2bb6d1e16912..acb104673937 100644 --- a/go.sum +++ b/go.sum @@ -193,44 +193,30 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 3b18cb5e670a..f89ef8f04f88 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -30,8 +30,6 @@ import ( "github.com/stretchr/testify/assert" ) -var testEndpoint = "127.0.0.1:9393" - func TestBuildSchema(t *testing.T) { // Make sure the schema can be parsed and matched up to the object model. if _, err := newHandler(nil); err != nil { @@ -48,7 +46,7 @@ func TestGQLAllowed(t *testing.T) { t.Fatalf("could not start node: %v", err) } // check that server was created - server := stack.ExistingHTTPServer(testEndpoint) + server := stack.ExistingHTTPServer("127.0.0.1:9393") if server == nil { t.Errorf("server was not created on the given endpoint") } @@ -64,7 +62,7 @@ func TestMultiplexedServer(t *testing.T) { if err := stack.Start(); err != nil { t.Error("could not start http service on node ", err) } - server := stack.ExistingHTTPServer(testEndpoint) + server := stack.ExistingHTTPServer("127.0.0.1:9393") if server == nil { t.Fatalf("server was not configured on the given endpoint") } @@ -83,7 +81,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { } // create http request body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", testEndpoint), body) + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) if err != nil { t.Error("could not issue new http request ", err) } @@ -107,14 +105,14 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { t.Fatalf("could not start node: %v", err) } // make sure GQL is not enabled - server := stack.ExistingHTTPServer(testEndpoint) + server := stack.ExistingHTTPServer("127.0.0.1:9797") if server == nil { t.Fatalf("server was not created on the given endpoint") } assert.False(t, server.GQLAllowed) // create http request body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", testEndpoint), body) + gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9797"), body) if err != nil { t.Error("could not issue new http request ", err) } @@ -174,7 +172,7 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { return stack } - createGQLService(t, stack, testEndpoint) + createGQLService(t, stack, "127.0.0.1:9393") return stack } From 52896aeeb7d2bea26f8eec35a36f41dca6c558e1 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 30 Jun 2020 21:47:40 +0200 Subject: [PATCH 072/160] fixed broken http test, waiting whether it passes on all builds --- graphql/graphql_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index f89ef8f04f88..c8c1d6e6be90 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -105,14 +105,14 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { t.Fatalf("could not start node: %v", err) } // make sure GQL is not enabled - server := stack.ExistingHTTPServer("127.0.0.1:9797") + server := stack.ExistingHTTPServer("127.0.0.1:9393") if server == nil { t.Fatalf("server was not created on the given endpoint") } assert.False(t, server.GQLAllowed) // create http request body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9797"), body) + gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) if err != nil { t.Error("could not issue new http request ", err) } From dce47808d4dfdc745030d2202bcf1f031da51138 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 2 Jul 2020 16:56:40 +0200 Subject: [PATCH 073/160] use int32 instead of bool when checking whether to handle requests (#25) --- graphql/graphql_test.go | 65 ----------------------------------------- graphql/service.go | 8 ++--- node/api.go | 19 ++++++------ node/node.go | 35 +++++++++------------- node/node_test.go | 6 ++-- node/rpcstack.go | 8 ++--- node/rpcstack_test.go | 7 +++-- 7 files changed, 40 insertions(+), 108 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index c8c1d6e6be90..08c1e4c8b576 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -37,40 +37,6 @@ func TestBuildSchema(t *testing.T) { } } -// Tests that a graphql handler can be added to an existing HTTPServer -func TestGQLAllowed(t *testing.T) { - stack := createNode(t, true) - defer stack.Close() - // start node - if err := stack.Start(); err != nil { - t.Fatalf("could not start node: %v", err) - } - // check that server was created - server := stack.ExistingHTTPServer("127.0.0.1:9393") - if server == nil { - t.Errorf("server was not created on the given endpoint") - } - // assert that server allows GQL requests - assert.True(t, server.GQLAllowed) -} - -// Tests to make sure an HTTPServer is created that handles for http, ws, and graphQL -func TestMultiplexedServer(t *testing.T) { - stack := createNode(t, true) - defer stack.Close() - // start the node - if err := stack.Start(); err != nil { - t.Error("could not start http service on node ", err) - } - server := stack.ExistingHTTPServer("127.0.0.1:9393") - if server == nil { - t.Fatalf("server was not configured on the given endpoint") - } - assert.True(t, server.RPCAllowed) - assert.True(t, server.WSAllowed) - assert.True(t, server.GQLAllowed) -} - // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { stack := createNode(t, true) @@ -109,7 +75,6 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if server == nil { t.Fatalf("server was not created on the given endpoint") } - assert.False(t, server.GQLAllowed) // create http request body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) @@ -128,36 +93,6 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, string(bodyBytes), expected) } -// Tests that graphql can be successfully enabled on a separate port than rpc and ws. -func TestGraphqlOnSeparatePort(t *testing.T) { - stack := createNode(t, false) - defer stack.Close() - - separateTestEndpoint := "127.0.0.1:7474" - - createGQLService(t, stack, separateTestEndpoint) - // start node - if err := stack.Start(); err != nil { - t.Fatalf("could not start node: %v", err) - } - // create http request - body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", separateTestEndpoint), body) - if err != nil { - t.Error("could not issue new http request ", err) - } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) - } - expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" - assert.Equal(t, expected, string(bodyBytes)) - -} - func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", diff --git a/graphql/service.go b/graphql/service.go index f93f12a545e3..5c3d872842d9 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,13 +17,12 @@ package graphql import ( - "net/http" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" + "net/http" ) // New constructs a new GraphQL service instance. @@ -34,7 +33,7 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts // check if http server with given endpoint exists and enable graphQL on it server := stack.ExistingHTTPServer(endpoint) if server != nil { - server.GQLAllowed = true + // set vhosts, cors and timeouts server.Vhosts = append(server.Vhosts, vhosts...) server.CorsAllowedOrigins = append(server.CorsAllowedOrigins, cors...) server.Timeouts = timeouts @@ -54,10 +53,11 @@ func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts } // create the http server gqlServer := &node.HTTPServer{ + RPCAllowed: 0, + WSAllowed: 0, Vhosts: vhosts, CorsAllowedOrigins: cors, Timeouts: timeouts, - GQLAllowed: true, GQLHandler: handler, Srv: rpc.NewServer(), } diff --git a/node/api.go b/node/api.go index d6f1901907aa..e359357bd016 100644 --- a/node/api.go +++ b/node/api.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "strings" + "sync/atomic" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -160,7 +161,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis endpoint := fmt.Sprintf("%s:%d", *host, *port) // check if HTTP server already exists if server, exists := api.node.httpServers[endpoint]; exists { - if server.RPCAllowed { + if atomic.LoadInt32(&server.RPCAllowed) == 1 { return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) } } @@ -221,7 +222,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { defer api.node.lock.Unlock() for _, httpServer := range api.node.httpServers { - if httpServer.RPCAllowed { + if atomic.LoadInt32(&httpServer.RPCAllowed) == 1 { api.node.stopServer(httpServer) return true, nil } @@ -236,7 +237,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str defer api.node.lock.Unlock() // check if an existing WS server already exists for _, server := range api.node.httpServers { - if server.WSAllowed { + if atomic.LoadInt32(&server.WSAllowed) == 1 { return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) } } @@ -255,7 +256,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // check if there is an existing server on the specified port, and if there is, enable ws on it if server, exists := api.node.httpServers[endpoint]; exists { // else configure ws on the existing server - server.WSAllowed = true + atomic.AddInt32(&server.WSAllowed, 1) // configure origins origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -280,7 +281,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // check if an HTTP server exists on the given endpoint, and if so, enable websocket on that HTTP server existingServer := api.node.ExistingHTTPServer(endpoint) if existingServer != nil { - existingServer.WSAllowed = true + atomic.AddInt32(&existingServer.WSAllowed, 1) existingServer.WsOrigins = origins } @@ -300,7 +301,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str port: *port, Whitelist: modules, WsOrigins: origins, - WSAllowed: true, + WSAllowed: 1, } // create handler wsServer.handler = wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) @@ -323,10 +324,10 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { defer api.node.lock.Unlock() for _, httpServer := range api.node.httpServers { - if httpServer.WSAllowed { - httpServer.WSAllowed = false + if atomic.LoadInt32(&httpServer.WSAllowed) == 1 { + atomic.AddInt32(&httpServer.WSAllowed, int32(-1)) // if RPC is not enabled on the WS http server, shut it down - if !httpServer.RPCAllowed && !httpServer.GQLAllowed { // TODO is the gql check necessary? Can GQL ever be on a WS server that doesn't also support regular http? + if atomic.LoadInt32(&httpServer.RPCAllowed) == 0 { api.node.stopServer(httpServer) return true, nil } diff --git a/node/node.go b/node/node.go index d347c7fbaa69..196d83f7d953 100644 --- a/node/node.go +++ b/node/node.go @@ -29,6 +29,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" @@ -142,11 +143,11 @@ func New(conf *Config) (*Node, error) { endpoint: conf.HTTPEndpoint(), host: conf.HTTPHost, port: conf.HTTPPort, - RPCAllowed: true, + RPCAllowed: 1, } // check if ws is enabled and if ws port is the same as http port if conf.WSHost != "" && conf.WSPort == conf.HTTPPort { - httpServ.WSAllowed = true + httpServ.WSAllowed = 1 httpServ.WsOrigins = conf.WSOrigins httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) @@ -157,13 +158,13 @@ func New(conf *Config) (*Node, error) { } if conf.WSHost != "" { node.httpServers[conf.WSEndpoint()] = &HTTPServer{ - WsOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - WSAllowed: true, + WsOrigins: conf.WSOrigins, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, + WSAllowed: 1, } } @@ -352,29 +353,21 @@ func (n *Node) configureRPC() error { for _, server := range n.httpServers { // configure the handlers - if server.RPCAllowed { + if atomic.LoadInt32(&server.RPCAllowed) == 1 { server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) // wrap ws handler just in case ws is enabled through the console after start-up wsHandler := server.Srv.WebsocketHandler(server.WsOrigins) server.handler = server.NewWebsocketUpgradeHandler(server.handler, wsHandler) n.log.Info("HTTP configured on endpoint ", "endpoint", server.endpoint) - if server.WSAllowed { + if atomic.LoadInt32(&server.WSAllowed) == 1 { n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) } } - if server.WSAllowed && server.handler == nil { + if (atomic.LoadInt32(&server.WSAllowed) == 1) && server.handler == nil { server.handler = server.Srv.WebsocketHandler(server.WsOrigins) n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) } - if server.GQLAllowed { - if server.handler == nil { - server.handler = server.GQLHandler - } else { - server.handler = NewGQLUpgradeHandler(server.handler, server.GQLHandler) - } - n.log.Info("GraphQL configured on endpoint ", "endpoint", server.endpoint) - } // create the HTTP server if err := n.CreateHTTPServer(server, false); err != nil { return err @@ -580,7 +573,7 @@ func (n *Node) WSEndpoint() string { defer n.lock.Unlock() for _, httpServer := range n.httpServers { - if httpServer.WSAllowed { + if atomic.LoadInt32(&httpServer.WSAllowed) == 1 { if httpServer.Listener != nil { return httpServer.Listener.Addr().String() } diff --git a/node/node_test.go b/node/node_test.go index d87a5612a5ce..bb1543650894 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -23,6 +23,7 @@ import ( "net/http" "os" "reflect" + "sync/atomic" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -423,7 +424,8 @@ func TestHTTPServerCreateAndStop(t *testing.T) { } // check to make sure http servers are configured properly for _, server := range node1.httpServers { - if !(server.WSAllowed && server.RPCAllowed) { + + if atomic.LoadInt32(&server.WSAllowed) == 0 && atomic.LoadInt32(&server.RPCAllowed) == 0 { t.Fatalf("node's http server is not configured to handle both rpc and ws") } node1.stopServer(server) @@ -450,7 +452,7 @@ func TestHTTPServerCreateAndStop(t *testing.T) { } // check that neither http server has both ws and rpc enabled for _, server := range node2.httpServers { - if server.WSAllowed && server.RPCAllowed { + if atomic.LoadInt32(&server.WSAllowed) == 1 && atomic.LoadInt32(&server.RPCAllowed) == 1 { t.Fatalf("both rpc and ws allowed on a single http server") } node2.stopServer(server) diff --git a/node/rpcstack.go b/node/rpcstack.go index d12089274b8d..32e4cd74d79d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -26,6 +26,7 @@ import ( "net/http" "strings" "sync" + "sync/atomic" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -52,9 +53,8 @@ type HTTPServer struct { WsOrigins []string Timeouts rpc.HTTPTimeouts - RPCAllowed bool - WSAllowed bool // TODO discuss this later bc possible race condition - GQLAllowed bool + RPCAllowed int32 + WSAllowed int32 GQLHandler http.Handler } @@ -224,7 +224,7 @@ func newGzipHandler(next http.Handler) http.Handler { func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { // TODO make sure you protect the pointer return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if hs.WSAllowed && isWebsocket(r) { + if atomic.LoadInt32(&hs.WSAllowed) == 1 && isWebsocket(r) { ws.ServeHTTP(w, r) log.Debug("serving websocket request") return diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 799b138a6917..f745e350afe9 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "sync/atomic" "testing" "github.com/ethereum/go-ethereum/rpc" @@ -13,7 +14,7 @@ import ( func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { h := &HTTPServer{ Srv: rpc.NewServer(), - WSAllowed: true, + WSAllowed: 1, } handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) ts := httptest.NewServer(handler) @@ -63,6 +64,6 @@ func TestWSAllowed(t *testing.T) { t.Fatalf("server was not started on the given endpoint: %v", err) } // assert that both RPC and WS are allowed on the HTTP Server - assert.True(t, server.RPCAllowed) - assert.True(t, server.WSAllowed) + assert.Equal(t, atomic.LoadInt32(&server.RPCAllowed), int32(1)) + assert.Equal(t, atomic.LoadInt32(&server.WSAllowed), int32(1)) } From 0e7b866b581a8e109bb15d321fff4f86b6e72e4d Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 3 Jul 2020 16:23:51 +0200 Subject: [PATCH 074/160] make sure to register all APIs when registering services apis on node --- eth/backend.go | 6 +++--- les/client.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index b9b356d20bee..82aef22944a2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -236,6 +236,9 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } + // Start the RPC service + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion()) + // Register the backend on the node stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) @@ -548,9 +551,6 @@ func (s *Ethereum) Start() error { // Start the bloom bits servicing goroutines s.startBloomHandlers(params.BloomBitsBlocks) - // Start the RPC service - s.netRPCService = ethapi.NewPublicNetAPI(s.p2pServer, s.NetVersion()) - // Figure out a max peers count based on the server limits maxPeers := s.p2pServer.MaxPeers if s.config.LightServ > 0 { diff --git a/les/client.go b/les/client.go index 0ae6700d0bb4..07f7acaa3d28 100644 --- a/les/client.go +++ b/les/client.go @@ -171,6 +171,8 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) + // Register the backend on the node stack.RegisterAPIs(leth.APIs()) stack.RegisterProtocols(leth.Protocols()) @@ -299,8 +301,6 @@ func (s *LightEthereum) Start() error { s.startBloomHandlers(params.BloomBitsBlocksClient) s.handler.start() - s.netRPCService = ethapi.NewPublicNetAPI(s.p2pServer, s.config.NetworkId) - return nil } From a5204a4ea7894d12096ec24f8fa55c3e944a1d89 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 6 Jul 2020 12:31:23 +0200 Subject: [PATCH 075/160] store int32 --- node/api.go | 11 +++++------ node/node.go | 45 +++++++++++++++++++-------------------------- node/rpcstack.go | 1 - 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/node/api.go b/node/api.go index e359357bd016..17adc19695b6 100644 --- a/node/api.go +++ b/node/api.go @@ -147,6 +147,7 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() + // set host, port, and endpoint if host == nil { h := DefaultHTTPHost @@ -235,6 +236,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() + // check if an existing WS server already exists for _, server := range api.node.httpServers { if atomic.LoadInt32(&server.WSAllowed) == 1 { @@ -256,7 +258,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // check if there is an existing server on the specified port, and if there is, enable ws on it if server, exists := api.node.httpServers[endpoint]; exists { // else configure ws on the existing server - atomic.AddInt32(&server.WSAllowed, 1) + atomic.StoreInt32(&server.WSAllowed, 1) // configure origins origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -281,7 +283,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // check if an HTTP server exists on the given endpoint, and if so, enable websocket on that HTTP server existingServer := api.node.ExistingHTTPServer(endpoint) if existingServer != nil { - atomic.AddInt32(&existingServer.WSAllowed, 1) + atomic.StoreInt32(&existingServer.WSAllowed, 1) existingServer.WsOrigins = origins } @@ -324,14 +326,11 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { defer api.node.lock.Unlock() for _, httpServer := range api.node.httpServers { - if atomic.LoadInt32(&httpServer.WSAllowed) == 1 { - atomic.AddInt32(&httpServer.WSAllowed, int32(-1)) + if atomic.SwapInt32(&httpServer.WSAllowed, 0) == 1 { // if RPC is not enabled on the WS http server, shut it down if atomic.LoadInt32(&httpServer.RPCAllowed) == 0 { api.node.stopServer(httpServer) - return true, nil } - return true, nil } } diff --git a/node/node.go b/node/node.go index 196d83f7d953..f9bc79e5688f 100644 --- a/node/node.go +++ b/node/node.go @@ -44,28 +44,21 @@ import ( // Node is a container on which services can be registered. type Node struct { - eventmux *event.TypeMux // Event multiplexer used between the services of a stack - config *Config - accman *accounts.Manager - + eventmux *event.TypeMux // Event multiplexer used between the services of a stack + config *Config + accman *accounts.Manager + log log.Logger ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory - server *p2p.Server // Currently running P2P networking layer - - lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - - rpcAPIs []rpc.API // List of APIs currently provided by the node - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - - ipc *HTTPServer // Stores information about the ipc http server - - stop chan struct{} // Channel to wait for termination notifications - lock sync.RWMutex - - log log.Logger + lock sync.RWMutex + stop chan struct{} // Channel to wait for termination notifications + server *p2p.Server // Currently running P2P networking layer + lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + rpcAPIs []rpc.API // List of APIs currently provided by the node + ipc *HTTPServer // Stores information about the ipc http server } // New creates a new P2P node, ready for protocol registration. @@ -158,13 +151,13 @@ func New(conf *Config) (*Node, error) { } if conf.WSHost != "" { node.httpServers[conf.WSEndpoint()] = &HTTPServer{ - WsOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - WSAllowed: 1, + WsOrigins: conf.WSOrigins, + Whitelist: conf.WSModules, + Srv: rpc.NewServer(), + endpoint: conf.WSEndpoint(), + host: conf.WSHost, + port: conf.WSPort, + WSAllowed: 1, } } diff --git a/node/rpcstack.go b/node/rpcstack.go index 32e4cd74d79d..cb39a1c4f822 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -222,7 +222,6 @@ func newGzipHandler(next http.Handler) http.Handler { // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { - // TODO make sure you protect the pointer return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if atomic.LoadInt32(&hs.WSAllowed) == 1 && isWebsocket(r) { ws.ServeHTTP(w, r) From 71d95904eea47aaa8f2519dacea9712ef67cb8f7 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 6 Jul 2020 19:11:28 +0200 Subject: [PATCH 076/160] remove P2PServer method from les, unnecessary --- les/client.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/les/client.go b/les/client.go index 07f7acaa3d28..96b9fffcd7f0 100644 --- a/les/client.go +++ b/les/client.go @@ -18,7 +18,6 @@ package les import ( - "errors" "fmt" "time" @@ -281,15 +280,6 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { }, s.dialCandidates) } -// P2PServer registers the node's running p2p server with the Backend. -func (s *LightEthereum) P2PServer(server *p2p.Server) error { - if server == nil { - return errors.New("p2p server is not running, cannot register with les backend") // TODO is this error message okay? - } - s.p2pServer = server - return nil -} - // Start implements node.Lifecycle, starting all internal goroutines needed by the // light ethereum protocol implementation. func (s *LightEthereum) Start() error { From 7661eb87b2f5b92a1f9556eda7ebc765267122e7 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 7 Jul 2020 13:39:04 +0200 Subject: [PATCH 077/160] refactor http servers + graphql (#26) --- cmd/geth/main.go | 2 - cmd/geth/usage.go | 4 +- cmd/utils/flags.go | 47 +++++++------------- cmd/utils/flags_legacy.go | 13 ++++++ go.mod | 2 +- go.sum | 15 +++++++ graphql/graphql_test.go | 10 +++-- graphql/service.go | 71 +++++++----------------------- node/api.go | 37 +++++----------- node/config.go | 20 +-------- node/defaults.go | 1 - node/node.go | 91 ++++++++++++++++++++++++--------------- node/node_test.go | 67 +++++++++++++++++++++++++--- node/rpcstack.go | 28 ++++-------- node/rpcstack_test.go | 2 +- 15 files changed, 208 insertions(+), 202 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index bbbc1b70d4ea..c475c9c66105 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -170,8 +170,6 @@ var ( utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCVirtualHostsFlag, utils.GraphQLEnabledFlag, - utils.GraphQLListenAddrFlag, - utils.GraphQLPortFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.HTTPApiFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 277774b0cc5b..44f5750fee82 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -142,8 +142,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.WSApiFlag, utils.WSAllowedOriginsFlag, utils.GraphQLEnabledFlag, - utils.GraphQLListenAddrFlag, - utils.GraphQLPortFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.RPCGlobalGasCap, @@ -231,6 +229,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LegacyWSApiFlag, utils.LegacyGpoBlocksFlag, utils.LegacyGpoPercentileFlag, + utils.LegacyGraphQLListenAddrFlag, + utils.LegacyGraphQLPortFlag, }, debug.DeprecatedFlags...), }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e177df569261..1a20dc6189cc 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -516,6 +516,20 @@ var ( Usage: "API's offered over the HTTP-RPC interface", Value: "", } + GraphQLEnabledFlag = cli.BoolFlag{ + Name: "graphql", + Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.", + } + GraphQLCORSDomainFlag = cli.StringFlag{ + Name: "graphql.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: "", + } + GraphQLVirtualHostsFlag = cli.StringFlag{ + Name: "graphql.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","), + } WSEnabledFlag = cli.BoolFlag{ Name: "ws", Usage: "Enable the WS-RPC server", @@ -540,30 +554,6 @@ var ( Usage: "Origins from which to accept websockets requests", Value: "", } - GraphQLEnabledFlag = cli.BoolFlag{ - Name: "graphql", - Usage: "Enable the GraphQL server", - } - GraphQLListenAddrFlag = cli.StringFlag{ - Name: "graphql.addr", - Usage: "GraphQL server listening interface", - Value: node.DefaultGraphQLHost, - } - GraphQLPortFlag = cli.IntFlag{ - Name: "graphql.port", - Usage: "GraphQL server listening port", - Value: node.DefaultGraphQLPort, - } - GraphQLCORSDomainFlag = cli.StringFlag{ - Name: "graphql.corsdomain", - Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", - Value: "", - } - GraphQLVirtualHostsFlag = cli.StringFlag{ - Name: "graphql.vhosts", - Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", - Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","), - } ExecFlag = cli.StringFlag{ Name: "exec", Usage: "Execute JavaScript statement", @@ -950,13 +940,6 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { // setGraphQL creates the GraphQL listener interface string from the set // command line flags, returning empty if the GraphQL endpoint is disabled. func setGraphQL(ctx *cli.Context, cfg *node.Config) { - if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" { - cfg.GraphQLHost = "127.0.0.1" - if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) { - cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name) - } - } - cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name) if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) { cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name)) } @@ -1728,7 +1711,7 @@ func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url strin // RegisterGraphQLService is a utility function to construct a new service and register it against a node. func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) { - if err := graphql.New(stack, backend, cfg.GraphQLEndpoint(), cfg.GraphQLCors, cfg.GraphQLVirtualHosts, cfg.HTTPTimeouts); err != nil { + if err := graphql.New(stack, backend, cfg.GraphQLCors, cfg.GraphQLVirtualHosts); err != nil { Fatalf("Failed to register the GraphQL service: %v", err) } } diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index c39664d24984..1376d47c0554 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -89,6 +89,8 @@ var ( Name: "testnet", Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)", } + + // (Deprecated May 2020, shown in aliased flags section) LegacyRPCEnabledFlag = cli.BoolFlag{ Name: "rpc", Usage: "Enable the HTTP-RPC server (deprecated, use --http)", @@ -158,6 +160,17 @@ var ( Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)", Value: "", } + + // (Deprecated July 2020, shown in aliased flags section) + LegacyGraphQLListenAddrFlag = cli.StringFlag{ + Name: "graphql.addr", + Usage: "GraphQL server listening interface (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", + } + LegacyGraphQLPortFlag = cli.IntFlag{ + Name: "graphql.port", + Usage: "GraphQL server listening port (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", + Value: node.DefaultHTTPPort, + } ) // showDeprecated displays deprecated flags that will be soon removed from the codebase. diff --git a/go.mod b/go.mod index db16cd3cb592..eaff1e2ac5e6 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f + golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 diff --git a/go.sum b/go.sum index acb104673937..d0e87bd69cc3 100644 --- a/go.sum +++ b/go.sum @@ -193,30 +193,45 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 08c1e4c8b576..366685151014 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -25,14 +25,16 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" ) func TestBuildSchema(t *testing.T) { + stack, err := node.New(&node.DefaultConfig) + if err != nil { + t.Fatalf("could not create new node: %v", err) + } // Make sure the schema can be parsed and matched up to the object model. - if _, err := newHandler(nil); err != nil { + if err := newHandler(stack, nil, []string{}, []string{}); err != nil { t.Errorf("Could not construct GraphQL handler: %v", err) } } @@ -120,7 +122,7 @@ func createGQLService(t *testing.T, stack *node.Node, endpoint string) { } // create gql service - err = New(stack, ethBackend.APIBackend, endpoint, []string{}, []string{}, rpc.DefaultHTTPTimeouts) + err = New(stack, ethBackend.APIBackend,[]string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 5c3d872842d9..72ecf35bcb22 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,81 +17,42 @@ package graphql import ( + "fmt" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" - "net/http" ) // New constructs a new GraphQL service instance. -func New(stack *node.Node, backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { +func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { if backend == nil { stack.Fatalf("missing backend") } // check if http server with given endpoint exists and enable graphQL on it - server := stack.ExistingHTTPServer(endpoint) - if server != nil { - // set vhosts, cors and timeouts - server.Vhosts = append(server.Vhosts, vhosts...) - server.CorsAllowedOrigins = append(server.CorsAllowedOrigins, cors...) - server.Timeouts = timeouts - // create handler - handler, err := createHandler(backend, cors, vhosts) - if err != nil { - return err - } - server.GQLHandler = handler - // don't register lifecycle if registering on existing http server - return nil - } - // otherwise create a new server - handler, err := createHandler(backend, cors, vhosts) - if err != nil { - return err - } - // create the http server - gqlServer := &node.HTTPServer{ - RPCAllowed: 0, - WSAllowed: 0, - Vhosts: vhosts, - CorsAllowedOrigins: cors, - Timeouts: timeouts, - GQLHandler: handler, - Srv: rpc.NewServer(), - } - gqlServer.SetEndpoint(endpoint) - stack.RegisterHTTPServer(endpoint, gqlServer) - - return nil -} - -func createHandler(backend ethapi.Backend, cors, vhosts []string) (http.Handler, error) { - // create handler stack and wrap the graphql handler - handler, err := newHandler(backend) - if err != nil { - return nil, err - } - handler = node.NewHTTPHandlerStack(handler, cors, vhosts) - - return handler, nil + return newHandler(stack, backend, cors, vhosts) } // newHandler returns a new `http.Handler` that will answer GraphQL queries. // It additionally exports an interactive query browser on the / endpoint. -func newHandler(backend ethapi.Backend) (http.Handler, error) { +func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { q := Resolver{backend} s, err := graphql.ParseSchema(schema, &q) if err != nil { - return nil, err + return err } h := &relay.Handler{Schema: s} + handler := node.NewHTTPHandlerStack(h, cors, vhosts) + + endpoint := stack.RegisterPath("/graphql/ui", GraphiQL{}) + endpoint = stack.RegisterPath("/graphql", handler) + endpoint = stack.RegisterPath("/graphql/", handler) - mux := http.NewServeMux() - mux.Handle("/", GraphiQL{}) - mux.Handle("/graphql", h) - mux.Handle("/graphql/", h) - return mux, nil + if endpoint != "" { + log.Info("GraphQL configured on endpoint", "endpoint", fmt.Sprintf("http://%s/graphql", endpoint)) + log.Info("GraphQL web UI enabled", "endpoint", fmt.Sprintf("http://%s/graphql/ui", endpoint)) + } + return nil } diff --git a/node/api.go b/node/api.go index 17adc19695b6..55956f63ded0 100644 --- a/node/api.go +++ b/node/api.go @@ -191,7 +191,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } // configure http server - httpServer := &HTTPServer{ + httpServer := &httpServer{ host: *host, port: *port, endpoint: endpoint, @@ -201,7 +201,8 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis Whitelist: modules, } // create handler - httpServer.handler = NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) + handler := NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) + httpServer.srvMux.Handle("/", handler) // create HTTP server if err := api.node.CreateHTTPServer(httpServer, false); err != nil { return false, err @@ -213,7 +214,6 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.Listener.Addr()), "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), "vhosts", strings.Join(httpServer.Vhosts, ",")) - return true, nil } @@ -228,7 +228,6 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { return true, nil } } - return false, fmt.Errorf("HTTP RPC not running") } @@ -255,23 +254,6 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str port = &api.node.config.WSPort } endpoint := fmt.Sprintf("%s:%d", *host, *port) - // check if there is an existing server on the specified port, and if there is, enable ws on it - if server, exists := api.node.httpServers[endpoint]; exists { - // else configure ws on the existing server - atomic.StoreInt32(&server.WSAllowed, 1) - // configure origins - origins := api.node.config.WSOrigins - if allowedOrigins != nil { - origins = nil - for _, origin := range strings.Split(*allowedOrigins, ",") { - origins = append(origins, strings.TrimSpace(origin)) - } - } - server.WsOrigins = origins - - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", server.Listener.Addr())) - return true, nil - } origins := api.node.config.WSOrigins if allowedOrigins != nil { @@ -286,6 +268,8 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str atomic.StoreInt32(&existingServer.WSAllowed, 1) existingServer.WsOrigins = origins + api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.Listener.Addr())) + return true, nil } modules := api.node.config.WSModules @@ -296,7 +280,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } } // configure http server - wsServer := &HTTPServer{ + wsServer := &httpServer{ Srv: rpc.NewServer(), endpoint: endpoint, host: *host, @@ -306,7 +290,8 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str WSAllowed: 1, } // create handler - wsServer.handler = wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) + handler := wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) + wsServer.srvMux.Handle("/", handler) // create the HTTP server if err := api.node.CreateHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { return false, err @@ -314,9 +299,10 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // register the HTTP server api.node.RegisterHTTPServer(endpoint, wsServer) // start the HTTP server - wsServer.Start() + if err := wsServer.Start(); err != nil { + return false, err + } api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.Listener.Addr())) - return true, nil } @@ -334,7 +320,6 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { return true, nil } } - return false, fmt.Errorf("WebSocket RPC not running") } diff --git a/node/config.go b/node/config.go index 61566b7bee9b..55532632cd1b 100644 --- a/node/config.go +++ b/node/config.go @@ -162,15 +162,6 @@ type Config struct { // private APIs to untrusted users is a major security risk. WSExposeAll bool `toml:",omitempty"` - // GraphQLHost is the host interface on which to start the GraphQL server. If this - // field is empty, no GraphQL API endpoint will be started. - GraphQLHost string - - // GraphQLPort is the TCP port number on which to start the GraphQL server. The - // default zero value is/ valid and will pick a port number randomly (useful - // for ephemeral nodes). - GraphQLPort int `toml:",omitempty"` - // GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting // clients. Please be aware that CORS is a browser enforced security, it's fully // useless for custom HTTP clients. @@ -247,15 +238,6 @@ func (c *Config) HTTPEndpoint() string { return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) } -// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface -// and port parameters. -func (c *Config) GraphQLEndpoint() string { - if c.GraphQLHost == "" { - return "" - } - return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort) -} - // DefaultHTTPEndpoint returns the HTTP endpoint used by default. func DefaultHTTPEndpoint() string { config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} @@ -280,7 +262,7 @@ func DefaultWSEndpoint() string { // ExtRPCEnabled returns the indicator whether node enables the external // RPC(http, ws or graphql). func (c *Config) ExtRPCEnabled() bool { - return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != "" + return c.HTTPHost != "" || c.WSHost != "" } // NodeName returns the devp2p node identifier. diff --git a/node/defaults.go b/node/defaults.go index f84a5d547998..c685dde5d127 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -45,7 +45,6 @@ var DefaultConfig = Config{ HTTPTimeouts: rpc.DefaultHTTPTimeouts, WSPort: DefaultWSPort, WSModules: []string{"net", "web3"}, - GraphQLPort: DefaultGraphQLPort, GraphQLVirtualHosts: []string{"localhost"}, P2P: p2p.Config{ ListenAddr: ":30303", diff --git a/node/node.go b/node/node.go index f9bc79e5688f..af0c71b1bc3a 100644 --- a/node/node.go +++ b/node/node.go @@ -58,7 +58,7 @@ type Node struct { httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. inprocHandler *rpc.Server // In-process RPC request handler to process the API requests rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *HTTPServer // Stores information about the ipc http server + ipc *httpServer // Stores information about the ipc http server } // New creates a new P2P node, ready for protocol registration. @@ -102,7 +102,7 @@ func New(conf *Config) (*Node, error) { config: conf, lifecycles: make(map[reflect.Type]Lifecycle), httpServers: make(serverMap), - ipc: &HTTPServer{ + ipc: &httpServer{ endpoint: conf.IPCEndpoint(), }, eventmux: new(event.TypeMux), @@ -127,7 +127,7 @@ func New(conf *Config) (*Node, error) { // Configure HTTP server(s) if conf.HTTPHost != "" { - httpServ := &HTTPServer{ + httpServ := &httpServer{ CorsAllowedOrigins: conf.HTTPCors, Vhosts: conf.HTTPVirtualHosts, Whitelist: conf.HTTPModules, @@ -150,7 +150,7 @@ func New(conf *Config) (*Node, error) { node.httpServers[conf.HTTPEndpoint()] = httpServ } if conf.WSHost != "" { - node.httpServers[conf.WSEndpoint()] = &HTTPServer{ + node.httpServers[conf.WSEndpoint()] = &httpServer{ WsOrigins: conf.WSOrigins, Whitelist: conf.WSModules, Srv: rpc.NewServer(), @@ -187,7 +187,7 @@ func (n *Node) Close() error { } } -// RegisterLifecycle registers the given Lifecycle on the node +// RegisterLifecycle registers the given Lifecycle on the node. func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { kind := reflect.TypeOf(lifecycle) if _, exists := n.lifecycles[kind]; exists { @@ -197,31 +197,43 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { n.lifecycles[kind] = lifecycle } -// RegisterProtocols adds backend's protocols to the node's p2p server +// RegisterProtocols adds backend's protocols to the node's p2p server. func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { n.server.Protocols = append(n.server.Protocols, protocols...) } -// RegisterAPIs registers the APIs a service provides on the node +// RegisterAPIs registers the APIs a service provides on the node. func (n *Node) RegisterAPIs(apis []rpc.API) { n.rpcAPIs = append(n.rpcAPIs, apis...) } -// RegisterHTTPServer registers the given HTTP server on the node -func (n *Node) RegisterHTTPServer(endpoint string, server *HTTPServer) { +// RegisterHTTPServer registers the given HTTP server on the node. +func (n *Node) RegisterHTTPServer(endpoint string, server *httpServer) { n.httpServers[endpoint] = server } -// ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint -func (n *Node) ExistingHTTPServer(endpoint string) *HTTPServer { +// RegisterPath mounts the given handler on the given path on the canonical HTTP server. +func (n *Node) RegisterPath(path string, handler http.Handler) string { + for _, server := range n.httpServers { + if atomic.LoadInt32(&server.RPCAllowed) == 1 { + server.srvMux.Handle(path, handler) + return server.endpoint + } + } + n.log.Warn(fmt.Sprintf("HTTP server not configured on node, path %s cannot be enabled", path)) + return "" +} + +// ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint. +func (n *Node) ExistingHTTPServer(endpoint string) *httpServer { if server, exists := n.httpServers[endpoint]; exists { return server } return nil } -// CreateHTTPServer creates an http.Server and adds it to the given HTTPServer -func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { +// CreateHTTPServer creates an http.Server and adds it to the given httpServer. +func (n *Node) CreateHTTPServer(h *httpServer, exposeAll bool) error { // register apis and create handler stack err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) if err != nil { @@ -234,7 +246,7 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { return err } // create the HTTP server - httpSrv := &http.Server{Handler: h.handler} + httpSrv := &http.Server{Handler: &h.srvMux} // check timeouts if they exist if h.Timeouts != (rpc.HTTPTimeouts{}) { CheckTimeouts(&h.Timeouts) @@ -242,14 +254,14 @@ func (n *Node) CreateHTTPServer(h *HTTPServer, exposeAll bool) error { httpSrv.WriteTimeout = h.Timeouts.WriteTimeout httpSrv.IdleTimeout = h.Timeouts.IdleTimeout } - // add listener and http.Server to HTTPServer + // add listener and http.Server to httpServer h.Listener = listener h.Server = httpSrv return nil } -// running returns true if the node's p2p server is already running +// running returns true if the node's p2p server is already running. func (n *Node) running() bool { return n.server.Running() } @@ -298,7 +310,7 @@ func (n *Node) Start() error { return nil } -// stopLifecycles stops the node's running Lifecycles +// stopLifecycles stops the node's running Lifecycles. func (n *Node) stopLifecycles(started []Lifecycle) { for _, lifecycle := range started { lifecycle.Stop() @@ -343,23 +355,12 @@ func (n *Node) configureRPC() error { n.stopInProc() return err } - + // configure HTTPServers for _, server := range n.httpServers { // configure the handlers - if atomic.LoadInt32(&server.RPCAllowed) == 1 { - server.handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) - // wrap ws handler just in case ws is enabled through the console after start-up - wsHandler := server.Srv.WebsocketHandler(server.WsOrigins) - server.handler = server.NewWebsocketUpgradeHandler(server.handler, wsHandler) - - n.log.Info("HTTP configured on endpoint ", "endpoint", server.endpoint) - if atomic.LoadInt32(&server.WSAllowed) == 1 { - n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) - } - } - if (atomic.LoadInt32(&server.WSAllowed) == 1) && server.handler == nil { - server.handler = server.Srv.WebsocketHandler(server.WsOrigins) - n.log.Info("Websocket configured on endpoint ", "endpoint", server.endpoint) + handler := n.createHandler(server) + if handler != nil { + server.srvMux.Handle("/", handler) } // create the HTTP server if err := n.CreateHTTPServer(server, false); err != nil { @@ -374,6 +375,28 @@ func (n *Node) configureRPC() error { return nil } +// createHandler creates the http.Handler for the given httpServer. +func (n *Node) createHandler(server *httpServer) http.Handler { + var handler http.Handler + if atomic.LoadInt32(&server.RPCAllowed) == 1 { + handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) + // wrap ws handler just in case ws is enabled through the console after start-up + wsHandler := server.Srv.WebsocketHandler(server.WsOrigins) + handler = server.NewWebsocketUpgradeHandler(handler, wsHandler) + + n.log.Info("HTTP configured on endpoint ", "endpoint", fmt.Sprintf("http://%s/", server.endpoint)) + if atomic.LoadInt32(&server.WSAllowed) == 1 { + n.log.Info("Websocket configured on endpoint ", "endpoint", fmt.Sprintf("ws://%s/", server.endpoint)) + } + } + if (atomic.LoadInt32(&server.WSAllowed) == 1) && handler == nil { + handler = server.Srv.WebsocketHandler(server.WsOrigins) + n.log.Info("Websocket configured on endpoint ", "endpoint", fmt.Sprintf("ws://%s/", server.endpoint)) + } + + return handler +} + // startInProc initializes an in-process RPC endpoint. func (n *Node) startInProc() error { // Register all the APIs exposed by the services @@ -426,7 +449,7 @@ func (n *Node) stopIPC() { } // stopServers terminates the given HTTP servers' endpoints -func (n *Node) stopServer(server *HTTPServer) { +func (n *Node) stopServer(server *httpServer) { if server.Server != nil { url := fmt.Sprintf("http://%v/", server.Listener.Addr()) // Don't bother imposing a timeout here. @@ -437,7 +460,7 @@ func (n *Node) stopServer(server *HTTPServer) { server.Srv.Stop() server.Srv = nil } - // remove stopped http server from node's http servers // TODO is this preferable? + // remove stopped http server from node's http servers delete(n.httpServers, server.endpoint) } diff --git a/node/node_test.go b/node/node_test.go index bb1543650894..f417eb18215d 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -19,6 +19,7 @@ package node import ( "errors" "fmt" + "io" "io/ioutil" "net/http" "os" @@ -365,7 +366,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stack.server.PrivateKey = testNodeKey } -// Tests whether a given HTTPServer can be registered on the node +// Tests whether a given httpServer can be registered on the node func TestRegisterHTTPServer(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { @@ -373,21 +374,21 @@ func TestRegisterHTTPServer(t *testing.T) { } defer stack.Close() - srv1 := &HTTPServer{ + srv1 := &httpServer{ host: "test1", port: 0001, } endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) stack.RegisterHTTPServer(endpoint1, srv1) - srv2 := &HTTPServer{ + srv2 := &httpServer{ host: "test2", port: 0002, } endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) stack.RegisterHTTPServer(endpoint2, srv2) - noop := &HTTPServer{ + noop := &httpServer{ host: "test", port: 0000, } @@ -404,6 +405,55 @@ func TestRegisterHTTPServer(t *testing.T) { } } +// Tests whether a handler can be successfully mounted on the canonical HTTP server +// on the given path +func TestRegisterPath_Successful(t *testing.T) { + node := createNode(t, 7878, 7979) + + // create and mount handler + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("success")) + }) + endpoint := node.RegisterPath("/test", handler) + assert.Equal(t, "127.0.0.1:7878", endpoint) + + // start node + if err := node.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + // create HTTP request + httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil) + if err != nil { + t.Error("could not issue new http request ", err) + } + + // check response + resp := doHTTPRequest(t, httpReq) + buf := make([]byte, 7) + _, err = io.ReadFull(resp.Body, buf) + if err != nil { + t.Fatalf("could not read response: %v", err) + } + assert.Equal(t, "success", string(buf)) +} + +// Tests that the given handler will not be successfully mounted since no HTTP server +// is enabled for RPC +func TestRegisterPath_Unsuccessful(t *testing.T) { + node, err := New(&DefaultConfig) + if err != nil { + t.Fatalf("could not create new node: %v", err) + } + + // create and mount handler + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("success")) + }) + endpoint := node.RegisterPath("/test", handler) + assert.Equal(t, "", endpoint) +} + // Tests whether a node can successfully create and register HTTP server // lifecycles on the node. func TestHTTPServerCreateAndStop(t *testing.T) { @@ -496,7 +546,7 @@ func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) } -func startHTTP(t *testing.T, httpPort, wsPort int) *Node { +func createNode(t *testing.T, httpPort, wsPort int) *Node { conf := &Config{ HTTPHost: "127.0.0.1", HTTPPort: httpPort, @@ -507,7 +557,12 @@ func startHTTP(t *testing.T, httpPort, wsPort int) *Node { if err != nil { t.Fatalf("could not create a new node: %v", err) } - err = node.Start() + return node +} + +func startHTTP(t *testing.T, httpPort, wsPort int) *Node { + node := createNode(t, httpPort, wsPort) + err := node.Start() if err != nil { t.Fatalf("could not start http service on node: %v", err) } diff --git a/node/rpcstack.go b/node/rpcstack.go index cb39a1c4f822..fe2d2673af71 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -33,9 +33,11 @@ import ( "github.com/rs/cors" ) -type serverMap map[string]*HTTPServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql +type serverMap map[string]*httpServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql + +type httpServer struct { + srvMux http.ServeMux -type HTTPServer struct { handler http.Handler Srv *rpc.Server Server *http.Server @@ -55,8 +57,6 @@ type HTTPServer struct { RPCAllowed int32 WSAllowed int32 - - GQLHandler http.Handler } func (sm serverMap) Start() error { @@ -77,15 +77,15 @@ func (sm serverMap) Stop() error { return nil } -// Start starts the serverMap's HTTP server. // TODO I don't like the way this is written -func (h *HTTPServer) Start() error { +// Start starts the httpServer's http.Server +func (h *httpServer) Start() error { go h.Server.Serve(h.Listener) log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) return nil } -// Stop shuts down the serverMap's HTTP server. // TODO I don't like the way this is written -func (h *HTTPServer) Stop() error { +// Stop shuts down the httpServer's http.Server +func (h *httpServer) Stop() error { if h.Server != nil { url := fmt.Sprintf("http://%v/", h.Listener.Addr()) // Don't bother imposing a timeout here. @@ -100,16 +100,6 @@ func (h *HTTPServer) Stop() error { return nil } -// SetHandler assigns the given handler to the serverMap. -func (h *HTTPServer) SetHandler(handler http.Handler) { - h.handler = handler -} - -// SetEndpoints assigns the given endpoint to the serverMap. -func (h *HTTPServer) SetEndpoint(endpoint string) { - h.endpoint = endpoint -} - // NewHTTPHandlerStack returns wrapped http-related handlers func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { // Wrap the CORS-handler within a host-handler @@ -221,7 +211,7 @@ func newGzipHandler(next http.Handler) http.Handler { // NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade // request to the websocket protocol. If not, serves the the request with the http handler. -func (hs *HTTPServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { +func (hs *httpServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if atomic.LoadInt32(&hs.WSAllowed) == 1 && isWebsocket(r) { ws.ServeHTTP(w, r) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index f745e350afe9..3dfaf53ed6bc 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -12,7 +12,7 @@ import ( ) func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { - h := &HTTPServer{ + h := &httpServer{ Srv: rpc.NewServer(), WSAllowed: 1, } From d96b2800e847effd0c970ba7f4e6c5e6499c22d8 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 7 Jul 2020 13:46:38 +0200 Subject: [PATCH 078/160] removed some TODOs --- node/node.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index af0c71b1bc3a..76d52ca67d36 100644 --- a/node/node.go +++ b/node/node.go @@ -111,7 +111,7 @@ func New(conf *Config) (*Node, error) { // Initialize the p2p server. This creates the node key and // discovery databases. - node.server = &p2p.Server{Config: conf.P2P} // TODO add init step for p2p server + node.server = &p2p.Server{Config: conf.P2P} node.server.Config.PrivateKey = node.config.NodeKey() node.server.Config.Name = node.config.NodeName() node.server.Config.Logger = node.log @@ -285,8 +285,6 @@ func (n *Node) Start() error { } n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) - // TODO running p2p server needs to somehow be added to the backend - // Configure the RPC interfaces if err := n.configureRPC(); err != nil { n.httpServers.Stop() @@ -597,7 +595,7 @@ func (n *Node) WSEndpoint() string { } } - return n.config.WSEndpoint() // TODO should it return the endpoint from the node's config? Or just an empty string? + return n.config.WSEndpoint() } // EventMux retrieves the event multiplexer used by all the network services in From ff002b1a61fcd9699b76eeda91d2fc1d3222a42b Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:09:17 +0200 Subject: [PATCH 079/160] init leth handler --- les/client.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/les/client.go b/les/client.go index 96b9fffcd7f0..198cda1b6b91 100644 --- a/les/client.go +++ b/les/client.go @@ -170,6 +170,12 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) + leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth) + if leth.handler.ulc != nil { + log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction) + leth.blockchain.DisableCheckFreq() + } + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) // Register the backend on the node From bafc12adba292eb7b506005b419070ed8eb44684 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Thu, 9 Jul 2020 16:27:12 +0200 Subject: [PATCH 080/160] register protocols and apis of les on eth backend --- cmd/utils/flags.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1a20dc6189cc..2f54c5d97b73 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1688,6 +1688,8 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { } if cfg.LightServ > 0 { ls, _ := les.NewLesServer(backend, cfg) + stack.RegisterProtocols(ls.Protocols()) // TODO should this happen? + stack.RegisterAPIs(ls.APIs()) backend.AddLesServer(ls) } return backend.APIBackend From 975061ca36f7a03daec8be3c3626f0d9c90a361e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 13:30:52 +0200 Subject: [PATCH 081/160] node: make Node.Attach work before Node.Start --- node/node.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/node/node.go b/node/node.go index 76d52ca67d36..63409fa169d8 100644 --- a/node/node.go +++ b/node/node.go @@ -105,8 +105,9 @@ func New(conf *Config) (*Node, error) { ipc: &httpServer{ endpoint: conf.IPCEndpoint(), }, - eventmux: new(event.TypeMux), - log: conf.Logger, + inprocHandler: rpc.NewServer(), + eventmux: new(event.TypeMux), + log: conf.Logger, } // Initialize the p2p server. This creates the node key and @@ -395,26 +396,19 @@ func (n *Node) createHandler(server *httpServer) http.Handler { return handler } -// startInProc initializes an in-process RPC endpoint. +// startInProc registers all RPC APIs on the inproc server. func (n *Node) startInProc() error { - // Register all the APIs exposed by the services - handler := rpc.NewServer() for _, api := range n.rpcAPIs { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil { return err } - n.log.Debug("InProc registered", "namespace", api.Namespace) } - n.inprocHandler = handler return nil } // stopInProc terminates the in-process RPC endpoint. func (n *Node) stopInProc() { - if n.inprocHandler != nil { - n.inprocHandler.Stop() - n.inprocHandler = nil - } + n.inprocHandler.Stop() } // startIPC initializes and starts the IPC RPC endpoint. @@ -530,12 +524,6 @@ func (n *Node) Wait() { // Attach creates an RPC client attached to an in-process API handler. func (n *Node) Attach() (*rpc.Client, error) { - n.lock.RLock() - defer n.lock.RUnlock() - - if n.server == nil { - return nil, ErrNodeStopped - } return rpc.DialInProc(n.inprocHandler), nil } From 8a568a42889d89d8f10a8faab84b8449b62b2ec9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 13:47:44 +0200 Subject: [PATCH 082/160] all: excise SetContractBackend --- cmd/geth/main.go | 12 +----------- cmd/utils/flags.go | 2 +- eth/api_backend.go | 5 ----- eth/backend.go | 10 ---------- internal/ethapi/backend.go | 3 --- les/api_backend.go | 5 ----- les/api_test.go | 2 +- les/client.go | 17 ++--------------- les/commons.go | 15 +++++++++++++++ les/server.go | 20 +++----------------- 10 files changed, 23 insertions(+), 68 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index c475c9c66105..8a155a76a6df 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -378,18 +378,8 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } - ethClient := ethclient.NewClient(rpcClient) - // Set contract backend for ethereum service if local node - // is serving LES requests. - if ctx.GlobalInt(utils.LegacyLightServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { - backend.SetContractBackend(ethClient) - } - // Set contract backend for les service if local node is - // running as a light client. - if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { - backend.SetContractBackend(ethClient) - } + ethClient := ethclient.NewClient(rpcClient) go func() { // Open any wallets already attached diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2f54c5d97b73..7d837eaf2839 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1687,7 +1687,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { Fatalf("Failed to register the Ethereum service: %w", err) } if cfg.LightServ > 0 { - ls, _ := les.NewLesServer(backend, cfg) + ls, _ := les.NewLesServer(stack, backend, cfg) stack.RegisterProtocols(ls.Protocols()) // TODO should this happen? stack.RegisterAPIs(ls.APIs()) backend.AddLesServer(ls) diff --git a/eth/api_backend.go b/eth/api_backend.go index 46f41e7cd678..1b0e841d80db 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/miner" @@ -334,7 +333,3 @@ func (b *EthAPIBackend) Miner() *miner.Miner { func (b *EthAPIBackend) StartMining(threads int) error { return b.eth.StartMining(threads) } - -func (b *EthAPIBackend) SetContractBackend(client *ethclient.Client) { - b.eth.SetContractBackend(client) -} diff --git a/eth/backend.go b/eth/backend.go index 82aef22944a2..a76191a6a2ec 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -26,7 +26,6 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -60,7 +59,6 @@ type LesServer interface { APIs() []rpc.API Protocols() []p2p.Protocol SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) - SetContractBackend(bind.ContractBackend) } // Ethereum implements the Ethereum full node service. @@ -104,14 +102,6 @@ func (s *Ethereum) AddLesServer(ls LesServer) { ls.SetBloomBitsIndexer(s.bloomIndexer) } -// SetClient sets a rpc client which connecting to our local node. -func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) { - // Pass the rpc client to les server if it is enabled. - if s.lesServer != nil { - s.lesServer.SetContractBackend(backend) - } -} - // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) func New(stack *node.Node, config *Config) (*Ethereum, error) { diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 6d8374eb53c9..12faa307171e 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/miner" @@ -96,8 +95,6 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine - - SetContractBackend(client *ethclient.Client) } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/les/api_backend.go b/les/api_backend.go index d905081077a6..25dde0a2cd57 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" @@ -308,10 +307,6 @@ func (b *LesApiBackend) StartMining(threads int) error { return fmt.Errorf("Light clients do not support mining") // TODO is this okay? } -func (b *LesApiBackend) SetContractBackend(client *ethclient.Client) { - b.eth.SetContractBackend(client) -} - func (b *LesApiBackend) TxPool() *core.TxPool { return nil // TODO is this okay? } diff --git a/les/api_test.go b/les/api_test.go index 1fcef8edacc2..0d8baac85491 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -508,7 +508,7 @@ func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.L if err != nil { return nil, err } - server, err := NewLesServer(ethereum, &config) + server, err := NewLesServer(stack, ethereum, &config) if err != nil { return nil, err } diff --git a/les/client.go b/les/client.go index 198cda1b6b91..a2f7c56dfd57 100644 --- a/les/client.go +++ b/les/client.go @@ -22,7 +22,6 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/mclock" @@ -37,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/les/checkpointoracle" lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -76,6 +74,7 @@ type LightEthereum struct { p2pServer *p2p.Server } +// New creates an instance of the light client. func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { @@ -142,11 +141,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) // Set up checkpoint oracle. - oracle := config.CheckpointOracle - if oracle == nil { - oracle = params.CheckpointOracles[genesisHash] - } - leth.oracle = checkpointoracle.New(oracle, leth.localCheckpoint) + leth.oracle = leth.setupOracle(stack, genesisHash, config) // Note: AddChildIndexer starts the update process for the child leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) @@ -323,11 +318,3 @@ func (s *LightEthereum) Stop() error { log.Info("Light ethereum stopped") return nil } - -// SetClient sets the rpc client and binds the registrar contract. -func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) { - if s.oracle == nil { - return - } - s.oracle.Start(backend) -} diff --git a/les/commons.go b/les/commons.go index cd8a2283493b..9f14f82a473a 100644 --- a/les/commons.go +++ b/les/commons.go @@ -26,9 +26,11 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" @@ -145,3 +147,16 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { BloomRoot: light.GetBloomTrieRoot(c.chainDb, index, sectionHead), } } + +// setupOracle sets up the checkpoint oracle contract client. +func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, config *eth.Config) *checkpointoracle.CheckpointOracle { + cfg := config.CheckpointOracle + if cfg == nil { + cfg = params.CheckpointOracles[genesis] + } + oracle := checkpointoracle.New(cfg, c.localCheckpoint) + rpcClient, _ := node.Attach() + client := ethclient.NewClient(rpcClient) + oracle.Start(client) + return oracle +} diff --git a/les/server.go b/les/server.go index 609a24fd2b4a..76c906e8b21f 100644 --- a/les/server.go +++ b/les/server.go @@ -20,14 +20,13 @@ import ( "crypto/ecdsa" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" @@ -57,7 +56,7 @@ type LesServer struct { threadsBusy int // Request serving threads count when system is busy(block insertion). } -func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { +func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { // Collect les protocol version information supported by local node. lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) for i, pv := range AdvertiseProtocolVersions { @@ -93,12 +92,7 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) { srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.freeCapacity = srv.minCapacity - // Set up checkpoint oracle. - oracle := config.CheckpointOracle - if oracle == nil { - oracle = params.CheckpointOracles[e.BlockChain().Genesis().Hash()] - } - srv.oracle = checkpointoracle.New(oracle, srv.localCheckpoint) + srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config) // Initialize server capacity management fields. srv.defParams = flowcontrol.ServerParams{ @@ -213,14 +207,6 @@ func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { bloomIndexer.AddChildIndexer(s.bloomTrieIndexer) } -// SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched. -func (s *LesServer) SetContractBackend(backend bind.ContractBackend) { - if s.oracle == nil { - return - } - s.oracle.Start(backend) -} - // capacityManagement starts an event handler loop that updates the recharge curve of // the client manager and adjusts the client pool's size according to the total // capacity updates coming from the client manager From e930d38b74a5065f408347b9edd91cf0c0da4223 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 13:55:44 +0200 Subject: [PATCH 083/160] revert blackbox test for ethclient --- ethclient/ethclient.go | 8 -------- ethclient/ethclient_test.go | 33 ++++++++++++++++----------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index e869f492e575..bc0305fc229d 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -397,14 +397,6 @@ func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuer return ec.c.EthSubscribe(ctx, ch, "logs", arg) } -// ToFilterArg is for testing purposes only. -func ToFilterArg(q ethereum.FilterQuery, test bool) (interface{}, error) { - if test { - return toFilterArg(q) - } - return nil, fmt.Errorf("functionality reserved for testing") // TODO is this okay? -} - func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { arg := map[string]interface{}{ "address": q.Addresses, diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 09cbdefa7e72..b49abe917732 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package ethclient_test +package ethclient import ( "context" @@ -33,24 +33,23 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) // Verify that Client implements the ethereum interfaces. var ( - _ = ethereum.ChainReader(ðclient.Client{}) - _ = ethereum.TransactionReader(ðclient.Client{}) - _ = ethereum.ChainStateReader(ðclient.Client{}) - _ = ethereum.ChainSyncReader(ðclient.Client{}) - _ = ethereum.ContractCaller(ðclient.Client{}) - _ = ethereum.GasEstimator(ðclient.Client{}) - _ = ethereum.GasPricer(ðclient.Client{}) - _ = ethereum.LogFilterer(ðclient.Client{}) - _ = ethereum.PendingStateReader(ðclient.Client{}) + _ = ethereum.ChainReader(&Client{}) + _ = ethereum.TransactionReader(&Client{}) + _ = ethereum.ChainStateReader(&Client{}) + _ = ethereum.ChainSyncReader(&Client{}) + _ = ethereum.ContractCaller(&Client{}) + _ = ethereum.GasEstimator(&Client{}) + _ = ethereum.GasPricer(&Client{}) + _ = ethereum.LogFilterer(&Client{}) + _ = ethereum.PendingStateReader(&Client{}) // _ = ethereum.PendingStateEventer(&Client{}) - _ = ethereum.PendingContractCaller(ðclient.Client{}) + _ = ethereum.PendingContractCaller(&Client{}) ) func TestToFilterArg(t *testing.T) { @@ -164,7 +163,7 @@ func TestToFilterArg(t *testing.T) { }, } { t.Run(testCase.name, func(t *testing.T) { - output, err := ethclient.ToFilterArg(testCase.input, true) + output, err := toFilterArg(testCase.input) if (testCase.err == nil) != (err == nil) { t.Fatalf("expected error %v but got %v", testCase.err, err) } @@ -256,7 +255,7 @@ func TestHeader(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := ethclient.NewClient(client) + ec := NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -305,7 +304,7 @@ func TestBalanceAt(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := ethclient.NewClient(client) + ec := NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() @@ -326,7 +325,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) { defer backend.Stop() defer client.Close() - ec := ethclient.NewClient(client) + ec := NewClient(client) ctx, cancel := context.WithCancel(context.Background()) cancel() tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) @@ -343,7 +342,7 @@ func TestChainID(t *testing.T) { client, _ := backend.Attach() defer backend.Stop() defer client.Close() - ec := ethclient.NewClient(client) + ec := NewClient(client) id, err := ec.ChainID(context.Background()) if err != nil { From 54b8389cd9bccbd3aa2bef7435fb5f7718f2d9ad Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 14:14:39 +0200 Subject: [PATCH 084/160] les: fix setOracle for chains without checkpoint --- les/checkpointoracle/oracle.go | 10 ---------- les/commons.go | 21 ++++++++++++++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/les/checkpointoracle/oracle.go b/les/checkpointoracle/oracle.go index 73bd8f35d409..8f2dda3937f7 100644 --- a/les/checkpointoracle/oracle.go +++ b/les/checkpointoracle/oracle.go @@ -51,16 +51,6 @@ type CheckpointOracle struct { // New creates a checkpoint oracle handler with given configs and callback. func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle { - if config == nil { - log.Info("Checkpoint registrar is not enabled") - return nil - } - if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { - log.Warn("Invalid checkpoint registrar config") - return nil - } - log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) - return &CheckpointOracle{ config: config, getLocal: getLocal, diff --git a/les/commons.go b/les/commons.go index 9f14f82a473a..003e196d2b82 100644 --- a/les/commons.go +++ b/les/commons.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" @@ -149,14 +150,24 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { } // setupOracle sets up the checkpoint oracle contract client. -func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, config *eth.Config) *checkpointoracle.CheckpointOracle { - cfg := config.CheckpointOracle - if cfg == nil { - cfg = params.CheckpointOracles[genesis] +func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *eth.Config) *checkpointoracle.CheckpointOracle { + config := ethconfig.CheckpointOracle + if config == nil { + // Try loading default config. + config = params.CheckpointOracles[genesis] } - oracle := checkpointoracle.New(cfg, c.localCheckpoint) + if config == nil { + log.Info("Checkpoint registrar is not enabled") + return nil + } + if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { + log.Warn("Invalid checkpoint registrar config") + return nil + } + oracle := checkpointoracle.New(config, c.localCheckpoint) rpcClient, _ := node.Attach() client := ethclient.NewClient(rpcClient) oracle.Start(client) + log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) return oracle } From 212bb78b1fdff5e109f95d7d739bbef6e8c493b4 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:18:29 +0200 Subject: [PATCH 085/160] les --> lifecycle decoupled from ethbackend (#29) --- eth/backend.go | 25 +------------------------ les/server.go | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index a76191a6a2ec..e29844211c56 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -54,10 +54,6 @@ import ( ) type LesServer interface { - Start(srvr *p2p.Server) - Stop() - APIs() []rpc.API - Protocols() []p2p.Protocol SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) } @@ -98,7 +94,6 @@ type Ethereum struct { } func (s *Ethereum) AddLesServer(ls LesServer) { - s.lesServer = ls ls.SetBloomBitsIndexer(s.bloomIndexer) } @@ -290,19 +285,10 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { apis := ethapi.GetAPIs(s.APIBackend) - - // Append any APIs exposed explicitly by the les server - if s.lesServer != nil { - apis = append(apis, s.lesServer.APIs()...) - } + // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) - // Append any APIs exposed explicitly by the les server - if s.lesServer != nil { - apis = append(apis, s.lesServer.APIs()...) - } - // Append all the local APIs and return return append(apis, []rpc.API{ { @@ -527,9 +513,6 @@ func (s *Ethereum) Protocols() []p2p.Protocol { protos[i].Attributes = []enr.Entry{s.currentEthEntry()} protos[i].DialCandidates = s.dialCandidates } - if s.lesServer != nil { - protos = append(protos, s.lesServer.Protocols()...) - } return protos } @@ -551,9 +534,6 @@ func (s *Ethereum) Start() error { } // Start the networking layer and the light server if requested s.protocolManager.Start(maxPeers) - if s.lesServer != nil { - s.lesServer.Start(s.p2pServer) - } return nil } @@ -562,9 +542,6 @@ func (s *Ethereum) Start() error { func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. s.protocolManager.Stop() - if s.lesServer != nil { - s.lesServer.Stop() - } // Then stop everything else. s.bloomIndexer.Close() diff --git a/les/server.go b/les/server.go index 76c906e8b21f..57d335f8a9c3 100644 --- a/les/server.go +++ b/les/server.go @@ -54,6 +54,8 @@ type LesServer struct { minCapacity, maxCapacity, freeCapacity uint64 threadsIdle int // Request serving threads count when system is idle. threadsBusy int // Request serving threads count when system is busy(block insertion). + + p2pSrv *p2p.Server } func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { @@ -87,6 +89,7 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, threadsIdle: threads, + p2pSrv: node.Server(), } srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) @@ -119,6 +122,11 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer "chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot) } srv.chtIndexer.Start(e.BlockChain()) + + node.RegisterProtocols(srv.Protocols()) + node.RegisterAPIs(srv.APIs()) + node.RegisterLifecycle(srv) + return srv, nil } @@ -160,14 +168,14 @@ func (s *LesServer) Protocols() []p2p.Protocol { } // Start starts the LES server -func (s *LesServer) Start(srvr *p2p.Server) { - s.privateKey = srvr.PrivateKey +func (s *LesServer) Start() error { + s.privateKey = s.p2pSrv.PrivateKey s.handler.start() s.wg.Add(1) go s.capacityManagement() - if srvr.DiscV5 != nil { + if s.p2pSrv.DiscV5 != nil { for _, topic := range s.lesTopics { topic := topic go func() { @@ -175,14 +183,16 @@ func (s *LesServer) Start(srvr *p2p.Server) { logger.Info("Starting topic registration") defer logger.Info("Terminated topic registration") - srvr.DiscV5.RegisterTopic(topic, s.closeCh) + s.p2pSrv.DiscV5.RegisterTopic(topic, s.closeCh) }() } } + + return nil } // Stop stops the LES service -func (s *LesServer) Stop() { +func (s *LesServer) Stop() error { close(s.closeCh) // Disconnect existing sessions. @@ -201,6 +211,8 @@ func (s *LesServer) Stop() { s.chtIndexer.Close() s.wg.Wait() log.Info("Les server stopped") + + return nil } func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { From 1844d22469df9e9b023325b481d14e21f7eebdd7 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:41:07 +0200 Subject: [PATCH 086/160] remove Fatalf from node, should panic instead --- graphql/service.go | 2 +- node/node.go | 24 +----------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index 72ecf35bcb22..61d097bb3f25 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -28,7 +28,7 @@ import ( // New constructs a new GraphQL service instance. func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { if backend == nil { - stack.Fatalf("missing backend") + panic("missing backend") } // check if http server with given endpoint exists and enable graphQL on it return newHandler(stack, backend, cors, vhosts) diff --git a/node/node.go b/node/node.go index 63409fa169d8..377685ac38e3 100644 --- a/node/node.go +++ b/node/node.go @@ -20,13 +20,11 @@ import ( "context" "errors" "fmt" - "io" "net" "net/http" "os" "path/filepath" "reflect" - "runtime" "strings" "sync" "sync/atomic" @@ -192,7 +190,7 @@ func (n *Node) Close() error { func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { kind := reflect.TypeOf(lifecycle) if _, exists := n.lifecycles[kind]; exists { - n.Fatalf("Lifecycle cannot be registered more than once", kind) + panic(fmt.Sprintf("Lifecycle cannot be registered more than once: %v", kind)) } n.lifecycles[kind] = lifecycle @@ -673,23 +671,3 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server } return nil } - -// Fatalf formats a message to standard error and exits the program. -// The message is also printed to standard output if standard error -// is redirected to a different file. -func (n *Node) Fatalf(format string, args ...interface{}) { - w := io.MultiWriter(os.Stdout, os.Stderr) - if runtime.GOOS == "windows" { - // The SameFile check below doesn't work on Windows. - // stdout is unlikely to get redirected though, so just print there. - w = os.Stdout - } else { - outf, _ := os.Stdout.Stat() - errf, _ := os.Stderr.Stat() - if outf != nil && errf != nil && os.SameFile(outf, errf) { - w = os.Stderr - } - } - fmt.Fprintf(w, "Fatal: "+format+"\n", args...) - os.Exit(1) -} From a24c7b802c7f13225b53daa58bc86394af548948 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 14:23:05 +0200 Subject: [PATCH 087/160] eth: remove AddLesServer --- cmd/utils/flags.go | 1 - eth/backend.go | 12 ++---------- les/api_test.go | 1 - les/server.go | 9 +++++---- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7d837eaf2839..e4536366407a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1690,7 +1690,6 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { ls, _ := les.NewLesServer(stack, backend, cfg) stack.RegisterProtocols(ls.Protocols()) // TODO should this happen? stack.RegisterAPIs(ls.APIs()) - backend.AddLesServer(ls) } return backend.APIBackend } diff --git a/eth/backend.go b/eth/backend.go index e29844211c56..fa03f547e167 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -53,10 +53,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -type LesServer interface { - SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) -} - // Ethereum implements the Ethereum full node service. type Ethereum struct { config *Config @@ -65,7 +61,6 @@ type Ethereum struct { txPool *core.TxPool blockchain *core.BlockChain protocolManager *ProtocolManager - lesServer LesServer dialCandidates enode.Iterator // DB interfaces @@ -93,10 +88,6 @@ type Ethereum struct { lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) } -func (s *Ethereum) AddLesServer(ls LesServer) { - ls.SetBloomBitsIndexer(s.bloomIndexer) -} - // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) func New(stack *node.Node, config *Config) (*Ethereum, error) { @@ -285,7 +276,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { apis := ethapi.GetAPIs(s.APIBackend) - + // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) @@ -503,6 +494,7 @@ func (s *Ethereum) NetVersion() uint64 { return s.networkID } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } +func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } // Protocols returns all the currently configured // network protocols to start. diff --git a/les/api_test.go b/les/api_test.go index 0d8baac85491..14387865facf 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -512,6 +512,5 @@ func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.L if err != nil { return nil, err } - ethereum.AddLesServer(server) return ethereum, nil } diff --git a/les/server.go b/les/server.go index 57d335f8a9c3..b856d24306ff 100644 --- a/les/server.go +++ b/les/server.go @@ -55,7 +55,7 @@ type LesServer struct { threadsIdle int // Request serving threads count when system is idle. threadsBusy int // Request serving threads count when system is busy(block insertion). - p2pSrv *p2p.Server + p2pSrv *p2p.Server } func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { @@ -89,14 +89,16 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, threadsIdle: threads, - p2pSrv: node.Server(), + p2pSrv: node.Server(), } srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.freeCapacity = srv.minCapacity - srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config) + // Initialize the bloom trie indexer. + e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer) + // Initialize server capacity management fields. srv.defParams = flowcontrol.ServerParams{ BufLimit: srv.freeCapacity * bufLimitRatio, @@ -216,7 +218,6 @@ func (s *LesServer) Stop() error { } func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { - bloomIndexer.AddChildIndexer(s.bloomTrieIndexer) } // capacityManagement starts an event handler loop that updates the recharge curve of From aaee36b21cf4bca40728d60171217c447becb352 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 14:24:33 +0200 Subject: [PATCH 088/160] cmd/utils: remove duplicate protocol/API registration --- cmd/utils/flags.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e4536366407a..6af946dffe8a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1684,12 +1684,13 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { } else { backend, err := eth.New(stack, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %w", err) + Fatalf("Failed to register the Ethereum service: %v", err) } if cfg.LightServ > 0 { - ls, _ := les.NewLesServer(stack, backend, cfg) - stack.RegisterProtocols(ls.Protocols()) // TODO should this happen? - stack.RegisterAPIs(ls.APIs()) + _, err := les.NewLesServer(stack, backend, cfg) + if err != nil { + Fatalf("Failed to create the LES server: %v", err) + } } return backend.APIBackend } From ebc70617a158609f0969393cf20eaf6825db93d9 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:47:20 +0200 Subject: [PATCH 089/160] remove newgqlhandler from rpcstack --- node/rpcstack.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index fe2d2673af71..6d4c536caa0f 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -228,21 +228,3 @@ func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && strings.ToLower(r.Header.Get("Connection")) == "upgrade" } - -// NewGQLUpgradeHandler wraps the given handler, h, in the given graphQL handler. -func NewGQLUpgradeHandler(h http.Handler, gql http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if isGQL(r) { - gql.ServeHTTP(w, r) - log.Debug("serving graphql request") - return - } - - h.ServeHTTP(w, r) - }) -} - -// isGQL checks if the given request is a graphQL request. -func isGQL(r *http.Request) bool { - return r.URL.Path == "/graphql" || r.URL.Path == "/graphql/" -} From 5d2e75539187db6673d1fe08fe78098c4c7478b6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 14:46:41 +0200 Subject: [PATCH 090/160] node: stop lifecycles in reverse order --- node/node.go | 67 +++++++++++++++++++++++++++-------------------- node/node_test.go | 19 +++----------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/node/node.go b/node/node.go index 377685ac38e3..d909b8c06e17 100644 --- a/node/node.go +++ b/node/node.go @@ -50,13 +50,13 @@ type Node struct { instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory lock sync.RWMutex - stop chan struct{} // Channel to wait for termination notifications - server *p2p.Server // Currently running P2P networking layer - lifecycles map[reflect.Type]Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *httpServer // Stores information about the ipc http server + stop chan struct{} // Channel to wait for termination notifications + server *p2p.Server // Currently running P2P networking layer + lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + rpcAPIs []rpc.API // List of APIs currently provided by the node + ipc *httpServer // Stores information about the ipc http server } // New creates a new P2P node, ready for protocol registration. @@ -98,7 +98,6 @@ func New(conf *Config) (*Node, error) { accman: am, ephemeralKeystore: ephemeralKeystore, config: conf, - lifecycles: make(map[reflect.Type]Lifecycle), httpServers: make(serverMap), ipc: &httpServer{ endpoint: conf.IPCEndpoint(), @@ -188,12 +187,12 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node. func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - kind := reflect.TypeOf(lifecycle) - if _, exists := n.lifecycles[kind]; exists { - panic(fmt.Sprintf("Lifecycle cannot be registered more than once: %v", kind)) + for _, obj := range n.lifecycles { + if obj == lifecycle { + panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) + } } - - n.lifecycles[kind] = lifecycle + n.lifecycles = append(n.lifecycles, lifecycle) } // RegisterProtocols adds backend's protocols to the node's p2p server. @@ -295,7 +294,7 @@ func (n *Node) Start() error { var started []Lifecycle for _, lifecycle := range n.lifecycles { if err := lifecycle.Start(); err != nil { - n.stopLifecycles(started) + stopLifecycles(started) n.server.Stop() return err } @@ -307,11 +306,25 @@ func (n *Node) Start() error { return nil } -// stopLifecycles stops the node's running Lifecycles. -func (n *Node) stopLifecycles(started []Lifecycle) { - for _, lifecycle := range started { - lifecycle.Stop() +// containsLifecycle checks if 'lfs' contains 'l'. +func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { + for _, obj := range lfs { + if obj == l { + return true + } + } + return false +} + +// stopLifecycles stops the given lifecycles in reverse order. +func stopLifecycles(lfs []Lifecycle) map[reflect.Type]error { + errors := make(map[reflect.Type]error) + for i := len(lfs) - 1; i >= 0; i-- { + if err := lfs[i].Stop(); err != nil { + errors[reflect.TypeOf(lfs[i])] = err + } } + return errors } // Config returns the configuration of node. @@ -364,10 +377,12 @@ func (n *Node) configureRPC() error { return err } } + // only register http server as a lifecycle if it has not already been registered - if _, exists := n.lifecycles[reflect.TypeOf(n.httpServers)]; !exists { - n.RegisterLifecycle(n.httpServers) + if !containsLifecycle(n.lifecycles, &n.httpServers) { + n.RegisterLifecycle(&n.httpServers) } + // All API endpoints started successfully return nil } @@ -468,15 +483,9 @@ func (n *Node) Stop() error { // Terminate the API, services and the p2p server. n.stopIPC() n.rpcAPIs = nil - failure := &StopError{ - Services: make(map[reflect.Type]error), - } - for kind, lifecycle := range n.lifecycles { - if err := lifecycle.Stop(); err != nil { - failure.Services[reflect.TypeOf(lifecycle)] = err - } - delete(n.lifecycles, kind) - } + failure := new(StopError) + failure.Services = stopLifecycles(n.lifecycles) + n.server.Stop() n.server = nil diff --git a/node/node_test.go b/node/node_test.go index f417eb18215d..d1a40658a8f9 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -119,7 +119,7 @@ func TestLifecycleRegistry_Successful(t *testing.T) { noop := NewNoop() stack.RegisterLifecycle(noop) - if _, exists := stack.lifecycles[reflect.TypeOf(noop)]; !exists { + if !containsLifecycle(stack.lifecycles, noop) { t.Fatalf("lifecycle was not properly registered on the node, %v", err) } } @@ -463,18 +463,11 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("node has more than 1 http server") } // check to make sure http servers are registered - var exists bool - for _, lifecycle := range node1.lifecycles { - if reflect.DeepEqual(node1.httpServers, lifecycle) { - exists = true - } - } - if !exists { + if !containsLifecycle(node1.lifecycles, &node1.httpServers) { t.Fatal("HTTP servers not registered as lifecycles on the node") } // check to make sure http servers are configured properly for _, server := range node1.httpServers { - if atomic.LoadInt32(&server.WSAllowed) == 0 && atomic.LoadInt32(&server.RPCAllowed) == 0 { t.Fatalf("node's http server is not configured to handle both rpc and ws") } @@ -491,13 +484,7 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("amount of http servers on the node is not equal to 2") } // check to make sure http servers are registered - exists = false - for _, lifecycle := range node2.lifecycles { - if reflect.DeepEqual(node2.httpServers, lifecycle) { - exists = true - } - } - if !exists { + if !containsLifecycle(node2.lifecycles, &node2.httpServers) { t.Fatal("HTTP servers not registered as lifecycles on the node") } // check that neither http server has both ws and rpc enabled From 1b50b554ed7dbb77c6a8bfc17ace9006ffb73e4f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 10 Jul 2020 14:50:17 +0200 Subject: [PATCH 091/160] node: simplify RegisterLifecycle --- node/node.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index d909b8c06e17..4692d81172d8 100644 --- a/node/node.go +++ b/node/node.go @@ -187,10 +187,8 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node. func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - for _, obj := range n.lifecycles { - if obj == lifecycle { - panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) - } + if containsLifecycle(n.lifecycles, lifecycle) { + panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) } n.lifecycles = append(n.lifecycles, lifecycle) } From 489bb0580371ca41d26ae1f7e419d7e2a7cae45e Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:54:35 +0200 Subject: [PATCH 092/160] donnt check ls --- les/api_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/les/api_test.go b/les/api_test.go index 14387865facf..7f6aca55cc5a 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -508,7 +508,7 @@ func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.L if err != nil { return nil, err } - server, err := NewLesServer(stack, ethereum, &config) + _, err = NewLesServer(stack, ethereum, &config) if err != nil { return nil, err } From b2931b65b845df63a92a03f54c5ca3d10ba4e966 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 15:46:02 +0200 Subject: [PATCH 093/160] Explicit type check for backend (#27) --- cmd/geth/main.go | 17 ++++--- eth/api_backend.go | 4 -- ethstats/ethstats.go | 100 +++++++++++++++++++++++++------------ go.sum | 5 -- internal/ethapi/backend.go | 9 +--- les/api_backend.go | 19 ------- 6 files changed, 79 insertions(+), 75 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 8a155a76a6df..735aeb653db0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -19,6 +19,7 @@ package main import ( "fmt" + "github.com/ethereum/go-ethereum/eth" "math" "os" godebug "runtime/debug" @@ -378,7 +379,6 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if err != nil { utils.Fatalf("Failed to attach to self: %v", err) } - ethClient := ethclient.NewClient(rpcClient) go func() { @@ -444,23 +444,24 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } + ethBackend, ok := backend.(*eth.EthAPIBackend) + if !ok { + utils.Fatalf("Ethereum service not running: %v", err) + } + // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) } - txpool := backend.TxPool() - if txpool == nil { - utils.Fatalf("Ethereum service not running: %v", err) - } - txpool.SetGasPrice(gasprice) - + ethBackend.TxPool().SetGasPrice(gasprice) + // start mining threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") } - if err := backend.StartMining(threads); err != nil { + if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } diff --git a/eth/api_backend.go b/eth/api_backend.go index 1b0e841d80db..0e91691d8f51 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -318,10 +318,6 @@ func (b *EthAPIBackend) Engine() consensus.Engine { return b.eth.engine } -func (b *EthAPIBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { - return b.eth.blockchain.GetBlockByNumber(number), nil -} - func (b *EthAPIBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 2f8766ef8cca..b1e42d18da1e 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -22,6 +22,10 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/rpc" "math/big" "net/http" "regexp" @@ -36,7 +40,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -56,14 +59,29 @@ const ( chainHeadChanSize = 10 ) -// Backend encompasses the backend behaviors needed for the ethstats service. -type Backend ethapi.Backend +type backend interface{ + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription + SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription + CurrentHeader() *types.Header + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + GetTd(blockHash common.Hash) *big.Int + Stats() (pending int, queued int) + Downloader() *downloader.Downloader +} + +type fullNodeBackend interface{ + backend + Miner() *miner.Miner + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + CurrentBlock() *types.Block + SuggestPrice(ctx context.Context) (*big.Int, error) +} // Service implements an Ethereum netstats reporting daemon that pushes local // chain statistics up to a monitoring server. type Service struct { server *p2p.Server // Peer-to-peer server to retrieve networking infos - backend Backend + backend backend engine consensus.Engine // Consensus engine to retrieve variadic block fields node string // Name of the node to display on the monitoring page @@ -75,7 +93,7 @@ type Service struct { } // New returns a monitoring service ready for stats reporting. -func New(node *node.Node, backend Backend, engine consensus.Engine, url string) error { +func New(node *node.Node, backend backend, engine consensus.Engine, url string) error { // Parse the netstats connection url re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") parts := re.FindStringSubmatch(url) @@ -530,18 +548,30 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { uncles []*types.Header ) - // Full nodes have all needed information available - if block == nil { - block = s.backend.CurrentBlock() - } - header = block.Header() - td = s.backend.GetTd(header.Hash()) // TODO is it okay to just call `GetTD` with just the hash and not number? + // check if backend is a full node + fullBackend, ok := s.backend.(fullNodeBackend) + if ok { + if block == nil { + block = fullBackend.CurrentBlock() + } + header = block.Header() + td = fullBackend.GetTd(header.Hash()) - txs = make([]txStats, len(block.Transactions())) - for i, tx := range block.Transactions() { - txs[i].Hash = tx.Hash() + txs = make([]txStats, len(block.Transactions())) + for i, tx := range block.Transactions() { + txs[i].Hash = tx.Hash() + } + uncles = block.Uncles() + } else { + // Light nodes would need on-demand lookups for transactions/uncles, skip + if block != nil { + header = block.Header() + } else { + header = s.backend.CurrentHeader() + } + td = s.backend.GetTd(header.Hash()) + txs = []txStats{} } - uncles = block.Uncles() // Assemble and return the block stats author, _ := s.engine.Author(header) @@ -585,12 +615,16 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { // Gather the batch of blocks to report history := make([]*blockStats, len(indexes)) for i, number := range indexes { + fullBackend, ok := s.backend.(fullNodeBackend) // Retrieve the next block if it's known to us - block, err := s.backend.GetBlockByNumber(context.Background(), number) - if err != nil { - return err + var block *types.Block + if ok { + block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ? + } else { + if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil { + block = types.NewBlockWithHeader(header) + } } - // If we do have the block, add to the history and continue if block != nil { history[len(history)-1-i] = s.assembleBlockStats(block) @@ -652,27 +686,31 @@ type nodeStats struct { Uptime int `json:"uptime"` } -// reportPending retrieves various stats about the node at the networking and +// reportStats retrieves various stats about the node at the networking and // mining layer and reports it to the stats server. func (s *Service) reportStats(conn *websocket.Conn) error { // Gather the syncing and mining infos from the local miner instance var ( mining bool hashrate int + syncing bool + gasprice int ) + // check if backend is a full node + fullBackend, ok := s.backend.(fullNodeBackend) + if ok { + mining = fullBackend.Miner().Mining() + hashrate = int(fullBackend.Miner().HashRate()) - mine := s.backend.Miner() - if mine != nil { - mining = mine.Mining() - hashrate = int(s.backend.Miner().HashRate()) - } - - sync := s.backend.Downloader().Progress() - syncing := s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock - - price, _ := s.backend.SuggestPrice(context.Background()) - gasprice := int(price.Uint64()) + sync := fullBackend.Downloader().Progress() + syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock + price, _ := fullBackend.SuggestPrice(context.Background()) + gasprice = int(price.Uint64()) + } else { + sync := s.backend.Downloader().Progress() + syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock + } // Assemble the node stats and send it to the server log.Trace("Sending node details to ethstats") diff --git a/go.sum b/go.sum index d0e87bd69cc3..7546ca177394 100644 --- a/go.sum +++ b/go.sum @@ -218,20 +218,15 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 12faa307171e..c92d8b20c48a 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -47,8 +46,8 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs - RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection // Blockchain API SetHead(number uint64) @@ -60,7 +59,6 @@ type Backend interface { BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) - GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) @@ -78,13 +76,8 @@ type Backend interface { GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) Stats() (pending int, queued int) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) - TxPool() *core.TxPool SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription - // Mining API - Miner() *miner.Miner - StartMining(threads int) error - // Filter API BloomStatus() (uint64, uint64) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) diff --git a/les/api_backend.go b/les/api_backend.go index 25dde0a2cd57..75bea56da67a 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -19,7 +19,6 @@ package les import ( "context" "errors" - "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts" @@ -36,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -290,23 +288,6 @@ func (b *LesApiBackend) Engine() consensus.Engine { return b.eth.engine } -func (b *LesApiBackend) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { - header := b.eth.blockchain.GetHeaderByNumber(number) - return types.NewBlockWithHeader(header), nil // TODO is this okay? -} - func (b *LesApiBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } - -func (b *LesApiBackend) Miner() *miner.Miner { - return nil -} - -func (b *LesApiBackend) StartMining(threads int) error { - return fmt.Errorf("Light clients do not support mining") // TODO is this okay? -} - -func (b *LesApiBackend) TxPool() *core.TxPool { - return nil // TODO is this okay? -} From e7602e3feb97d91d061801c3b1fa04caec4fc2d3 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Fri, 10 Jul 2020 15:50:25 +0200 Subject: [PATCH 094/160] go mod tidy --- go.sum | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/go.sum b/go.sum index 7546ca177394..d0e87bd69cc3 100644 --- a/go.sum +++ b/go.sum @@ -218,15 +218,20 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= From 9ee9bbea2a67d79d4c865e587b010f64e565f741 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 13 Jul 2020 13:46:19 +0200 Subject: [PATCH 095/160] linted --- cmd/geth/main.go | 2 +- ethstats/ethstats.go | 17 ++++++++++------- graphql/graphql_test.go | 2 +- graphql/service.go | 7 ++++--- internal/ethapi/backend.go | 2 +- node/rpcstack.go | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 735aeb653db0..71a1c19e1d84 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -19,7 +19,6 @@ package main import ( "fmt" - "github.com/ethereum/go-ethereum/eth" "math" "os" godebug "runtime/debug" @@ -33,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index b1e42d18da1e..a32ae71d491f 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -22,10 +22,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/miner" - "github.com/ethereum/go-ethereum/rpc" "math/big" "net/http" "regexp" @@ -40,10 +36,14 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" ) @@ -59,7 +59,8 @@ const ( chainHeadChanSize = 10 ) -type backend interface{ +// backend encompasses the bare-minimum functionality needed for ethstats reporting +type backend interface { SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription CurrentHeader() *types.Header @@ -69,7 +70,9 @@ type backend interface{ Downloader() *downloader.Downloader } -type fullNodeBackend interface{ +// fullNodeBackend encompasses the functionality necessary for a full node +// reporting to ethstats +type fullNodeBackend interface { backend Miner() *miner.Miner BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) @@ -693,7 +696,7 @@ func (s *Service) reportStats(conn *websocket.Conn) error { var ( mining bool hashrate int - syncing bool + syncing bool gasprice int ) // check if backend is a full node diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 366685151014..203bdea82bde 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -122,7 +122,7 @@ func createGQLService(t *testing.T, stack *node.Node, endpoint string) { } // create gql service - err = New(stack, ethBackend.APIBackend,[]string{}, []string{}) + err = New(stack, ethBackend.APIBackend, []string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 61d097bb3f25..302fd678f1d9 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -18,6 +18,7 @@ package graphql import ( "fmt" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -46,9 +47,9 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) h := &relay.Handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) - endpoint := stack.RegisterPath("/graphql/ui", GraphiQL{}) - endpoint = stack.RegisterPath("/graphql", handler) - endpoint = stack.RegisterPath("/graphql/", handler) + _ = stack.RegisterPath("/graphql/ui", GraphiQL{}) + _ = stack.RegisterPath("/graphql", handler) + endpoint := stack.RegisterPath("/graphql/", handler) if endpoint != "" { log.Info("GraphQL configured on endpoint", "endpoint", fmt.Sprintf("http://%s/graphql", endpoint)) diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index c92d8b20c48a..10e716bf200d 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -46,7 +46,7 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool - RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs // Blockchain API diff --git a/node/rpcstack.go b/node/rpcstack.go index 6d4c536caa0f..43716a4dbd66 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -36,7 +36,7 @@ import ( type serverMap map[string]*httpServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql type httpServer struct { - srvMux http.ServeMux + srvMux http.ServeMux handler http.Handler Srv *rpc.Server From b26ea835bbefc1bffa0f8e512212aed54c3b4f3c Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Wed, 15 Jul 2020 12:58:34 +0200 Subject: [PATCH 096/160] changes requested by gary --- les/server.go | 4 ---- miner/stress_clique.go | 10 +++++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/les/server.go b/les/server.go index b856d24306ff..ec856c977c7c 100644 --- a/les/server.go +++ b/les/server.go @@ -21,7 +21,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" @@ -217,9 +216,6 @@ func (s *LesServer) Stop() error { return nil } -func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) { -} - // capacityManagement starts an event handler loop that updates the recharge curve of // the client manager and adjusts the client pool's size according to the total // capacity updates coming from the client manager diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 143ce3f5eb2e..d09538050164 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -22,7 +22,6 @@ package main import ( "bytes" "crypto/ecdsa" - "github.com/ethereum/go-ethereum/internal/ethapi" "io/ioutil" "math/big" "math/rand" @@ -36,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -60,8 +60,8 @@ func main() { genesis := makeGenesis(faucets, sealers) var ( - nodes []struct{ - node *node.Node + nodes []struct { + node *node.Node backend ethapi.Backend } enodes []*enode.Node @@ -83,8 +83,8 @@ func main() { node.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, struct{ - node *node.Node + nodes = append(nodes, struct { + node *node.Node backend ethapi.Backend }{ node, From dd06152fbf027d704ec9d3d4554ba98887af37b2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 13 Jul 2020 12:56:41 +0200 Subject: [PATCH 097/160] node: unexport CreateHTTPServer --- node/api.go | 4 ++-- node/node.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/node/api.go b/node/api.go index 55956f63ded0..60a960fe0e87 100644 --- a/node/api.go +++ b/node/api.go @@ -204,7 +204,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis handler := NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) httpServer.srvMux.Handle("/", handler) // create HTTP server - if err := api.node.CreateHTTPServer(httpServer, false); err != nil { + if err := api.node.createHTTPServer(httpServer, false); err != nil { return false, err } // register the HTTP server @@ -293,7 +293,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str handler := wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) wsServer.srvMux.Handle("/", handler) // create the HTTP server - if err := api.node.CreateHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { + if err := api.node.createHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { return false, err } // register the HTTP server diff --git a/node/node.go b/node/node.go index 4692d81172d8..25daaf3fa6da 100644 --- a/node/node.go +++ b/node/node.go @@ -228,8 +228,8 @@ func (n *Node) ExistingHTTPServer(endpoint string) *httpServer { return nil } -// CreateHTTPServer creates an http.Server and adds it to the given httpServer. -func (n *Node) CreateHTTPServer(h *httpServer, exposeAll bool) error { +// createHTTPServer creates an http.Server and adds it to the given httpServer. +func (n *Node) createHTTPServer(h *httpServer, exposeAll bool) error { // register apis and create handler stack err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) if err != nil { @@ -371,7 +371,7 @@ func (n *Node) configureRPC() error { server.srvMux.Handle("/", handler) } // create the HTTP server - if err := n.CreateHTTPServer(server, false); err != nil { + if err := n.createHTTPServer(server, false); err != nil { return err } } From 57ffd7c91eab5a8f7a187221905227183e4b5bfe Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 13 Jul 2020 12:57:00 +0200 Subject: [PATCH 098/160] graphql: don't check for HTTP server creation The test request will just fail if the server isn't there. --- graphql/graphql_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 203bdea82bde..091102889144 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -68,15 +68,10 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { stack := createNode(t, false) defer stack.Close() - // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } - // make sure GQL is not enabled - server := stack.ExistingHTTPServer("127.0.0.1:9393") - if server == nil { - t.Fatalf("server was not created on the given endpoint") - } + // create http request body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) From c8b01c431e22549d4df1cdeaf87ce2869a1f1ff5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 13 Jul 2020 12:58:11 +0200 Subject: [PATCH 099/160] node: unexport ExistingHTTPServer --- node/api.go | 2 +- node/node.go | 4 ++-- node/node_test.go | 10 +++++----- node/rpcstack_test.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/node/api.go b/node/api.go index 60a960fe0e87..810697fafb35 100644 --- a/node/api.go +++ b/node/api.go @@ -263,7 +263,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } } // check if an HTTP server exists on the given endpoint, and if so, enable websocket on that HTTP server - existingServer := api.node.ExistingHTTPServer(endpoint) + existingServer := api.node.existingHTTPServer(endpoint) if existingServer != nil { atomic.StoreInt32(&existingServer.WSAllowed, 1) existingServer.WsOrigins = origins diff --git a/node/node.go b/node/node.go index 25daaf3fa6da..a91a3cc50b5d 100644 --- a/node/node.go +++ b/node/node.go @@ -220,8 +220,8 @@ func (n *Node) RegisterPath(path string, handler http.Handler) string { return "" } -// ExistingHTTPServer checks if an HTTP server is already configured on the given endpoint. -func (n *Node) ExistingHTTPServer(endpoint string) *httpServer { +// existingHTTPServer checks if an HTTP server is already configured on the given endpoint. +func (n *Node) existingHTTPServer(endpoint string) *httpServer { if server, exists := n.httpServers[endpoint]; exists { return server } diff --git a/node/node_test.go b/node/node_test.go index d1a40658a8f9..5f10e9ffcab9 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -394,13 +394,13 @@ func TestRegisterHTTPServer(t *testing.T) { } endpointNoop := fmt.Sprintf("%s:%d", noop.host, noop.port) - if srv1 != stack.ExistingHTTPServer(endpoint1) { + if srv1 != stack.existingHTTPServer(endpoint1) { t.Fatalf("server %v was not properly registered on the given endpoint %s", srv1, endpoint1) } - if srv2 != stack.ExistingHTTPServer(endpoint2) { + if srv2 != stack.existingHTTPServer(endpoint2) { t.Fatalf("server %v was not properly registered on the given endpoint %s", srv2, endpoint2) } - if noop == stack.ExistingHTTPServer(endpointNoop) { + if noop == stack.existingHTTPServer(endpointNoop) { t.Fatalf("server %v was incorrectly registered on the given endpoint %s", noop, endpointNoop) } } @@ -472,7 +472,7 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("node's http server is not configured to handle both rpc and ws") } node1.stopServer(server) - if node1.ExistingHTTPServer(server.endpoint) != nil { + if node1.existingHTTPServer(server.endpoint) != nil { t.Fatalf("failed to remove server %v from node after stopping it", server) } } @@ -493,7 +493,7 @@ func TestHTTPServerCreateAndStop(t *testing.T) { t.Fatalf("both rpc and ws allowed on a single http server") } node2.stopServer(server) - if node2.ExistingHTTPServer(server.endpoint) != nil { + if node2.existingHTTPServer(server.endpoint) != nil { t.Fatalf("failed to remove server %v from node after stopping it", server) } } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 3dfaf53ed6bc..c00c4d665896 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -59,7 +59,7 @@ func TestWSAllowed(t *testing.T) { t.Fatalf("could not start node: %v", err) } // check that server was configured on the given endpoint - server := stack.ExistingHTTPServer(fmt.Sprintf("%s:%d", DefaultHTTPHost, 9393)) + server := stack.existingHTTPServer(fmt.Sprintf("%s:%d", DefaultHTTPHost, 9393)) if server == nil { t.Fatalf("server was not started on the given endpoint: %v", err) } From 783ad61f28587007c17acf4ace1fe17ff53d302a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 12:02:01 +0200 Subject: [PATCH 100/160] node: add new lifecycle docs --- node/doc.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/node/doc.go b/node/doc.go index e3cc58e5f49c..b257f412fed1 100644 --- a/node/doc.go +++ b/node/doc.go @@ -22,6 +22,43 @@ resources to provide RPC APIs. Services can also offer devp2p protocols, which a up to the devp2p network when the node instance is started. +Node Lifecycle + +The Node object has a lifecycle consisting of three basic states, INITIALIZING, RUNNING +and CLOSED. + + + ●───────┐ + New() + │ + ▼ + INITIALIZING ────Start()─┐ + │ │ + │ ▼ + Close() RUNNING + │ │ + ▼ │ + CLOSED ◀──────Close()─┘ + + +Creating a Node allocates basic resources such as the data directory and returns the node +in its INITIALIZING state. Lifecycle objects, RPC APIs and peer-to-peer networking +protocols can be registered in this state. Basic operations such as opening a key-value +database are permitted while initializing. + +Once everything is registered, the node can be started, which moves it into the RUNNING +state. Starting the node starts all registered Lifecycle objects and enables RPC and +peer-to-peer networking. Note that no additional Lifecycles, APIs or p2p protocols can be +registered while the node is running. + +Closing the node releases all held resources. The actions performed by Close depend on the +state it was in. When closing a node in INITIALIZING state, resources related to the data +directory are released. If the node was RUNNING, closing it also stops all Lifecycle +objects and shuts down RPC and peer-to-peer networking. + +You must always call Close on Node, even if the node was not started. + + Resources Managed By Node All file-system resources used by a node instance are located in a directory called the From af82314eb1861e97a7b46f718a45007aa988222e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 12:02:30 +0200 Subject: [PATCH 101/160] node: fix comment on Start --- node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index a91a3cc50b5d..2a848568c7bf 100644 --- a/node/node.go +++ b/node/node.go @@ -262,7 +262,7 @@ func (n *Node) running() bool { return n.server.Running() } -// Start creates a live P2P node and starts running it. +// Start starts all registered lifecycles, RPC services and p2p networking. func (n *Node) Start() error { n.lock.Lock() defer n.lock.Unlock() From 33ffe5bcac6c3c2e25901b404252fc3250d37fb8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 12:25:48 +0200 Subject: [PATCH 102/160] node: move datadir cleanup from Stop to Close --- node/node.go | 75 +++++++++++++++++++++++++---------------------- node/node_test.go | 11 ++----- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/node/node.go b/node/node.go index 2a848568c7bf..43f587fb6009 100644 --- a/node/node.go +++ b/node/node.go @@ -83,22 +83,13 @@ func New(conf *Config) (*Node, error) { if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } - // Ensure that the AccountManager method works before the node has started. - // We rely on this in cmd/geth. - am, ephemeralKeystore, err := makeAccountManager(conf) - if err != nil { - return nil, err - } if conf.Logger == nil { conf.Logger = log.New() } - // Note: any interaction with Config that would create/touch files - // in the data directory or instance directory is delayed until Start. + node := &Node{ - accman: am, - ephemeralKeystore: ephemeralKeystore, - config: conf, - httpServers: make(serverMap), + config: conf, + httpServers: make(serverMap), ipc: &httpServer{ endpoint: conf.IPCEndpoint(), }, @@ -107,6 +98,19 @@ func New(conf *Config) (*Node, error) { log: conf.Logger, } + // Acquire the instance directory lock. + if err := node.openDataDir(); err != nil { + return nil, err + } + // Ensure that the AccountManager method works before the node has started. + // We rely on this in cmd/geth. + am, ephemeralKeystore, err := makeAccountManager(conf) + if err != nil { + return nil, err + } + node.accman = am + node.ephemeralKeystore = ephemeralKeystore + // Initialize the p2p server. This creates the node key and // discovery databases. node.server = &p2p.Server{Config: conf.P2P} @@ -167,13 +171,24 @@ func New(conf *Config) (*Node, error) { func (n *Node) Close() error { var errs []error - // Terminate all subsystems and collect any errors - if err := n.Stop(); err != nil && err != ErrNodeStopped { - errs = append(errs, err) + if n.server != nil && n.running() { + // The node is in RUNNING state, stop lifecycles first. + if err := n.Stop(); err != nil && err != ErrNodeStopped { + errs = append(errs, err) + } } + + // Release resources acquired by New(). + n.closeDataDir() if err := n.accman.Close(); err != nil { errs = append(errs, err) } + if n.ephemeralKeystore != "" { + if err := os.RemoveAll(n.ephemeralKeystore); err != nil { + errs = append(errs, err) + } + } + // Report any errors that might have occurred switch len(errs) { case 0: @@ -271,9 +286,6 @@ func (n *Node) Start() error { if n.running() { return ErrNodeRunning } - if err := n.openDataDir(); err != nil { - return err - } // Start the p2p node if err := n.server.Start(); err != nil { @@ -349,6 +361,16 @@ func (n *Node) openDataDir() error { return nil } +func (n *Node) closeDataDir() { + // Release instance directory lock. + if n.instanceDirLock != nil { + if err := n.instanceDirLock.Release(); err != nil { + n.log.Error("Can't release datadir lock", "err", err) + } + n.instanceDirLock = nil + } +} + // configureRPC is a helper method to configure all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. @@ -487,29 +509,12 @@ func (n *Node) Stop() error { n.server.Stop() n.server = nil - // Release instance directory lock. - if n.instanceDirLock != nil { - if err := n.instanceDirLock.Release(); err != nil { - n.log.Error("Can't release datadir lock", "err", err) - } - n.instanceDirLock = nil - } - // unblock n.Wait close(n.stop) - // Remove the keystore if it was created ephemerally. - var keystoreErr error - if n.ephemeralKeystore != "" { - keystoreErr = os.RemoveAll(n.ephemeralKeystore) - } - if len(failure.Services) > 0 { return failure } - if keystoreErr != nil { - return keystoreErr - } return nil } diff --git a/node/node_test.go b/node/node_test.go index 5f10e9ffcab9..4435265635ea 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -97,13 +97,8 @@ func TestNodeUsedDataDir(t *testing.T) { defer original.Stop() // Create a second node based on the same data directory and ensure failure - duplicate, err := New(&Config{DataDir: dir}) - if err != nil { - t.Fatalf("failed to create duplicate protocol stack: %v", err) - } - defer duplicate.Close() - - if err := duplicate.Start(); err != ErrDatadirUsed { + _, err = New(&Config{DataDir: dir}) + if err != ErrDatadirUsed { t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) } } @@ -406,7 +401,7 @@ func TestRegisterHTTPServer(t *testing.T) { } // Tests whether a handler can be successfully mounted on the canonical HTTP server -// on the given path +// on the givenq path func TestRegisterPath_Successful(t *testing.T) { node := createNode(t, 7878, 7979) From 767e3b173b8d9f734eb6260b71db2ed3c010113f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 12:29:50 +0200 Subject: [PATCH 103/160] node: simplify stop channel --- node/node.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/node/node.go b/node/node.go index 43f587fb6009..0fdcbc1c6a6d 100644 --- a/node/node.go +++ b/node/node.go @@ -48,15 +48,15 @@ type Node struct { log log.Logger ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory + stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex - stop chan struct{} // Channel to wait for termination notifications - server *p2p.Server // Currently running P2P networking layer - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *httpServer // Stores information about the ipc http server + server *p2p.Server // Currently running P2P networking layer + lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + rpcAPIs []rpc.API // List of APIs currently provided by the node + ipc *httpServer // Stores information about the ipc http server } // New creates a new P2P node, ready for protocol registration. @@ -96,6 +96,7 @@ func New(conf *Config) (*Node, error) { inprocHandler: rpc.NewServer(), eventmux: new(event.TypeMux), log: conf.Logger, + stop: make(chan struct{}), } // Acquire the instance directory lock. @@ -310,9 +311,6 @@ func (n *Node) Start() error { } started = append(started, lifecycle) } - - // Finish initializing the startup - n.stop = make(chan struct{}) return nil } @@ -518,18 +516,9 @@ func (n *Node) Stop() error { return nil } -// Wait blocks the thread until the node is stopped. If the node is not running -// at the time of invocation, the method immediately returns. +// Wait blocks until the node is stopped. func (n *Node) Wait() { - n.lock.RLock() - if n.server == nil { - n.lock.RUnlock() - return - } - stop := n.stop - n.lock.RUnlock() - - <-stop + <-n.stop } // Attach creates an RPC client attached to an in-process API handler. From ce38ec6dbc8ec2f416db32e5dc969dce6785700e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 13:45:25 +0200 Subject: [PATCH 104/160] node: remove Stop This changes the Node lifecycle API to match the new documentation. --- node/node.go | 62 +++++++++++++++++++++++---------------- node/node_example_test.go | 2 +- node/node_test.go | 46 ++++++++++++++++------------- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/node/node.go b/node/node.go index 0fdcbc1c6a6d..69be3aef86b6 100644 --- a/node/node.go +++ b/node/node.go @@ -51,6 +51,7 @@ type Node struct { stop chan struct{} // Channel to wait for termination notifications lock sync.RWMutex + runstate int server *p2p.Server // Currently running P2P networking layer lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. @@ -59,6 +60,12 @@ type Node struct { ipc *httpServer // Stores information about the ipc http server } +const ( + initializingState = iota + runningState + stoppedState +) + // New creates a new P2P node, ready for protocol registration. func New(conf *Config) (*Node, error) { // Copy config and resolve the datadir so future changes to the current @@ -170,11 +177,16 @@ func New(conf *Config) (*Node, error) { // Close stops the Node and releases resources acquired in // Node constructor New. func (n *Node) Close() error { - var errs []error + n.lock.Lock() + defer n.lock.Unlock() - if n.server != nil && n.running() { - // The node is in RUNNING state, stop lifecycles first. - if err := n.Stop(); err != nil && err != ErrNodeStopped { + var errs []error + switch n.runstate { + case stoppedState: + return ErrNodeStopped + case runningState: + // The node was started, release resources acquired by Start(). + if err := n.stopServices(); err != nil { errs = append(errs, err) } } @@ -189,8 +201,12 @@ func (n *Node) Close() error { errs = append(errs, err) } } + n.runstate = stoppedState + + // Unblock n.Wait. + close(n.stop) - // Report any errors that might have occurred + // Report any errors that might have occurred. switch len(errs) { case 0: return nil @@ -273,19 +289,17 @@ func (n *Node) createHTTPServer(h *httpServer, exposeAll bool) error { return nil } -// running returns true if the node's p2p server is already running. -func (n *Node) running() bool { - return n.server.Running() -} - // Start starts all registered lifecycles, RPC services and p2p networking. func (n *Node) Start() error { n.lock.Lock() defer n.lock.Unlock() - // Short circuit if the node's already running - if n.running() { + // Node can only be started when it + switch n.runstate { + case runningState: return ErrNodeRunning + case stoppedState: + return ErrNodeStopped } // Start the p2p node @@ -298,6 +312,7 @@ func (n *Node) Start() error { if err := n.configureRPC(); err != nil { n.httpServers.Stop() n.server.Stop() + n.runstate = stoppedState return err } @@ -307,10 +322,13 @@ func (n *Node) Start() error { if err := lifecycle.Start(); err != nil { stopLifecycles(started) n.server.Stop() + n.runstate = stoppedState return err } started = append(started, lifecycle) } + + n.runstate = runningState return nil } @@ -487,15 +505,11 @@ func (n *Node) stopServer(server *httpServer) { delete(n.httpServers, server.endpoint) } -// Stop terminates a running node along with all it's services. In the node was -// not started, an error is returned. -func (n *Node) Stop() error { - n.lock.Lock() - defer n.lock.Unlock() - - // Short circuit if the node's not running - if n.server == nil || !n.running() { - return ErrNodeStopped +// stopServices terminates running services, RPC and p2p networking. +// It is the inverse of Start. +func (n *Node) stopServices() error { + if n.runstate != runningState { + panic("call to stopServices on node that isn't running") } // Terminate the API, services and the p2p server. @@ -503,13 +517,9 @@ func (n *Node) Stop() error { n.rpcAPIs = nil failure := new(StopError) failure.Services = stopLifecycles(n.lifecycles) - n.server.Stop() n.server = nil - // unblock n.Wait - close(n.stop) - if len(failure.Services) > 0 { return failure } @@ -595,6 +605,8 @@ func (n *Node) EventMux() *event.TypeMux { // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { + // TODO: track databases so they can be closed later. + if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } diff --git a/node/node_example_test.go b/node/node_example_test.go index 6ad74aaca885..d54fe03067df 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -50,7 +50,7 @@ func ExampleLifecycle() { if err := stack.Start(); err != nil { log.Fatalf("Failed to start the protocol stack: %v", err) } - if err := stack.Stop(); err != nil { + if err := stack.Close(); err != nil { log.Fatalf("Failed to stop the protocol stack: %v", err) } // Output: diff --git a/node/node_test.go b/node/node_test.go index 4435265635ea..38222d91ae93 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -45,20 +45,28 @@ func testNodeConfig() *Config { } } -// Tests that an empty protocol stack can be started and stopped. -func TestNodeLifeCycle(t *testing.T) { +// Tests that an empty protocol stack can be closed more than once. +func TestNodeCloseMultipleTimes(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } - defer stack.Close() + stack.Close() // Ensure that a stopped node can be stopped again for i := 0; i < 3; i++ { - if err := stack.Stop(); err != ErrNodeStopped { + if err := stack.Close(); err != ErrNodeStopped { t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped) } } +} + +func TestNodeStartMultipleTimes(t *testing.T) { + stack, err := New(testNodeConfig()) + if err != nil { + t.Fatalf("failed to create protocol stack: %v", err) + } + // Ensure that a node can be successfully started, but only once if err := stack.Start(); err != nil { t.Fatalf("failed to start node: %v", err) @@ -67,10 +75,10 @@ func TestNodeLifeCycle(t *testing.T) { t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning) } // Ensure that a node can be stopped, but only once - if err := stack.Stop(); err != nil { + if err := stack.Close(); err != nil { t.Fatalf("failed to stop node: %v", err) } - if err := stack.Stop(); err != ErrNodeStopped { + if err := stack.Close(); err != ErrNodeStopped { t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped) } } @@ -90,11 +98,9 @@ func TestNodeUsedDataDir(t *testing.T) { t.Fatalf("failed to create original protocol stack: %v", err) } defer original.Close() - if err := original.Start(); err != nil { t.Fatalf("failed to start original protocol stack: %v", err) } - defer original.Stop() // Create a second node based on the same data directory and ensure failure _, err = New(&Config{DataDir: dir}) @@ -212,7 +218,7 @@ func TestLifecycleLifeCycle(t *testing.T) { } } // Stop the node and check that all services have been stopped - if err := stack.Stop(); err != nil { + if err := stack.Close(); err != nil { t.Fatalf("failed to stop protocol stack: %v", err) } for id := range lifecycles { @@ -224,7 +230,7 @@ func TestLifecycleLifeCycle(t *testing.T) { // Tests that if a Lifecycle fails to start, all others started before it will be // shut down. -func TestLifecycleStartupAbortion(t *testing.T) { +func TestLifecycleStartupError(t *testing.T) { stack, err := New(testNodeConfig()) if err != nil { t.Fatalf("failed to create protocol stack: %v", err) @@ -266,17 +272,15 @@ func TestLifecycleStartupAbortion(t *testing.T) { stack.RegisterLifecycle(failer) // Start the protocol stack and ensure all started services stop - for i := 0; i < 100; i++ { - if err := stack.Start(); err != failure { - t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure) - } - for id := range lifecycles { - if started[id] && !stopped[id] { - t.Fatalf("service %s: started but not stopped", id) - } - delete(started, id) - delete(stopped, id) + if err := stack.Start(); err != failure { + t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure) + } + for id := range lifecycles { + if started[id] && !stopped[id] { + t.Fatalf("service %s: started but not stopped", id) } + delete(started, id) + delete(stopped, id) } } @@ -337,7 +341,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { } } // Stop the stack, verify failure and check all terminations - err = stack.Stop() + err = stack.Close() if err, ok := err.(*StopError); !ok { t.Fatalf("termination failure mismatch: have %v, want StopError", err) } else { From cd46fce173e8a990ebe3031082f3875558e16e1d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 13:56:29 +0200 Subject: [PATCH 105/160] all: Node.Stop -> Node.Close --- cmd/faucet/faucet.go | 2 +- cmd/geth/main.go | 2 +- cmd/utils/cmd.go | 2 +- ethclient/ethclient_test.go | 8 ++++---- mobile/geth.go | 12 +++++++----- p2p/simulations/adapters/exec.go | 2 +- p2p/simulations/adapters/inproc.go | 2 +- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 65c71ce4eb03..7dc5536eba13 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -265,7 +265,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u // Attach to the client and retrieve and interesting metadatas api, err := stack.Attach() if err != nil { - stack.Stop() + stack.Close() return nil, err } client := ethclient.NewClient(api) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 71a1c19e1d84..da0b00c74464 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -432,7 +432,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), "age", common.PrettyAge(timestamp)) - stack.Stop() + stack.Close() } } }() diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index a40978bcb746..869cf90ea57b 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -73,7 +73,7 @@ func StartNode(stack *node.Node) { defer signal.Stop(sigc) <-sigc log.Info("Got interrupt, shutting down...") - go stack.Stop() + go stack.Close() for i := 10; i > 0; i-- { <-sigc if i > 1 { diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index b49abe917732..16cf5ce61cee 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -232,7 +232,7 @@ func generateTestChain() (*core.Genesis, []*types.Block) { func TestHeader(t *testing.T) { backend, chain := newTestBackend(t) client, _ := backend.Attach() - defer backend.Stop() + defer backend.Close() defer client.Close() tests := map[string]struct { @@ -276,7 +276,7 @@ func TestHeader(t *testing.T) { func TestBalanceAt(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() - defer backend.Stop() + defer backend.Close() defer client.Close() tests := map[string]struct { @@ -322,7 +322,7 @@ func TestBalanceAt(t *testing.T) { func TestTransactionInBlockInterrupted(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() - defer backend.Stop() + defer backend.Close() defer client.Close() ec := NewClient(client) @@ -340,7 +340,7 @@ func TestTransactionInBlockInterrupted(t *testing.T) { func TestChainID(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() - defer backend.Stop() + defer backend.Close() defer client.Close() ec := NewClient(client) diff --git a/mobile/geth.go b/mobile/geth.go index 22567a5d107b..42b62b718564 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -195,8 +195,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { return &Node{rawStack}, nil } -// Close terminates a running node along with all it's services, tearing internal -// state doen too. It's not possible to restart a closed node. +// Close terminates a running node along with all it's services, tearing internal state +// down. It is not possible to restart a closed node. func (n *Node) Close() error { return n.node.Close() } @@ -206,10 +206,12 @@ func (n *Node) Start() error { return n.node.Start() } -// Stop terminates a running node along with all it's services. If the node was -// not started, an error is returned. +// Stop terminates a running node along with all its services. If the node was not started, +// an error is returned. It is not possible to restart a stopped node. +// +// Deprecated: use Close() func (n *Node) Stop() error { - return n.node.Stop() + return n.node.Close() } // GetEthereumClient retrieves a client to access the Ethereum subsystem. diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 1f7a21470db0..ccb0d82e7d3d 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -400,7 +400,7 @@ func execP2PNode() { defer signal.Stop(sigc) <-sigc log.Info("Received SIGTERM, shutting down...") - stack.Stop() + stack.Close() }() stack.Wait() // Wait for the stack to exit. } diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index c786b9f9ddc1..36e0a0450ed8 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -299,7 +299,7 @@ func (sn *SimNode) Stop() error { sn.client = nil } sn.lock.Unlock() - return sn.node.Stop() + return sn.node.Close() } // Service returns a running service by name From d2c247465ca8a8cb4fb4673d664d7659c9ac3503 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 13:58:11 +0200 Subject: [PATCH 106/160] mobile: add TODO for restart --- mobile/geth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/geth.go b/mobile/geth.go index 42b62b718564..d614f8eb3666 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -203,6 +203,7 @@ func (n *Node) Close() error { // Start creates a live P2P node and starts running it. func (n *Node) Start() error { + // TODO: recreate the node so it can be started multiple times return n.node.Start() } From 4fdd72babf50b30abb0762c9a79bce7b94f4a2ce Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 14:00:13 +0200 Subject: [PATCH 107/160] node: add more runstate checks --- node/node.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 69be3aef86b6..a4875174cdbb 100644 --- a/node/node.go +++ b/node/node.go @@ -541,7 +541,7 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { n.lock.RLock() defer n.lock.RUnlock() - if n.inprocHandler == nil { + if n.runstate == stoppedState { return nil, ErrNodeStopped } return n.inprocHandler, nil @@ -607,6 +607,9 @@ func (n *Node) EventMux() *event.TypeMux { func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { // TODO: track databases so they can be closed later. + if n.runstate == stoppedState { + return nil, ErrNodeStopped + } if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } @@ -619,6 +622,9 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { + if n.runstate == stoppedState { + return nil, ErrNodeStopped + } if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } From 068fe781ffeeb194c8b0b37e6e01d84820b94715 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 14:02:56 +0200 Subject: [PATCH 108/160] node: ensure Register* can only be used in initializing state --- node/node.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/node/node.go b/node/node.go index a4875174cdbb..2be5b8ec34df 100644 --- a/node/node.go +++ b/node/node.go @@ -219,6 +219,9 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node. func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { + if n.runstate != initializingState { + panic(fmt.Sprintf("can't register lifecycle on running/stopped node")) + } if containsLifecycle(n.lifecycles, lifecycle) { panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) } @@ -227,11 +230,17 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { // RegisterProtocols adds backend's protocols to the node's p2p server. func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { + if n.runstate != initializingState { + panic(fmt.Sprintf("can't register protocols on running/stopped node")) + } n.server.Protocols = append(n.server.Protocols, protocols...) } // RegisterAPIs registers the APIs a service provides on the node. func (n *Node) RegisterAPIs(apis []rpc.API) { + if n.runstate != initializingState { + panic(fmt.Sprintf("can't register APIs on running/stopped node")) + } n.rpcAPIs = append(n.rpcAPIs, apis...) } @@ -242,6 +251,9 @@ func (n *Node) RegisterHTTPServer(endpoint string, server *httpServer) { // RegisterPath mounts the given handler on the given path on the canonical HTTP server. func (n *Node) RegisterPath(path string, handler http.Handler) string { + if n.runstate != initializingState { + panic(fmt.Sprintf("can't register HTTP handler on running/stopped node")) + } for _, server := range n.httpServers { if atomic.LoadInt32(&server.RPCAllowed) == 1 { server.srvMux.Handle(path, handler) From 81ecb44852dc250cedd582e7aeee0b8ff2fdd764 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:30:32 +0200 Subject: [PATCH 109/160] removed unnecessary use of fmt.Sprintf --- node/node.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index 2be5b8ec34df..0047cbb6f46b 100644 --- a/node/node.go +++ b/node/node.go @@ -220,7 +220,7 @@ func (n *Node) Close() error { // RegisterLifecycle registers the given Lifecycle on the node. func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { if n.runstate != initializingState { - panic(fmt.Sprintf("can't register lifecycle on running/stopped node")) + panic("can't register lifecycle on running/stopped node") } if containsLifecycle(n.lifecycles, lifecycle) { panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) @@ -231,7 +231,7 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { // RegisterProtocols adds backend's protocols to the node's p2p server. func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { if n.runstate != initializingState { - panic(fmt.Sprintf("can't register protocols on running/stopped node")) + panic("can't register protocols on running/stopped node") } n.server.Protocols = append(n.server.Protocols, protocols...) } @@ -239,7 +239,7 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { // RegisterAPIs registers the APIs a service provides on the node. func (n *Node) RegisterAPIs(apis []rpc.API) { if n.runstate != initializingState { - panic(fmt.Sprintf("can't register APIs on running/stopped node")) + panic("can't register APIs on running/stopped node") } n.rpcAPIs = append(n.rpcAPIs, apis...) } @@ -252,7 +252,7 @@ func (n *Node) RegisterHTTPServer(endpoint string, server *httpServer) { // RegisterPath mounts the given handler on the given path on the canonical HTTP server. func (n *Node) RegisterPath(path string, handler http.Handler) string { if n.runstate != initializingState { - panic(fmt.Sprintf("can't register HTTP handler on running/stopped node")) + panic("can't register HTTP handler on running/stopped node") } for _, server := range n.httpServers { if atomic.LoadInt32(&server.RPCAllowed) == 1 { From 0ea3bb4f12d0ca6f02dc87d70959d14c5a010870 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:31:06 +0200 Subject: [PATCH 110/160] revert accidental q --- node/node_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/node_test.go b/node/node_test.go index 38222d91ae93..f18df593f91e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -405,7 +405,7 @@ func TestRegisterHTTPServer(t *testing.T) { } // Tests whether a handler can be successfully mounted on the canonical HTTP server -// on the givenq path +// on the given path func TestRegisterPath_Successful(t *testing.T) { node := createNode(t, 7878, 7979) From f3f11786be865edb71987def3eb1acfaa1a2bde8 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 21 Jul 2020 18:26:13 +0200 Subject: [PATCH 111/160] fixed ethstats backend interface --- ethstats/ethstats.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index a32ae71d491f..f100af4d118e 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -65,7 +65,7 @@ type backend interface { SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription CurrentHeader() *types.Header HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) - GetTd(blockHash common.Hash) *big.Int + GetTd(ctx context.Context, hash common.Hash) *big.Int Stats() (pending int, queued int) Downloader() *downloader.Downloader } @@ -558,7 +558,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { block = fullBackend.CurrentBlock() } header = block.Header() - td = fullBackend.GetTd(header.Hash()) + td = fullBackend.GetTd(context.Background(), header.Hash()) txs = make([]txStats, len(block.Transactions())) for i, tx := range block.Transactions() { @@ -572,7 +572,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { } else { header = s.backend.CurrentHeader() } - td = s.backend.GetTd(header.Hash()) + td = s.backend.GetTd(context.Background(), header.Hash()) txs = []txStats{} } From 3c0fb992ed3b7bd59d1272c0b6fc12db94a94562 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 16:53:30 +0200 Subject: [PATCH 112/160] node: whitespace changes --- node/node.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node/node.go b/node/node.go index 0047cbb6f46b..760c490a8ace 100644 --- a/node/node.go +++ b/node/node.go @@ -79,6 +79,7 @@ func New(conf *Config) (*Node, error) { } conf.DataDir = absdatadir } + // Ensure that the instance name doesn't cause weird conflicts with // other files in the data directory. if strings.ContainsAny(conf.Name, `/\`) { @@ -90,10 +91,10 @@ func New(conf *Config) (*Node, error) { if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } + if conf.Logger == nil { conf.Logger = log.New() } - node := &Node{ config: conf, httpServers: make(serverMap), @@ -104,14 +105,15 @@ func New(conf *Config) (*Node, error) { eventmux: new(event.TypeMux), log: conf.Logger, stop: make(chan struct{}), + server: &p2p.Server{Config: conf.P2P}, } // Acquire the instance directory lock. if err := node.openDataDir(); err != nil { return nil, err } - // Ensure that the AccountManager method works before the node has started. - // We rely on this in cmd/geth. + // Ensure that the AccountManager method works before the node has started. We rely on + // this in cmd/geth. am, ephemeralKeystore, err := makeAccountManager(conf) if err != nil { return nil, err @@ -119,9 +121,7 @@ func New(conf *Config) (*Node, error) { node.accman = am node.ephemeralKeystore = ephemeralKeystore - // Initialize the p2p server. This creates the node key and - // discovery databases. - node.server = &p2p.Server{Config: conf.P2P} + // Initialize the p2p server. This creates the node key and discovery databases. node.server.Config.PrivateKey = node.config.NodeKey() node.server.Config.Name = node.config.NodeName() node.server.Config.Logger = node.log @@ -135,7 +135,7 @@ func New(conf *Config) (*Node, error) { node.server.Config.NodeDatabase = node.config.NodeDB() } - // Configure HTTP server(s) + // Configure HTTP servers. if conf.HTTPHost != "" { httpServ := &httpServer{ CorsAllowedOrigins: conf.HTTPCors, @@ -148,7 +148,7 @@ func New(conf *Config) (*Node, error) { port: conf.HTTPPort, RPCAllowed: 1, } - // check if ws is enabled and if ws port is the same as http port + // Enable WebSocket on HTTP port if enabled. if conf.WSHost != "" && conf.WSPort == conf.HTTPPort { httpServ.WSAllowed = 1 httpServ.WsOrigins = conf.WSOrigins From 7801fad1e6463e035e3a8681c5ced4a57db1b3a1 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 16:53:47 +0200 Subject: [PATCH 113/160] p2p/simulations/adapters: use Node.Attach where possible --- p2p/simulations/adapters/inproc.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 36e0a0450ed8..fd10da431929 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -147,11 +147,7 @@ func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) { if !ok { return nil, fmt.Errorf("unknown node: %s", id) } - handler, err := node.node.RPCHandler() - if err != nil { - return nil, err - } - return rpc.DialInProc(handler), nil + return node.node.Attach() } // GetNode returns the node with the given ID if it exists @@ -279,13 +275,12 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { } // create an in-process RPC client - handler, err := sn.node.RPCHandler() + client, err := sn.node.Attach() if err != nil { return err } - sn.lock.Lock() - sn.client = rpc.DialInProc(handler) + sn.client = client sn.lock.Unlock() return nil From d4e2173c7c657f1080c392418697390d05677476 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 16:56:06 +0200 Subject: [PATCH 114/160] node: avoid setting server to nil --- node/node.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/node/node.go b/node/node.go index 760c490a8ace..f93f179af805 100644 --- a/node/node.go +++ b/node/node.go @@ -49,10 +49,10 @@ type Node struct { ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory stop chan struct{} // Channel to wait for termination notifications + server *p2p.Server // Currently running P2P networking layer lock sync.RWMutex runstate int - server *p2p.Server // Currently running P2P networking layer lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. inprocHandler *rpc.Server // In-process RPC request handler to process the API requests @@ -530,7 +530,6 @@ func (n *Node) stopServices() error { failure := new(StopError) failure.Services = stopLifecycles(n.lifecycles) n.server.Stop() - n.server = nil if len(failure.Services) > 0 { return failure @@ -560,12 +559,9 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { } // Server retrieves the currently running P2P network layer. This method is meant -// only to inspect fields of the currently running server, life cycle management -// should be left to this Node entity. +// only to inspect fields of the currently running server. Callers should not +// start or stop the returned server. func (n *Node) Server() *p2p.Server { - n.lock.RLock() - defer n.lock.RUnlock() - return n.server } From dab27f69b1dade758504be6d48851af9523b0fa8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 16:57:02 +0200 Subject: [PATCH 115/160] node: use simple lock --- node/node.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/node.go b/node/node.go index f93f179af805..419d9ad5bca7 100644 --- a/node/node.go +++ b/node/node.go @@ -51,7 +51,7 @@ type Node struct { stop chan struct{} // Channel to wait for termination notifications server *p2p.Server // Currently running P2P networking layer - lock sync.RWMutex + lock sync.Mutex runstate int lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. @@ -549,8 +549,8 @@ func (n *Node) Attach() (*rpc.Client, error) { // RPCHandler returns the in-process RPC request handler. func (n *Node) RPCHandler() (*rpc.Server, error) { - n.lock.RLock() - defer n.lock.RUnlock() + n.lock.Lock() + defer n.lock.Unlock() if n.runstate == stoppedState { return nil, ErrNodeStopped From b8d75870b1644fb52f6df8584a17f780f017095e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 17:02:24 +0200 Subject: [PATCH 116/160] node: more cosmetic fixes --- node/node.go | 53 ++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/node/node.go b/node/node.go index 419d9ad5bca7..a658bc4feb4d 100644 --- a/node/node.go +++ b/node/node.go @@ -42,14 +42,14 @@ import ( // Node is a container on which services can be registered. type Node struct { - eventmux *event.TypeMux // Event multiplexer used between the services of a stack - config *Config - accman *accounts.Manager - log log.Logger - ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop - instanceDirLock fileutil.Releaser // prevents concurrent use of instance directory - stop chan struct{} // Channel to wait for termination notifications - server *p2p.Server // Currently running P2P networking layer + eventmux *event.TypeMux // Event multiplexer used between the services of a stack + config *Config + accman *accounts.Manager + log log.Logger + ephemKeystore string // if non-empty, the key directory that will be removed by Stop + dirLock fileutil.Releaser // prevents concurrent use of instance directory + stop chan struct{} // Channel to wait for termination notifications + server *p2p.Server // Currently running P2P networking layer lock sync.Mutex runstate int @@ -96,11 +96,9 @@ func New(conf *Config) (*Node, error) { conf.Logger = log.New() } node := &Node{ - config: conf, - httpServers: make(serverMap), - ipc: &httpServer{ - endpoint: conf.IPCEndpoint(), - }, + config: conf, + httpServers: make(serverMap), + ipc: &httpServer{endpoint: conf.IPCEndpoint()}, inprocHandler: rpc.NewServer(), eventmux: new(event.TypeMux), log: conf.Logger, @@ -119,7 +117,7 @@ func New(conf *Config) (*Node, error) { return nil, err } node.accman = am - node.ephemeralKeystore = ephemeralKeystore + node.ephemKeystore = ephemeralKeystore // Initialize the p2p server. This creates the node key and discovery databases. node.server.Config.PrivateKey = node.config.NodeKey() @@ -196,8 +194,8 @@ func (n *Node) Close() error { if err := n.accman.Close(); err != nil { errs = append(errs, err) } - if n.ephemeralKeystore != "" { - if err := os.RemoveAll(n.ephemeralKeystore); err != nil { + if n.ephemKeystore != "" { + if err := os.RemoveAll(n.ephemKeystore); err != nil { errs = append(errs, err) } } @@ -385,17 +383,17 @@ func (n *Node) openDataDir() error { if err != nil { return convertFileLockError(err) } - n.instanceDirLock = release + n.dirLock = release return nil } func (n *Node) closeDataDir() { // Release instance directory lock. - if n.instanceDirLock != nil { - if err := n.instanceDirLock.Release(); err != nil { + if n.dirLock != nil { + if err := n.dirLock.Release(); err != nil { n.log.Error("Can't release datadir lock", "err", err) } - n.instanceDirLock = nil + n.dirLock = nil } } @@ -537,7 +535,7 @@ func (n *Node) stopServices() error { return nil } -// Wait blocks until the node is stopped. +// Wait blocks until the node is closed. func (n *Node) Wait() { <-n.stop } @@ -613,7 +611,8 @@ func (n *Node) EventMux() *event.TypeMux { // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { - // TODO: track databases so they can be closed later. + n.lock.Lock() + defer n.lock.Unlock() if n.runstate == stoppedState { return nil, ErrNodeStopped @@ -621,7 +620,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } - return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace) + return rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) } // OpenDatabaseWithFreezer opens an existing database with the given name (or @@ -630,19 +629,21 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { + n.lock.Lock() + defer n.lock.Unlock() + if n.runstate == stoppedState { return nil, ErrNodeStopped } if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } - root := n.config.ResolvePath(name) - + root := n.ResolvePath(name) switch { case freezer == "": freezer = filepath.Join(root, "ancient") case !filepath.IsAbs(freezer): - freezer = n.config.ResolvePath(freezer) + freezer = n.ResolvePath(freezer) } return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) } From b6c60cf25856e8b729a0a007bb2a196694040b8b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 17:35:56 +0200 Subject: [PATCH 117/160] node: track open databases --- node/node.go | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/node/node.go b/node/node.go index a658bc4feb4d..885a3106b280 100644 --- a/node/node.go +++ b/node/node.go @@ -42,7 +42,7 @@ import ( // Node is a container on which services can be registered. type Node struct { - eventmux *event.TypeMux // Event multiplexer used between the services of a stack + eventmux *event.TypeMux config *Config accman *accounts.Manager log log.Logger @@ -53,11 +53,12 @@ type Node struct { lock sync.Mutex runstate int - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *httpServer // Stores information about the ipc http server + lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + rpcAPIs []rpc.API // List of APIs currently provided by the node + ipc *httpServer // Stores information about the ipc http server + databases []ethdb.Database // all opened databases } const ( @@ -190,7 +191,6 @@ func (n *Node) Close() error { } // Release resources acquired by New(). - n.closeDataDir() if err := n.accman.Close(); err != nil { errs = append(errs, err) } @@ -199,6 +199,8 @@ func (n *Node) Close() error { errs = append(errs, err) } } + errs = append(errs, n.closeDatabases()...) + n.closeDataDir() n.runstate = stoppedState // Unblock n.Wait. @@ -620,7 +622,9 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( if n.config.DataDir == "" { return rawdb.NewMemoryDatabase(), nil } - return rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) + db, err := rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) + n.addDatabase(db) + return db, err } // OpenDatabaseWithFreezer opens an existing database with the given name (or @@ -645,7 +649,24 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, case !filepath.IsAbs(freezer): freezer = n.ResolvePath(freezer) } - return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) + db, err := rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) + n.addDatabase(db) + return db, err +} + +func (n *Node) addDatabase(db ethdb.Database) { + if db != nil { + n.databases = append(n.databases, db) + } +} + +func (n *Node) closeDatabases() (errors []error) { + for _, db := range n.databases { + if err := db.Close(); err != nil { + errors = append(errors, err) + } + } + return errors } // ResolvePath returns the absolute path of a resource in the instance directory. From 4ae4172d6591a56c25f3e4366206dfa740e5a997 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 21 Jul 2020 17:40:40 +0200 Subject: [PATCH 118/160] core/rawdb: make Freezer.Close idempotent --- core/rawdb/freezer.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index c5bb7cf1f443..621d35d3f420 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -22,6 +22,7 @@ import ( "math" "os" "path/filepath" + "sync" "sync/atomic" "time" @@ -74,6 +75,7 @@ type freezer struct { tables map[string]*freezerTable // Data tables for storing everything instanceLock fileutil.Releaser // File-system lock to prevent double opens quit chan struct{} + closeOnce sync.Once } // newFreezer creates a chain freezer that moves ancient chain data into @@ -128,16 +130,18 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { // Close terminates the chain freezer, unmapping all the data files. func (f *freezer) Close() error { - f.quit <- struct{}{} var errs []error - for _, table := range f.tables { - if err := table.Close(); err != nil { + f.closeOnce.Do(func() { + f.quit <- struct{}{} + for _, table := range f.tables { + if err := table.Close(); err != nil { + errs = append(errs, err) + } + } + if err := f.instanceLock.Release(); err != nil { errs = append(errs, err) } - } - if err := f.instanceLock.Release(); err != nil { - errs = append(errs, err) - } + }) if errs != nil { return fmt.Errorf("%v", errs) } From 8400a5da0bb9f2f7ba34b02bc6ea25edd3be3f3d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 00:01:35 +0200 Subject: [PATCH 119/160] node: avoid auto-closing DB if service closes it first --- node/node.go | 77 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/node/node.go b/node/node.go index 885a3106b280..ce73e348b907 100644 --- a/node/node.go +++ b/node/node.go @@ -53,12 +53,14 @@ type Node struct { lock sync.Mutex runstate int - lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests - rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *httpServer // Stores information about the ipc http server - databases []ethdb.Database // all opened databases + lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle + httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests + rpcAPIs []rpc.API // List of APIs currently provided by the node + ipc *httpServer // Stores information about the ipc http server + + dbLock sync.Mutex + databases map[*closeTrackingDB]struct{} // all opened databases } const ( @@ -105,6 +107,7 @@ func New(conf *Config) (*Node, error) { log: conf.Logger, stop: make(chan struct{}), server: &p2p.Server{Config: conf.P2P}, + databases: make(map[*closeTrackingDB]struct{}), } // Acquire the instance directory lock. @@ -623,7 +626,9 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( return rawdb.NewMemoryDatabase(), nil } db, err := rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) - n.addDatabase(db) + if err == nil { + db = n.wrapDatabase(db) + } return db, err } @@ -650,23 +655,10 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, freezer = n.ResolvePath(freezer) } db, err := rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) - n.addDatabase(db) - return db, err -} - -func (n *Node) addDatabase(db ethdb.Database) { - if db != nil { - n.databases = append(n.databases, db) - } -} - -func (n *Node) closeDatabases() (errors []error) { - for _, db := range n.databases { - if err := db.Close(); err != nil { - errors = append(errors, err) - } + if err == nil { + db = n.wrapDatabase(db) } - return errors + return db, err } // ResolvePath returns the absolute path of a resource in the instance directory. @@ -699,6 +691,45 @@ func (n *Node) apis() []rpc.API { } } +// closeTrackingDB wraps the Close method of a database. When the database is closed by the +// service, the wrapper removes it from the node's database map. This ensures that Node +// won't auto-close the database if it is closed by the service that opened it. +type closeTrackingDB struct { + ethdb.Database + n *Node +} + +func (db *closeTrackingDB) Close() error { + db.n.dbLock.Lock() + delete(db.n.databases, db) + db.n.dbLock.Unlock() + return db.Database.Close() +} + +// wrapDatabase ensures the database will be auto-closed when Node is closed. +func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { + n.dbLock.Lock() + defer n.dbLock.Unlock() + + wrapper := &closeTrackingDB{db, n} + n.databases[wrapper] = struct{}{} + return wrapper +} + +// closeDatabases closes all open databases. +func (n *Node) closeDatabases() (errors []error) { + n.dbLock.Lock() + defer n.dbLock.Unlock() + + for db := range n.databases { + delete(n.databases, db) + if err := db.Database.Close(); err != nil { + errors = append(errors, err) + } + } + return errors +} + // RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, // and then registers all of the APIs exposed by the services. func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { From fa88f302bfd8b71fd8b73190b4238d7ff580bd81 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 00:36:41 +0200 Subject: [PATCH 120/160] p2p/simulations/adapters: delete snapshotService --- p2p/simulations/adapters/exec.go | 39 +++++++------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index ccb0d82e7d3d..7ef908814d79 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -434,8 +434,8 @@ func startExecNodeStack() (*node.Node, error) { return nil, fmt.Errorf("error creating node stack: %v", err) } - // register the services, collecting them into a map so we can wrap - // them in a snapshot service + // Register the services, collecting them into a map so they can + // be accessed by the snapshot API. services := make(map[string]node.Lifecycle, len(serviceNames)) for _, name := range serviceNames { lifecycleFunc, exists := lifecycleConstructorFuncs[name] @@ -457,10 +457,13 @@ func startExecNodeStack() (*node.Node, error) { stack.RegisterLifecycle(service) } - // register the snapshot service - stack.RegisterLifecycle(&snapshotService{services}) + // Add the snapshot API. + stack.RegisterAPIs([]rpc.API{{ + Namespace: "simulation", + Version: "1.0", + Service: SnapshotAPI{services}, + }}) - // start the stack if err = stack.Start(); err != nil { err = fmt.Errorf("error starting stack: %v", err) } @@ -479,32 +482,6 @@ type nodeStartupJSON struct { NodeInfo *p2p.NodeInfo } -// snapshotService is a node.Service which wraps a list of services and -// exposes an API to generate a snapshot of those services -type snapshotService struct { - services map[string]node.Lifecycle -} - -func (s *snapshotService) APIs() []rpc.API { - return []rpc.API{{ - Namespace: "simulation", - Version: "1.0", - Service: SnapshotAPI{s.services}, - }} -} - -func (s *snapshotService) Protocols() []p2p.Protocol { - return nil -} - -func (s *snapshotService) Start() error { - return nil -} - -func (s *snapshotService) Stop() error { - return nil -} - // SnapshotAPI provides an RPC method to create snapshots of services type SnapshotAPI struct { services map[string]node.Lifecycle From 4da9ef7393bfb2c7a0e4463ffa822979cbc60742 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 00:38:43 +0200 Subject: [PATCH 121/160] p2p: delete Server.Running --- p2p/server.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index 58a41729861e..1fe5f3978923 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -1119,8 +1119,3 @@ func (srv *Server) PeersInfo() []*PeerInfo { } return infos } - -// Running returns whether the server is running. -func (srv *Server) Running() bool { - return srv.running -} From e1f4668e37f85da9925dd0624cc15c10a1460cef Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 10:11:49 +0200 Subject: [PATCH 122/160] cmd/geth, cmd/utils: gofmt -w -s --- cmd/geth/main.go | 2 +- cmd/utils/flags.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index da0b00c74464..ccc6358996b6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -36,8 +36,8 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" - "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6af946dffe8a..a2e5244dc4fd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -48,8 +48,8 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" - "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" From a30d4015ba1008de63b0b1a8b74ef692a8f21b4e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 12:00:01 +0200 Subject: [PATCH 123/160] p2p/simulations/examples: remove unused APIs callback --- p2p/simulations/examples/ping-pong.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index 13b6873bfea3..0cddd9b505f3 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rpc" ) var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`) @@ -49,7 +48,6 @@ func main() { "ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { pps := newPingPongService(ctx.Config.ID) stack.RegisterProtocols(pps.Protocols()) - stack.RegisterAPIs(pps.APIs()) return pps, nil }, } @@ -113,10 +111,6 @@ func (p *pingPongService) Protocols() []p2p.Protocol { }} } -func (p *pingPongService) APIs() []rpc.API { - return nil -} - func (p *pingPongService) Start() error { p.log.Info("ping-pong service starting") return nil From 5a3cf7a3ebcb1f53a616d2751f2edbd7d85ab80a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 12:08:47 +0200 Subject: [PATCH 124/160] p2p/testing: delete unused package pkg.go.dev lists zero imports from public Go code. --- p2p/testing/peerpool.go | 67 -------- p2p/testing/protocolsession.go | 283 -------------------------------- p2p/testing/protocoltester.go | 284 --------------------------------- 3 files changed, 634 deletions(-) delete mode 100644 p2p/testing/peerpool.go delete mode 100644 p2p/testing/protocolsession.go delete mode 100644 p2p/testing/protocoltester.go diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go deleted file mode 100644 index 91b9704c79f4..000000000000 --- a/p2p/testing/peerpool.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package testing - -import ( - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -type TestPeer interface { - ID() enode.ID - Drop() -} - -// TestPeerPool is an example peerPool to demonstrate registration of peer connections -type TestPeerPool struct { - lock sync.Mutex - peers map[enode.ID]TestPeer -} - -func NewTestPeerPool() *TestPeerPool { - return &TestPeerPool{peers: make(map[enode.ID]TestPeer)} -} - -func (p *TestPeerPool) Add(peer TestPeer) { - p.lock.Lock() - defer p.lock.Unlock() - log.Trace(fmt.Sprintf("pp add peer %v", peer.ID())) - p.peers[peer.ID()] = peer - -} - -func (p *TestPeerPool) Remove(peer TestPeer) { - p.lock.Lock() - defer p.lock.Unlock() - delete(p.peers, peer.ID()) -} - -func (p *TestPeerPool) Has(id enode.ID) bool { - p.lock.Lock() - defer p.lock.Unlock() - _, ok := p.peers[id] - return ok -} - -func (p *TestPeerPool) Get(id enode.ID) TestPeer { - p.lock.Lock() - defer p.lock.Unlock() - return p.peers[id] -} diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go deleted file mode 100644 index 58dac93c36c9..000000000000 --- a/p2p/testing/protocolsession.go +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package testing - -import ( - "errors" - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" -) - -var errTimedOut = errors.New("timed out") - -// ProtocolSession is a quasi simulation of a pivot node running -// a service and a number of dummy peers that can send (trigger) or -// receive (expect) messages -type ProtocolSession struct { - Server *p2p.Server - Nodes []*enode.Node - adapter *adapters.SimAdapter - events chan *p2p.PeerEvent -} - -// Exchange is the basic units of protocol tests -// the triggers and expects in the arrays are run immediately and asynchronously -// thus one cannot have multiple expects for the SAME peer with DIFFERENT message types -// because it's unpredictable which expect will receive which message -// (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code) -// an exchange is defined on a session -type Exchange struct { - Label string - Triggers []Trigger - Expects []Expect - Timeout time.Duration -} - -// Trigger is part of the exchange, incoming message for the pivot node -// sent by a peer -type Trigger struct { - Msg interface{} // type of message to be sent - Code uint64 // code of message is given - Peer enode.ID // the peer to send the message to - Timeout time.Duration // timeout duration for the sending -} - -// Expect is part of an exchange, outgoing message from the pivot node -// received by a peer -type Expect struct { - Msg interface{} // type of message to expect - Code uint64 // code of message is now given - Peer enode.ID // the peer that expects the message - Timeout time.Duration // timeout duration for receiving -} - -// Disconnect represents a disconnect event, used and checked by TestDisconnected -type Disconnect struct { - Peer enode.ID // discconnected peer - Error error // disconnect reason -} - -// trigger sends messages from peers -func (s *ProtocolSession) trigger(trig Trigger) error { - simNode, ok := s.adapter.GetNode(trig.Peer) - if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes)) - } - mockNode, ok := simNode.Services()[0].(*mockNode) - if !ok { - return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer) - } - - errc := make(chan error) - - go func() { - log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code)) - errc <- mockNode.Trigger(&trig) - log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code)) - }() - - t := trig.Timeout - if t == time.Duration(0) { - t = 1000 * time.Millisecond - } - select { - case err := <-errc: - return err - case <-time.After(t): - return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer) - } -} - -// expect checks an expectation of a message sent out by the pivot node -func (s *ProtocolSession) expect(exps []Expect) error { - // construct a map of expectations for each node - peerExpects := make(map[enode.ID][]Expect) - for _, exp := range exps { - if exp.Msg == nil { - return errors.New("no message to expect") - } - peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp) - } - - // construct a map of mockNodes for each node - mockNodes := make(map[enode.ID]*mockNode) - for nodeID := range peerExpects { - simNode, ok := s.adapter.GetNode(nodeID) - if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes)) - } - mockNode, ok := simNode.Services()[0].(*mockNode) - if !ok { - return fmt.Errorf("trigger: peer %v is not a mock", nodeID) - } - mockNodes[nodeID] = mockNode - } - - // done chanell cancels all created goroutines when function returns - done := make(chan struct{}) - defer close(done) - // errc catches the first error from - errc := make(chan error) - - wg := &sync.WaitGroup{} - wg.Add(len(mockNodes)) - for nodeID, mockNode := range mockNodes { - nodeID := nodeID - mockNode := mockNode - go func() { - defer wg.Done() - - // Sum all Expect timeouts to give the maximum - // time for all expectations to finish. - // mockNode.Expect checks all received messages against - // a list of expected messages and timeout for each - // of them can not be checked separately. - var t time.Duration - for _, exp := range peerExpects[nodeID] { - if exp.Timeout == time.Duration(0) { - t += 2000 * time.Millisecond - } else { - t += exp.Timeout - } - } - alarm := time.NewTimer(t) - defer alarm.Stop() - - // expectErrc is used to check if error returned - // from mockNode.Expect is not nil and to send it to - // errc only in that case. - // done channel will be closed when function - expectErrc := make(chan error) - go func() { - select { - case expectErrc <- mockNode.Expect(peerExpects[nodeID]...): - case <-done: - case <-alarm.C: - } - }() - - select { - case err := <-expectErrc: - if err != nil { - select { - case errc <- err: - case <-done: - case <-alarm.C: - errc <- errTimedOut - } - } - case <-done: - case <-alarm.C: - errc <- errTimedOut - } - - }() - } - - go func() { - wg.Wait() - // close errc when all goroutines finish to return nill err from errc - close(errc) - }() - - return <-errc -} - -// TestExchanges tests a series of exchanges against the session -func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error { - for i, e := range exchanges { - if err := s.testExchange(e); err != nil { - return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err) - } - log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label)) - } - return nil -} - -// testExchange tests a single Exchange. -// Default timeout value is 2 seconds. -func (s *ProtocolSession) testExchange(e Exchange) error { - errc := make(chan error) - done := make(chan struct{}) - defer close(done) - - go func() { - for _, trig := range e.Triggers { - err := s.trigger(trig) - if err != nil { - errc <- err - return - } - } - - select { - case errc <- s.expect(e.Expects): - case <-done: - } - }() - - // time out globally or finish when all expectations satisfied - t := e.Timeout - if t == 0 { - t = 2000 * time.Millisecond - } - alarm := time.NewTimer(t) - defer alarm.Stop() - select { - case err := <-errc: - return err - case <-alarm.C: - return errTimedOut - } -} - -// TestDisconnected tests the disconnections given as arguments -// the disconnect structs describe what disconnect error is expected on which peer -func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { - expects := make(map[enode.ID]error) - for _, disconnect := range disconnects { - expects[disconnect.Peer] = disconnect.Error - } - - timeout := time.After(time.Second) - for len(expects) > 0 { - select { - case event := <-s.events: - if event.Type != p2p.PeerEventTypeDrop { - continue - } - expectErr, ok := expects[event.Peer] - if !ok { - continue - } - - if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) { - return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error) - } - delete(expects, event.Peer) - case <-timeout: - return fmt.Errorf("timed out waiting for peers to disconnect") - } - } - return nil -} diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go deleted file mode 100644 index 9dd53e71226a..000000000000 --- a/p2p/testing/protocoltester.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -the p2p/testing package provides a unit test scheme to check simple -protocol message exchanges with one pivot node and a number of dummy peers -The pivot test node runs a node.Service, the dummy peers run a mock node -that can be used to send and receive messages -*/ - -package testing - -import ( - "bytes" - "crypto/ecdsa" - "fmt" - "io" - "io/ioutil" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" -) - -// ProtocolTester is the tester environment used for unit testing protocol -// message exchanges. It uses p2p/simulations framework -type ProtocolTester struct { - *ProtocolSession - network *simulations.Network -} - -// NewProtocolTester constructs a new ProtocolTester -// it takes as argument the pivot node id, the number of dummy peers and the -// protocol run function called on a peer connection by the p2p server -func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester { - services := adapters.LifecycleConstructors{ - "test": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - return &testNode{run}, nil - }, - "mock": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - return newMockNode(), nil - }, - } - adapter := adapters.NewSimAdapter(services) - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{}) - nodeConfig := &adapters.NodeConfig{ - PrivateKey: prvkey, - EnableMsgEvents: true, - Lifecycles: []string{"test"}, - } - if _, err := net.NewNodeWithConfig(nodeConfig); err != nil { - panic(err.Error()) - } - if err := net.Start(nodeConfig.ID); err != nil { - panic(err.Error()) - } - - node := net.GetNode(nodeConfig.ID).Node.(*adapters.SimNode) - peers := make([]*adapters.NodeConfig, nodeCount) - nodes := make([]*enode.Node, nodeCount) - for i := 0; i < nodeCount; i++ { - peers[i] = adapters.RandomNodeConfig() - peers[i].Lifecycles = []string{"mock"} - if _, err := net.NewNodeWithConfig(peers[i]); err != nil { - panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err)) - } - if err := net.Start(peers[i].ID); err != nil { - panic(fmt.Sprintf("error starting peer %v: %v", peers[i].ID, err)) - } - nodes[i] = peers[i].Node() - } - events := make(chan *p2p.PeerEvent, 1000) - node.SubscribeEvents(events) - ps := &ProtocolSession{ - Server: node.Server(), - Nodes: nodes, - adapter: adapter, - events: events, - } - self := &ProtocolTester{ - ProtocolSession: ps, - network: net, - } - - self.Connect(nodeConfig.ID, peers...) - - return self -} - -// Stop stops the p2p server -func (t *ProtocolTester) Stop() { - t.Server.Stop() - t.network.Shutdown() -} - -// Connect brings up the remote peer node and connects it using the -// p2p/simulations network connection with the in memory network adapter -func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) { - for _, peer := range peers { - log.Trace(fmt.Sprintf("connect to %v", peer.ID)) - if err := t.network.Connect(selfID, peer.ID); err != nil { - panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err)) - } - } - -} - -// testNode wraps a protocol run function and implements the node.Service -// interface -type testNode struct { - run func(*p2p.Peer, p2p.MsgReadWriter) error -} - -func (t *testNode) Protocols() []p2p.Protocol { - return []p2p.Protocol{{ - Length: 100, - Run: t.run, - }} -} - -func (t *testNode) APIs() []rpc.API { - return nil -} - -func (t *testNode) Start() error { - return nil -} - -func (t *testNode) Stop() error { - return nil -} - -// mockNode is a testNode which doesn't actually run a protocol, instead -// exposing channels so that tests can manually trigger and expect certain -// messages -type mockNode struct { - testNode - - trigger chan *Trigger - expect chan []Expect - err chan error - stop chan struct{} - stopOnce sync.Once -} - -func newMockNode() *mockNode { - mock := &mockNode{ - trigger: make(chan *Trigger), - expect: make(chan []Expect), - err: make(chan error), - stop: make(chan struct{}), - } - mock.testNode.run = mock.Run - return mock -} - -// Run is a protocol run function which just loops waiting for tests to -// instruct it to either trigger or expect a message from the peer -func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - for { - select { - case trig := <-m.trigger: - wmsg := Wrap(trig.Msg) - m.err <- p2p.Send(rw, trig.Code, wmsg) - case exps := <-m.expect: - m.err <- expectMsgs(rw, exps) - case <-m.stop: - return nil - } - } -} - -func (m *mockNode) Trigger(trig *Trigger) error { - m.trigger <- trig - return <-m.err -} - -func (m *mockNode) Expect(exp ...Expect) error { - m.expect <- exp - return <-m.err -} - -func (m *mockNode) Stop() error { - m.stopOnce.Do(func() { close(m.stop) }) - return nil -} - -func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error { - matched := make([]bool, len(exps)) - for { - msg, err := rw.ReadMsg() - if err != nil { - if err == io.EOF { - break - } - return err - } - actualContent, err := ioutil.ReadAll(msg.Payload) - if err != nil { - return err - } - var found bool - for i, exp := range exps { - if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(Wrap(exp.Msg))) { - if matched[i] { - return fmt.Errorf("message #%d received two times", i) - } - matched[i] = true - found = true - break - } - } - if !found { - expected := make([]string, 0) - for i, exp := range exps { - if matched[i] { - continue - } - expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(Wrap(exp.Msg)))) - } - return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or ")) - } - done := true - for _, m := range matched { - if !m { - done = false - break - } - } - if done { - return nil - } - } - for i, m := range matched { - if !m { - return fmt.Errorf("expected message #%d not received", i) - } - } - return nil -} - -// mustEncodeMsg uses rlp to encode a message. -// In case of error it panics. -func mustEncodeMsg(msg interface{}) []byte { - contentEnc, err := rlp.EncodeToBytes(msg) - if err != nil { - panic("content encode error: " + err.Error()) - } - return contentEnc -} - -type WrappedMsg struct { - Context []byte - Size uint32 - Payload []byte -} - -func Wrap(msg interface{}) interface{} { - data, _ := rlp.EncodeToBytes(msg) - return &WrappedMsg{ - Size: uint32(len(data)), - Payload: data, - } -} From e6cd0e3e696a1e1c70bbec853128e96874e35a01 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 12:16:05 +0200 Subject: [PATCH 125/160] node: unexport RegisterHTTPServer --- node/api.go | 4 ++-- node/node.go | 10 +++++----- node/node_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/node/api.go b/node/api.go index 810697fafb35..0c81bf4827b9 100644 --- a/node/api.go +++ b/node/api.go @@ -208,7 +208,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis return false, err } // register the HTTP server - api.node.RegisterHTTPServer(endpoint, httpServer) + api.node.registerHTTPServer(endpoint, httpServer) // start the HTTP server httpServer.Start() api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.Listener.Addr()), @@ -297,7 +297,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str return false, err } // register the HTTP server - api.node.RegisterHTTPServer(endpoint, wsServer) + api.node.registerHTTPServer(endpoint, wsServer) // start the HTTP server if err := wsServer.Start(); err != nil { return false, err diff --git a/node/node.go b/node/node.go index ce73e348b907..93b949acaa5b 100644 --- a/node/node.go +++ b/node/node.go @@ -247,11 +247,6 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { n.rpcAPIs = append(n.rpcAPIs, apis...) } -// RegisterHTTPServer registers the given HTTP server on the node. -func (n *Node) RegisterHTTPServer(endpoint string, server *httpServer) { - n.httpServers[endpoint] = server -} - // RegisterPath mounts the given handler on the given path on the canonical HTTP server. func (n *Node) RegisterPath(path string, handler http.Handler) string { if n.runstate != initializingState { @@ -267,6 +262,11 @@ func (n *Node) RegisterPath(path string, handler http.Handler) string { return "" } +// registerHTTPServer registers the given HTTP server on the node. +func (n *Node) registerHTTPServer(endpoint string, server *httpServer) { + n.httpServers[endpoint] = server +} + // existingHTTPServer checks if an HTTP server is already configured on the given endpoint. func (n *Node) existingHTTPServer(endpoint string) *httpServer { if server, exists := n.httpServers[endpoint]; exists { diff --git a/node/node_test.go b/node/node_test.go index f18df593f91e..a360c5cdfd07 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -378,14 +378,14 @@ func TestRegisterHTTPServer(t *testing.T) { port: 0001, } endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) - stack.RegisterHTTPServer(endpoint1, srv1) + stack.registerHTTPServer(endpoint1, srv1) srv2 := &httpServer{ host: "test2", port: 0002, } endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) - stack.RegisterHTTPServer(endpoint2, srv2) + stack.registerHTTPServer(endpoint2, srv2) noop := &httpServer{ host: "test", From aba06679a4b88a5cda4a7580bd7e0ba876cbbd99 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 12:21:26 +0200 Subject: [PATCH 126/160] node: clean up instrumented service tests --- node/node_test.go | 72 +++++++++++++++++----------------------------- node/utils_test.go | 8 ------ 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index a360c5cdfd07..4d04fd7ceaff 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -182,23 +182,17 @@ func TestLifecycleLifeCycle(t *testing.T) { // Create a batch of instrumented services lifecycles := map[string]Lifecycle{ - "A": &InstrumentedServiceA{ - InstrumentedService{ - startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, - }, + "A": &InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, - "B": &InstrumentedServiceB{ - InstrumentedService{ - startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, - }, + "B": &InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, - "C": &InstrumentedServiceC{ - InstrumentedService{ - startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, - }, + "C": &InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, } // register lifecycles on node @@ -242,23 +236,17 @@ func TestLifecycleStartupError(t *testing.T) { // Create a batch of instrumented services lifecycles := map[string]Lifecycle{ - "A": &InstrumentedServiceA{ - InstrumentedService{ - startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, - }, + "A": &InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, - "B": &InstrumentedServiceB{ - InstrumentedService{ - startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, - }, + "B": &InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, - "C": &InstrumentedServiceC{ - InstrumentedService{ - startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, - }, + "C": &InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, } // register lifecycles on node @@ -298,23 +286,17 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { // Create a batch of instrumented services lifecycles := map[string]Lifecycle{ - "A": &InstrumentedServiceA{ - InstrumentedService{ - startHook: func() { started["A"] = true }, - stopHook: func() { stopped["A"] = true }, - }, + "A": &InstrumentedService{ + startHook: func() { started["A"] = true }, + stopHook: func() { stopped["A"] = true }, }, - "B": &InstrumentedServiceB{ - InstrumentedService{ - startHook: func() { started["B"] = true }, - stopHook: func() { stopped["B"] = true }, - }, + "B": &InstrumentedService{ + startHook: func() { started["B"] = true }, + stopHook: func() { stopped["B"] = true }, }, - "C": &InstrumentedServiceC{ - InstrumentedService{ - startHook: func() { started["C"] = true }, - stopHook: func() { stopped["C"] = true }, - }, + "C": &InstrumentedService{ + startHook: func() { started["C"] = true }, + stopHook: func() { stopped["C"] = true }, }, } // register lifecycles on node diff --git a/node/utils_test.go b/node/utils_test.go index 481edd6d4822..44c83e22da75 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -51,14 +51,6 @@ type InstrumentedService struct { protocols []p2p.Protocol } -type InstrumentedServiceA struct{ InstrumentedService } -type InstrumentedServiceB struct{ InstrumentedService } -type InstrumentedServiceC struct{ InstrumentedService } - -func NewInstrumentedService() (*InstrumentedService, error) { - return new(InstrumentedService), nil -} - func (s *InstrumentedService) Start() error { if s.startHook != nil { s.startHook() From a23b6506640ab5c8cc0098849a177673272c9df2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 12:36:03 +0200 Subject: [PATCH 127/160] node: add test for database auto-closing --- node/node.go | 32 +++++++++++++++++++----------- node/node_test.go | 50 +++++++++++++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/node/node.go b/node/node.go index 93b949acaa5b..a53eadc4fd05 100644 --- a/node/node.go +++ b/node/node.go @@ -622,10 +622,15 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( if n.runstate == stoppedState { return nil, ErrNodeStopped } + + var db ethdb.Database + var err error if n.config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil + db = rawdb.NewMemoryDatabase() + } else { + db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) } - db, err := rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) + if err == nil { db = n.wrapDatabase(db) } @@ -644,17 +649,22 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, if n.runstate == stoppedState { return nil, ErrNodeStopped } + + var db ethdb.Database + var err error if n.config.DataDir == "" { - return rawdb.NewMemoryDatabase(), nil - } - root := n.ResolvePath(name) - switch { - case freezer == "": - freezer = filepath.Join(root, "ancient") - case !filepath.IsAbs(freezer): - freezer = n.ResolvePath(freezer) + db = rawdb.NewMemoryDatabase() + } else { + root := n.ResolvePath(name) + switch { + case freezer == "": + freezer = filepath.Join(root, "ancient") + case !filepath.IsAbs(freezer): + freezer = n.ResolvePath(freezer) + } + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) } - db, err := rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) + if err == nil { db = n.wrapDatabase(db) } diff --git a/node/node_test.go b/node/node_test.go index 4d04fd7ceaff..9c421be9435a 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -151,30 +151,28 @@ func TestRegisterProtocols(t *testing.T) { } } -func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool { - for _, a := range stackProtocols { - if reflect.DeepEqual(a, protocol) { - return true - } +// This test checks that open databases are closed with node. +func TestCloseNodeClosesDB(t *testing.T) { + stack, _ := New(testNodeConfig()) + defer stack.Close() + + db, err := stack.OpenDatabase("mydb", 0, 0, "") + if err != nil { + t.Fatal("can't open DB:", err) + } + if err = db.Put([]byte{}, []byte{}); err != nil { + t.Fatal("can't put on open DB:", err) } - return false -} -func containsAPI(stackAPIs []rpc.API, api rpc.API) bool { - for _, a := range stackAPIs { - if reflect.DeepEqual(a, api) { - return true - } + stack.Close() + if err = db.Put([]byte{}, []byte{}); err == nil { + t.Fatal("put succeeded after node is closed") } - return false } // Tests that registered Lifecycles get started and stopped correctly. func TestLifecycleLifeCycle(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } + stack, _ := New(testNodeConfig()) defer stack.Close() started := make(map[string]bool) @@ -547,3 +545,21 @@ func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { } return resp } + +func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool { + for _, a := range stackProtocols { + if reflect.DeepEqual(a, protocol) { + return true + } + } + return false +} + +func containsAPI(stackAPIs []rpc.API, api rpc.API) bool { + for _, a := range stackAPIs { + if reflect.DeepEqual(a, api) { + return true + } + } + return false +} From 0a5312afb1dbec3df94bfe1af4bfd9b73adccb61 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 18:18:15 +0200 Subject: [PATCH 128/160] node: WIP TestNodeOpenDatabaseFromLifecycle This is a lot more complicated than I thought... --- node/node.go | 108 ++++++++++++++++++++++++++++++++-------------- node/node_test.go | 30 +++++++++++-- 2 files changed, 102 insertions(+), 36 deletions(-) diff --git a/node/node.go b/node/node.go index a53eadc4fd05..a490698079bb 100644 --- a/node/node.go +++ b/node/node.go @@ -65,8 +65,10 @@ type Node struct { const ( initializingState = iota + startingState + closedWhileStartingState runningState - stoppedState + closedState ) // New creates a new P2P node, ready for protocol registration. @@ -180,20 +182,45 @@ func New(conf *Config) (*Node, error) { // Node constructor New. func (n *Node) Close() error { n.lock.Lock() - defer n.lock.Unlock() - var errs []error switch n.runstate { - case stoppedState: - return ErrNodeStopped + case initializingState: + // The node was never started. + defer n.lock.Unlock() + return n.doClose(nil) + case startingState: + // The node is currently starting lifecycles. Signal that + // Close was called through runstate to make Start stop + // everything when it re-acquires the lock. + n.runstate = closedWhileStartingState + n.lock.Unlock() + n.Wait() + return nil + case closedWhileStartingState: + // This branch handles double-close during startup. + n.lock.Unlock() + n.Wait() + return nil case runningState: // The node was started, release resources acquired by Start(). + defer n.lock.Unlock() + var errs []error if err := n.stopServices(); err != nil { errs = append(errs, err) } + return n.doClose(errs) + case closedState: + // Shutdown is already over. + n.lock.Unlock() + return ErrNodeStopped + default: + defer n.lock.Unlock() + panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) } +} - // Release resources acquired by New(). +// doClose releases resources acquired by New() and collects shutdown errors. +func (n *Node) doClose(errs []error) error { if err := n.accman.Close(); err != nil { errs = append(errs, err) } @@ -204,7 +231,7 @@ func (n *Node) Close() error { } errs = append(errs, n.closeDatabases()...) n.closeDataDir() - n.runstate = stoppedState + n.runstate = closedState // Unblock n.Wait. close(n.stop) @@ -305,46 +332,62 @@ func (n *Node) createHTTPServer(h *httpServer, exposeAll bool) error { } // Start starts all registered lifecycles, RPC services and p2p networking. +// Node can only be started once. func (n *Node) Start() error { n.lock.Lock() defer n.lock.Unlock() - // Node can only be started when it switch n.runstate { - case runningState: + case runningState, startingState: return ErrNodeRunning - case stoppedState: + case closedState, closedWhileStartingState: return ErrNodeStopped } - - // Start the p2p node - if err := n.server.Start(); err != nil { - return convertFileLockError(err) + if n.runstate != initializingState { + panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) } - n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) - - // Configure the RPC interfaces - if err := n.configureRPC(); err != nil { - n.httpServers.Stop() - n.server.Stop() - n.runstate = stoppedState + if err := n.startNetworking(); err != nil { + n.runstate = closedState return err } + n.runstate = startingState - // Start all registered lifecycles + // Start all registered lifecycles. The node lock mustn't + // be held here because the Start methods of lifecycle objects + // might want to access resources on the node. + n.lock.Unlock() var started []Lifecycle + var startErr error for _, lifecycle := range n.lifecycles { - if err := lifecycle.Start(); err != nil { - stopLifecycles(started) - n.server.Stop() - n.runstate = stoppedState - return err + if startErr = lifecycle.Start(); startErr != nil { + break } started = append(started, lifecycle) } + n.lock.Lock() - n.runstate = runningState - return nil + // Check if any lifecycle failed to start or the node was closed while starting. + if startErr != nil || n.runstate == closedWhileStartingState { + stopLifecycles(started) + n.doClose(nil) + } else { + n.runstate = runningState + } + return startErr +} + +// startNetworking starts all network endpoints. +func (n *Node) startNetworking() error { + n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) + if err := n.server.Start(); err != nil { + return convertFileLockError(err) + } + err := n.configureRPC() + if err != nil { + n.httpServers.Stop() + n.server.Stop() + } + return err } // containsLifecycle checks if 'lfs' contains 'l'. @@ -495,7 +538,6 @@ func (n *Node) stopIPC() { if n.ipc.Listener != nil { n.ipc.Listener.Close() n.ipc.Listener = nil - n.log.Info("IPC endpoint closed", "url", n.ipc.endpoint) } if n.ipc.Srv != nil { @@ -555,7 +597,7 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { n.lock.Lock() defer n.lock.Unlock() - if n.runstate == stoppedState { + if n.runstate == closedState { return nil, ErrNodeStopped } return n.inprocHandler, nil @@ -619,7 +661,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( n.lock.Lock() defer n.lock.Unlock() - if n.runstate == stoppedState { + if n.runstate == closedState { return nil, ErrNodeStopped } @@ -646,7 +688,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, n.lock.Lock() defer n.lock.Unlock() - if n.runstate == stoppedState { + if n.runstate == closedState { return nil, ErrNodeStopped } diff --git a/node/node_test.go b/node/node_test.go index 9c421be9435a..e57952105d32 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -28,6 +28,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" @@ -152,7 +153,7 @@ func TestRegisterProtocols(t *testing.T) { } // This test checks that open databases are closed with node. -func TestCloseNodeClosesDB(t *testing.T) { +func TestNodeCloseClosesDB(t *testing.T) { stack, _ := New(testNodeConfig()) defer stack.Close() @@ -161,15 +162,38 @@ func TestCloseNodeClosesDB(t *testing.T) { t.Fatal("can't open DB:", err) } if err = db.Put([]byte{}, []byte{}); err != nil { - t.Fatal("can't put on open DB:", err) + t.Fatal("can't Put on open DB:", err) } stack.Close() if err = db.Put([]byte{}, []byte{}); err == nil { - t.Fatal("put succeeded after node is closed") + t.Fatal("Put succeeded after node is closed") } } +// This test checks that OpenDatabase can be used from within a Lifecycle Start method. +func TestNodeOpenDatabaseFromLifecycle(t *testing.T) { + stack, _ := New(testNodeConfig()) + defer stack.Close() + + var db ethdb.Database + var err error + stack.RegisterLifecycle(&InstrumentedService{ + startHook: func() { + db, err = stack.OpenDatabase("mydb", 0, 0, "") + if err != nil { + t.Fatal("can't open DB:", err) + } + }, + stopHook: func() { + db.Close() + }, + }) + + stack.Start() + stack.Close() +} + // Tests that registered Lifecycles get started and stopped correctly. func TestLifecycleLifeCycle(t *testing.T) { stack, _ := New(testNodeConfig()) From 39dbf650ca1661aa9ae6a873ec0888a052e30df1 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 18:29:21 +0200 Subject: [PATCH 129/160] node: move Start up --- node/node.go | 90 ++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/node/node.go b/node/node.go index a490698079bb..222e68ffd32d 100644 --- a/node/node.go +++ b/node/node.go @@ -178,6 +178,51 @@ func New(conf *Config) (*Node, error) { return node, nil } +// Start starts all registered lifecycles, RPC services and p2p networking. +// Node can only be started once. +func (n *Node) Start() error { + n.lock.Lock() + defer n.lock.Unlock() + + switch n.runstate { + case runningState, startingState: + return ErrNodeRunning + case closedState, closedWhileStartingState: + return ErrNodeStopped + } + if n.runstate != initializingState { + panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) + } + if err := n.startNetworking(); err != nil { + n.doClose(nil) + return err + } + n.runstate = startingState + + // Start all registered lifecycles. The node lock mustn't + // be held here because the Start methods of lifecycle objects + // might want to access resources on the node. + n.lock.Unlock() + var started []Lifecycle + var startErr error + for _, lifecycle := range n.lifecycles { + if startErr = lifecycle.Start(); startErr != nil { + break + } + started = append(started, lifecycle) + } + n.lock.Lock() + + // Check if any lifecycle failed to start or the node was closed while starting. + if startErr != nil || n.runstate == closedWhileStartingState { + stopLifecycles(started) + n.doClose(nil) + } else { + n.runstate = runningState + } + return startErr +} + // Close stops the Node and releases resources acquired in // Node constructor New. func (n *Node) Close() error { @@ -331,51 +376,6 @@ func (n *Node) createHTTPServer(h *httpServer, exposeAll bool) error { return nil } -// Start starts all registered lifecycles, RPC services and p2p networking. -// Node can only be started once. -func (n *Node) Start() error { - n.lock.Lock() - defer n.lock.Unlock() - - switch n.runstate { - case runningState, startingState: - return ErrNodeRunning - case closedState, closedWhileStartingState: - return ErrNodeStopped - } - if n.runstate != initializingState { - panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) - } - if err := n.startNetworking(); err != nil { - n.runstate = closedState - return err - } - n.runstate = startingState - - // Start all registered lifecycles. The node lock mustn't - // be held here because the Start methods of lifecycle objects - // might want to access resources on the node. - n.lock.Unlock() - var started []Lifecycle - var startErr error - for _, lifecycle := range n.lifecycles { - if startErr = lifecycle.Start(); startErr != nil { - break - } - started = append(started, lifecycle) - } - n.lock.Lock() - - // Check if any lifecycle failed to start or the node was closed while starting. - if startErr != nil || n.runstate == closedWhileStartingState { - stopLifecycles(started) - n.doClose(nil) - } else { - n.runstate = runningState - } - return startErr -} - // startNetworking starts all network endpoints. func (n *Node) startNetworking() error { n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) From 6a16cbef94367a45889a94cadaf34857700c02ec Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:09:40 +0200 Subject: [PATCH 130/160] node: simplify Start/Stop concurrency handling --- node/node.go | 285 ++++++++++++++++++++++------------------------ node/node_test.go | 21 +++- 2 files changed, 157 insertions(+), 149 deletions(-) diff --git a/node/node.go b/node/node.go index 222e68ffd32d..62c4f62674ff 100644 --- a/node/node.go +++ b/node/node.go @@ -50,27 +50,33 @@ type Node struct { dirLock fileutil.Releaser // prevents concurrent use of instance directory stop chan struct{} // Channel to wait for termination notifications server *p2p.Server // Currently running P2P networking layer + startStopLock sync.Mutex // Start/Stop are protected by an additional lock + stateFlag int32 // Tracks state of node lifecycle lock sync.Mutex - runstate int lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. inprocHandler *rpc.Server // In-process RPC request handler to process the API requests rpcAPIs []rpc.API // List of APIs currently provided by the node ipc *httpServer // Stores information about the ipc http server - dbLock sync.Mutex - databases map[*closeTrackingDB]struct{} // all opened databases + databases map[*closeTrackingDB]struct{} // All open databases } const ( initializingState = iota - startingState - closedWhileStartingState runningState closedState ) +func (n *Node) state() int32 { + return atomic.LoadInt32(&n.stateFlag) +} + +func (n *Node) setState(state int32) { + atomic.StoreInt32(&n.stateFlag, state) +} + // New creates a new P2P node, ready for protocol registration. func New(conf *Config) (*Node, error) { // Copy config and resolve the datadir so future changes to the current @@ -84,6 +90,9 @@ func New(conf *Config) (*Node, error) { } conf.DataDir = absdatadir } + if conf.Logger == nil { + conf.Logger = log.New() + } // Ensure that the instance name doesn't cause weird conflicts with // other files in the data directory. @@ -97,9 +106,6 @@ func New(conf *Config) (*Node, error) { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } - if conf.Logger == nil { - conf.Logger = log.New() - } node := &Node{ config: conf, httpServers: make(serverMap), @@ -112,6 +118,9 @@ func New(conf *Config) (*Node, error) { databases: make(map[*closeTrackingDB]struct{}), } + // Register built-in APIs. + node.rpcAPIs = append(node.rpcAPIs, node.apis()...) + // Acquire the instance directory lock. if err := node.openDataDir(); err != nil { return nil, err @@ -181,91 +190,72 @@ func New(conf *Config) (*Node, error) { // Start starts all registered lifecycles, RPC services and p2p networking. // Node can only be started once. func (n *Node) Start() error { - n.lock.Lock() - defer n.lock.Unlock() + n.startStopLock.Lock() + defer n.startStopLock.Unlock() - switch n.runstate { - case runningState, startingState: + switch n.state() { + case runningState: return ErrNodeRunning - case closedState, closedWhileStartingState: + case closedState: return ErrNodeStopped } - if n.runstate != initializingState { - panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) - } - if err := n.startNetworking(); err != nil { + n.setState(runningState) + + n.lock.Lock() + err := n.startNetworking() + lifecycles := make([]Lifecycle, len(n.lifecycles)) + copy(lifecycles, n.lifecycles) + n.lock.Unlock() + + // Check if networking startup failed. + if err != nil { n.doClose(nil) return err } - n.runstate = startingState - - // Start all registered lifecycles. The node lock mustn't - // be held here because the Start methods of lifecycle objects - // might want to access resources on the node. - n.lock.Unlock() + // Start all registered lifecycles. var started []Lifecycle - var startErr error - for _, lifecycle := range n.lifecycles { - if startErr = lifecycle.Start(); startErr != nil { + for _, lifecycle := range lifecycles { + if err = lifecycle.Start(); err != nil { break } started = append(started, lifecycle) } - n.lock.Lock() - - // Check if any lifecycle failed to start or the node was closed while starting. - if startErr != nil || n.runstate == closedWhileStartingState { - stopLifecycles(started) + // Check if any lifecycle failed to start. + if err != nil { + n.stopServices(started) n.doClose(nil) - } else { - n.runstate = runningState } - return startErr + return err } // Close stops the Node and releases resources acquired in // Node constructor New. func (n *Node) Close() error { - n.lock.Lock() + n.startStopLock.Lock() + defer n.startStopLock.Unlock() - switch n.runstate { + switch state := n.state(); state { case initializingState: // The node was never started. - defer n.lock.Unlock() return n.doClose(nil) - case startingState: - // The node is currently starting lifecycles. Signal that - // Close was called through runstate to make Start stop - // everything when it re-acquires the lock. - n.runstate = closedWhileStartingState - n.lock.Unlock() - n.Wait() - return nil - case closedWhileStartingState: - // This branch handles double-close during startup. - n.lock.Unlock() - n.Wait() - return nil case runningState: // The node was started, release resources acquired by Start(). - defer n.lock.Unlock() var errs []error - if err := n.stopServices(); err != nil { + if err := n.stopServices(n.lifecycles); err != nil { errs = append(errs, err) } return n.doClose(errs) case closedState: - // Shutdown is already over. - n.lock.Unlock() return ErrNodeStopped default: - defer n.lock.Unlock() - panic(fmt.Sprintf("BUG: node is in unexpected state %d", n.runstate)) + panic(fmt.Sprintf("node is in unknown state %d", state)) } } -// doClose releases resources acquired by New() and collects shutdown errors. +// doClose releases resources acquired by New(), collecting errors. func (n *Node) doClose(errs []error) error { + n.setState(closedState) + if err := n.accman.Close(); err != nil { errs = append(errs, err) } @@ -274,9 +264,15 @@ func (n *Node) doClose(errs []error) error { errs = append(errs, err) } } + + // Close databases. This needs the lock because it needs to + // synchronize with OpenDatabase*. + n.lock.Lock() errs = append(errs, n.closeDatabases()...) + n.lock.Unlock() + + // Release instance directory lock. n.closeDataDir() - n.runstate = closedState // Unblock n.Wait. close(n.stop) @@ -292,48 +288,6 @@ func (n *Node) doClose(errs []error) error { } } -// RegisterLifecycle registers the given Lifecycle on the node. -func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { - if n.runstate != initializingState { - panic("can't register lifecycle on running/stopped node") - } - if containsLifecycle(n.lifecycles, lifecycle) { - panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) - } - n.lifecycles = append(n.lifecycles, lifecycle) -} - -// RegisterProtocols adds backend's protocols to the node's p2p server. -func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { - if n.runstate != initializingState { - panic("can't register protocols on running/stopped node") - } - n.server.Protocols = append(n.server.Protocols, protocols...) -} - -// RegisterAPIs registers the APIs a service provides on the node. -func (n *Node) RegisterAPIs(apis []rpc.API) { - if n.runstate != initializingState { - panic("can't register APIs on running/stopped node") - } - n.rpcAPIs = append(n.rpcAPIs, apis...) -} - -// RegisterPath mounts the given handler on the given path on the canonical HTTP server. -func (n *Node) RegisterPath(path string, handler http.Handler) string { - if n.runstate != initializingState { - panic("can't register HTTP handler on running/stopped node") - } - for _, server := range n.httpServers { - if atomic.LoadInt32(&server.RPCAllowed) == 1 { - server.srvMux.Handle(path, handler) - return server.endpoint - } - } - n.log.Warn(fmt.Sprintf("HTTP server not configured on node, path %s cannot be enabled", path)) - return "" -} - // registerHTTPServer registers the given HTTP server on the node. func (n *Node) registerHTTPServer(endpoint string, server *httpServer) { n.httpServers[endpoint] = server @@ -400,20 +354,27 @@ func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { return false } -// stopLifecycles stops the given lifecycles in reverse order. -func stopLifecycles(lfs []Lifecycle) map[reflect.Type]error { - errors := make(map[reflect.Type]error) - for i := len(lfs) - 1; i >= 0; i-- { - if err := lfs[i].Stop(); err != nil { - errors[reflect.TypeOf(lfs[i])] = err +// stopServices terminates running services, RPC and p2p networking. +// It is the inverse of Start. +func (n *Node) stopServices(running []Lifecycle) error { + n.stopIPC() + n.rpcAPIs = nil + + // Stop running lifecycles in reverse order. + failure := &StopError{Services: make(map[reflect.Type]error)} + for i := len(running) - 1; i >= 0; i-- { + if err := running[i].Stop(); err != nil { + failure.Services[reflect.TypeOf(running[i])] = err } } - return errors -} -// Config returns the configuration of node. -func (n *Node) Config() *Config { - return n.config + // Stop p2p networking. + n.server.Stop() + + if len(failure.Services) > 0 { + return failure + } + return nil } func (n *Node) openDataDir() error { @@ -449,8 +410,6 @@ func (n *Node) closeDataDir() { // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. func (n *Node) configureRPC() error { - n.RegisterAPIs(n.apis()) - // Start the various API endpoints, terminating all in case of errors if err := n.startInProc(); err != nil { return err @@ -459,25 +418,22 @@ func (n *Node) configureRPC() error { n.stopInProc() return err } - // configure HTTPServers + + // Configure HTTP servers. for _, server := range n.httpServers { - // configure the handlers handler := n.createHandler(server) if handler != nil { server.srvMux.Handle("/", handler) } - // create the HTTP server if err := n.createHTTPServer(server, false); err != nil { return err } } - // only register http server as a lifecycle if it has not already been registered + // Register HTTP server as a lifecycle if it has not already been registered. if !containsLifecycle(n.lifecycles, &n.httpServers) { - n.RegisterLifecycle(&n.httpServers) + n.lifecycles = append(n.lifecycles, &n.httpServers) } - - // All API endpoints started successfully return nil } @@ -562,29 +518,63 @@ func (n *Node) stopServer(server *httpServer) { delete(n.httpServers, server.endpoint) } -// stopServices terminates running services, RPC and p2p networking. -// It is the inverse of Start. -func (n *Node) stopServices() error { - if n.runstate != runningState { - panic("call to stopServices on node that isn't running") +// Wait blocks until the node is closed. +func (n *Node) Wait() { + <-n.stop +} + +// RegisterLifecycle registers the given Lifecycle on the node. +func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { + n.lock.Lock() + defer n.lock.Unlock() + + if n.state() != initializingState { + panic("can't register lifecycle on running/stopped node") + } + if containsLifecycle(n.lifecycles, lifecycle) { + panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) } + n.lifecycles = append(n.lifecycles, lifecycle) +} - // Terminate the API, services and the p2p server. - n.stopIPC() - n.rpcAPIs = nil - failure := new(StopError) - failure.Services = stopLifecycles(n.lifecycles) - n.server.Stop() +// RegisterProtocols adds backend's protocols to the node's p2p server. +func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { + n.lock.Lock() + defer n.lock.Unlock() - if len(failure.Services) > 0 { - return failure + if n.state() != initializingState { + panic("can't register protocols on running/stopped node") } - return nil + n.server.Protocols = append(n.server.Protocols, protocols...) } -// Wait blocks until the node is closed. -func (n *Node) Wait() { - <-n.stop +// RegisterAPIs registers the APIs a service provides on the node. +func (n *Node) RegisterAPIs(apis []rpc.API) { + n.lock.Lock() + defer n.lock.Unlock() + + if n.state() != initializingState { + panic("can't register APIs on running/stopped node") + } + n.rpcAPIs = append(n.rpcAPIs, apis...) +} + +// RegisterPath mounts the given handler on the given path on the canonical HTTP server. +func (n *Node) RegisterPath(path string, handler http.Handler) string { + n.lock.Lock() + defer n.lock.Unlock() + + if n.state() != initializingState { + panic("can't register HTTP handler on running/stopped node") + } + for _, server := range n.httpServers { + if atomic.LoadInt32(&server.RPCAllowed) == 1 { + server.srvMux.Handle(path, handler) + return server.endpoint + } + } + n.log.Warn(fmt.Sprintf("HTTP server not configured on node, path %s cannot be enabled", path)) + return "" } // Attach creates an RPC client attached to an in-process API handler. @@ -597,12 +587,17 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { n.lock.Lock() defer n.lock.Unlock() - if n.runstate == closedState { + if n.state() == closedState { return nil, ErrNodeStopped } return n.inprocHandler, nil } +// Config returns the configuration of node. +func (n *Node) Config() *Config { + return n.config +} + // Server retrieves the currently running P2P network layer. This method is meant // only to inspect fields of the currently running server. Callers should not // start or stop the returned server. @@ -661,7 +656,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( n.lock.Lock() defer n.lock.Unlock() - if n.runstate == closedState { + if n.state() == closedState { return nil, ErrNodeStopped } @@ -688,7 +683,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, n.lock.Lock() defer n.lock.Unlock() - if n.runstate == closedState { + if n.state() == closedState { return nil, ErrNodeStopped } @@ -752,17 +747,14 @@ type closeTrackingDB struct { } func (db *closeTrackingDB) Close() error { - db.n.dbLock.Lock() + db.n.lock.Lock() delete(db.n.databases, db) - db.n.dbLock.Unlock() + db.n.lock.Unlock() return db.Database.Close() } // wrapDatabase ensures the database will be auto-closed when Node is closed. func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { - n.dbLock.Lock() - defer n.dbLock.Unlock() - wrapper := &closeTrackingDB{db, n} n.databases[wrapper] = struct{}{} return wrapper @@ -770,9 +762,6 @@ func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { // closeDatabases closes all open databases. func (n *Node) closeDatabases() (errors []error) { - n.dbLock.Lock() - defer n.dbLock.Unlock() - for db := range n.databases { delete(n.databases, db) if err := db.Database.Close(); err != nil { diff --git a/node/node_test.go b/node/node_test.go index e57952105d32..f8df4b518b57 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -172,7 +172,7 @@ func TestNodeCloseClosesDB(t *testing.T) { } // This test checks that OpenDatabase can be used from within a Lifecycle Start method. -func TestNodeOpenDatabaseFromLifecycle(t *testing.T) { +func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) { stack, _ := New(testNodeConfig()) defer stack.Close() @@ -194,6 +194,25 @@ func TestNodeOpenDatabaseFromLifecycle(t *testing.T) { stack.Close() } +// This test checks that OpenDatabase can be used from within a Lifecycle Stop method. +func TestNodeOpenDatabaseFromLifecycleStop(t *testing.T) { + stack, _ := New(testNodeConfig()) + defer stack.Close() + + stack.RegisterLifecycle(&InstrumentedService{ + stopHook: func() { + db, err := stack.OpenDatabase("mydb", 0, 0, "") + if err != nil { + t.Fatal("can't open DB:", err) + } + db.Close() + }, + }) + + stack.Start() + stack.Close() +} + // Tests that registered Lifecycles get started and stopped correctly. func TestLifecycleLifeCycle(t *testing.T) { stack, _ := New(testNodeConfig()) From cc9fdddc14e5f5e88a768b291cb2ecf5476f8ab6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:14:44 +0200 Subject: [PATCH 131/160] node: unexport built-in API handler objects --- node/api.go | 83 +++++++++++++++++++++++++++++----------------------- node/node.go | 26 ---------------- 2 files changed, 46 insertions(+), 63 deletions(-) diff --git a/node/api.go b/node/api.go index 0c81bf4827b9..39ba48b2a1ee 100644 --- a/node/api.go +++ b/node/api.go @@ -24,26 +24,46 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rpc" ) -// PrivateAdminAPI is the collection of administrative API methods exposed only -// over a secure RPC channel. -type PrivateAdminAPI struct { - node *Node // Node interfaced by this API +// apis returns the collection of built-in RPC APIs. +func (n *Node) apis() []rpc.API { + return []rpc.API{ + { + Namespace: "admin", + Version: "1.0", + Service: &privateAdminAPI{n}, + }, { + Namespace: "admin", + Version: "1.0", + Service: &publicAdminAPI{n}, + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: debug.Handler, + }, { + Namespace: "web3", + Version: "1.0", + Service: &publicWeb3API{n}, + Public: true, + }, + } } -// NewPrivateAdminAPI creates a new API definition for the private admin methods -// of the node itself. -func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { - return &PrivateAdminAPI{node: node} +// privateAdminAPI is the collection of administrative API methods exposed only +// over a secure RPC channel. +type privateAdminAPI struct { + node *Node // Node interfaced by this API } // AddPeer requests connecting to a remote node, and also maintaining the new // connection at all times, even reconnecting if it is lost. -func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { +func (api *privateAdminAPI) AddPeer(url string) (bool, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { @@ -59,7 +79,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { } // RemovePeer disconnects from a remote node if the connection exists -func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { +func (api *privateAdminAPI) RemovePeer(url string) (bool, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { @@ -75,7 +95,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { } // AddTrustedPeer allows a remote node to always connect, even if slots are full -func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) { +func (api *privateAdminAPI) AddTrustedPeer(url string) (bool, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { @@ -91,7 +111,7 @@ func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) { // RemoveTrustedPeer removes a remote node from the trusted peer set, but it // does not disconnect it automatically. -func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) { +func (api *privateAdminAPI) RemoveTrustedPeer(url string) (bool, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { @@ -107,7 +127,7 @@ func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) { // PeerEvents creates an RPC subscription which receives peer events from the // node's p2p.Server -func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { +func (api *privateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { @@ -144,7 +164,7 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, } // StartRPC starts the HTTP RPC API server. -func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { +func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -218,7 +238,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } // StopRPC terminates an already running HTTP RPC API endpoint. -func (api *PrivateAdminAPI) StopRPC() (bool, error) { +func (api *privateAdminAPI) StopRPC() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -232,7 +252,7 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { } // StartWS starts the websocket RPC API server. -func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { +func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -307,7 +327,7 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str } // StopWS terminates an already running websocket RPC API endpoint. -func (api *PrivateAdminAPI) StopWS() (bool, error) { +func (api *privateAdminAPI) StopWS() (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -323,21 +343,15 @@ func (api *PrivateAdminAPI) StopWS() (bool, error) { return false, fmt.Errorf("WebSocket RPC not running") } -// PublicAdminAPI is the collection of administrative API methods exposed over +// publicAdminAPI is the collection of administrative API methods exposed over // both secure and unsecure RPC channels. -type PublicAdminAPI struct { +type publicAdminAPI struct { node *Node // Node interfaced by this API } -// NewPublicAdminAPI creates a new API definition for the public admin methods -// of the node itself. -func NewPublicAdminAPI(node *Node) *PublicAdminAPI { - return &PublicAdminAPI{node: node} -} - // Peers retrieves all the information we know about each individual peer at the // protocol granularity. -func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { +func (api *publicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { server := api.node.Server() if server == nil { return nil, ErrNodeStopped @@ -347,7 +361,7 @@ func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { // NodeInfo retrieves all the information we know about the host node at the // protocol granularity. -func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { +func (api *publicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { server := api.node.Server() if server == nil { return nil, ErrNodeStopped @@ -356,27 +370,22 @@ func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { } // Datadir retrieves the current data directory the node is using. -func (api *PublicAdminAPI) Datadir() string { +func (api *publicAdminAPI) Datadir() string { return api.node.DataDir() } -// PublicWeb3API offers helper utils -type PublicWeb3API struct { +// publicWeb3API offers helper utils +type publicWeb3API struct { stack *Node } -// NewPublicWeb3API creates a new Web3Service instance -func NewPublicWeb3API(stack *Node) *PublicWeb3API { - return &PublicWeb3API{stack} -} - // ClientVersion returns the node name -func (s *PublicWeb3API) ClientVersion() string { +func (s *publicWeb3API) ClientVersion() string { return s.stack.Server().Name } // Sha3 applies the ethereum sha3 implementation on the input. // It assumes the input is hex encoded. -func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { +func (s *publicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return crypto.Keccak256(input) } diff --git a/node/node.go b/node/node.go index 62c4f62674ff..c252f1e39cc7 100644 --- a/node/node.go +++ b/node/node.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" @@ -713,31 +712,6 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } -// apis returns the collection of RPC descriptors this node offers. -func (n *Node) apis() []rpc.API { - return []rpc.API{ - { - Namespace: "admin", - Version: "1.0", - Service: NewPrivateAdminAPI(n), - }, { - Namespace: "admin", - Version: "1.0", - Service: NewPublicAdminAPI(n), - Public: true, - }, { - Namespace: "debug", - Version: "1.0", - Service: debug.Handler, - }, { - Namespace: "web3", - Version: "1.0", - Service: NewPublicWeb3API(n), - Public: true, - }, - } -} - // closeTrackingDB wraps the Close method of a database. When the database is closed by the // service, the wrapper removes it from the node's database map. This ensures that Node // won't auto-close the database if it is closed by the service that opened it. From 390e8a3df822122d9cfac4321db144b345050ab6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:15:06 +0200 Subject: [PATCH 132/160] node: move RegisterApisFromWhitelist --- node/node.go | 22 ---------------------- node/rpcstack.go | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/node/node.go b/node/node.go index c252f1e39cc7..ad5927a0ee63 100644 --- a/node/node.go +++ b/node/node.go @@ -744,25 +744,3 @@ func (n *Node) closeDatabases() (errors []error) { } return errors } - -// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, -// and then registers all of the APIs exposed by the services. -func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { - if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { - log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) - } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := srv.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - } - } - return nil -} diff --git a/node/rpcstack.go b/node/rpcstack.go index 43716a4dbd66..aad54fa1945f 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -33,6 +33,28 @@ import ( "github.com/rs/cors" ) +// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, +// and then registers all of the APIs exposed by the services. +func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { + if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { + log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) + } + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := srv.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + } + return nil +} + type serverMap map[string]*httpServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql type httpServer struct { From 908268d51e600ed732d03db0d1b5bf25004bd414 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:33:19 +0200 Subject: [PATCH 133/160] miner: fix test scripts --- miner/stress_clique.go | 37 ++++++++++++++++++------------------- miner/stress_ethash.go | 42 ++++++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/miner/stress_clique.go b/miner/stress_clique.go index d09538050164..dbb285e068b3 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -22,6 +22,7 @@ package main import ( "bytes" "crypto/ecdsa" + "fmt" "io/ioutil" "math/big" "math/rand" @@ -35,8 +36,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -62,38 +64,38 @@ func main() { var ( nodes []struct { node *node.Node - backend ethapi.Backend + backend *eth.Ethereum } enodes []*enode.Node ) for _, sealer := range sealers { // Start the node and wait until it's up - node, ethBackend, err := makeSealer(genesis) + stack, ethBackend, err := makeSealer(genesis) if err != nil { panic(err) } - defer node.Close() + defer stack.Close() - for node.Server().NodeInfo().Ports.Listener == 0 { + for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } // Connect the node to al the previous ones for _, n := range enodes { - node.Server().AddPeer(n) + stack.Server().AddPeer(n) } // Start tracking the node and it's enode nodes = append(nodes, struct { node *node.Node - backend ethapi.Backend + backend *eth.Ethereum }{ - node, + stack, ethBackend, }) - enodes = append(enodes, node.Server().Self()) + enodes = append(enodes, stack.Server().Self()) // Inject the signer key and start sealing with it - store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) signer, err := store.ImportECDSA(sealer, "") if err != nil { panic(err) @@ -178,7 +180,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core return genesis } -func makeSealer(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { +func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -196,10 +198,10 @@ func makeSealer(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Start the node and configure a full Ethereum node on it stack, err := node.New(config) if err != nil { - return nil, err + return nil, nil, err } // Create and register the backend - ethBackend, err := eth.New(ctx, ð.Config{ + ethBackend, err := eth.New(stack, ð.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, @@ -215,12 +217,9 @@ func makeSealer(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { }, }) if err != nil { - return nil, err + return nil, nil, err } - stack.RegisterAPIs(ethBackend.APIs()) - stack.RegisterProtocols(ethBackend.Protocols()) - stack.RegisterLifecycle(ethBackend) - // Start the node and return if successful - return stack, ethBackend, stack.Start() + err = stack.Start() + return stack, ethBackend, err } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 4e0a75174d3f..b6e3899a2335 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -21,6 +21,7 @@ package main import ( "crypto/ecdsa" + "fmt" "io/ioutil" "math/big" "math/rand" @@ -61,39 +62,39 @@ func main() { genesis := makeGenesis(faucets) var ( - nodes []struct{ - node *node.Node - backend ethapi.Backend + nodes []struct { + node *node.Node + backend *eth.Ethereum } enodes []*enode.Node ) for i := 0; i < 4; i++ { // Start the node and wait until it's up - node, ethBackend, err := makeMiner(genesis) + stack, ethBackend, err := makeMiner(genesis) if err != nil { panic(err) } - defer node.Close() + defer stack.Close() - for node.Server().NodeInfo().Ports.Listener == 0 { + for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } // Connect the node to al the previous ones for _, n := range enodes { - node.Server().AddPeer(n) + stack.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, struct{ - node *node.Node - backend ethapi.Backend + nodes = append(nodes, struct { + node *node.Node + backend *eth.Ethereum }{ - node, + stack, ethBackend, }) - enodes = append(enodes, node.Server().Self()) + enodes = append(enodes, stack.Server().Self()) // Inject the signer key and start sealing with it - store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) + store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) if _, err := store.NewAccount(""); err != nil { panic(err) } @@ -154,7 +155,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { return genesis } -func makeMiner(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { +func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -173,9 +174,9 @@ func makeMiner(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { // Create the node and configure a full Ethereum node on it stack, err := node.New(config) if err != nil { - return nil, err + return nil, nil, err } - ethBackend, err := eth.New(ctx, ð.Config{ + ethBackend, err := eth.New(stack, ð.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, @@ -192,12 +193,9 @@ func makeMiner(genesis *core.Genesis) (*node.Node, ethapi.Backend, error) { }, }) if err != nil { - return nil, err + return nil, nil, err } - stack.RegisterAPIs(ethBackend.APIs()) - stack.RegisterProtocols(ethBackend.Protocols()) - stack.RegisterLifecycle(ethBackend) - // Start the node and return if successful - return stack, ethBackend, stack.Start() + err = stack.Start() + return stack, ethBackend, err } From 891b286abfea85bb7767dc1b097c555d29ff8393 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:41:50 +0200 Subject: [PATCH 134/160] miner: simplify test scripts --- miner/stress_clique.go | 24 +++++++----------------- miner/stress_ethash.go | 26 +++++++++----------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/miner/stress_clique.go b/miner/stress_clique.go index dbb285e068b3..a4bbb9d22112 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -62,10 +62,7 @@ func main() { genesis := makeGenesis(faucets, sealers) var ( - nodes []struct { - node *node.Node - backend *eth.Ethereum - } + nodes []*eth.Ethereum enodes []*enode.Node ) @@ -85,13 +82,7 @@ func main() { stack.Server().AddPeer(n) } // Start tracking the node and it's enode - nodes = append(nodes, struct { - node *node.Node - backend *eth.Ethereum - }{ - stack, - ethBackend, - }) + nodes = append(nodes, ethBackend) enodes = append(enodes, stack.Server().Self()) // Inject the signer key and start sealing with it @@ -104,11 +95,11 @@ func main() { panic(err) } } - // Iterate over all the nodes and start signing with them - time.Sleep(3 * time.Second) + // Iterate over all the nodes and start signing on them + time.Sleep(3 * time.Second) for _, node := range nodes { - if err := node.backend.StartMining(1); err != nil { + if err := node.StartMining(1); err != nil { panic(err) } } @@ -117,10 +108,9 @@ func main() { // Start injecting transactions from the faucet like crazy nonces := make([]uint64, len(faucets)) for { + // Pick a random signer node index := rand.Intn(len(faucets)) - - // Fetch the accessor for the relevant signer - backend := nodes[index%len(nodes)].backend + backend := nodes[index%len(nodes)] // Create a self transaction and inject into the pool tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index b6e3899a2335..1f4492a05d9f 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -62,10 +62,7 @@ func main() { genesis := makeGenesis(faucets) var ( - nodes []struct { - node *node.Node - backend *eth.Ethereum - } + nodes []*eth.Ethereum enodes []*enode.Node ) for i := 0; i < 4; i++ { @@ -83,14 +80,8 @@ func main() { for _, n := range enodes { stack.Server().AddPeer(n) } - // Start tracking the node and it's enode - nodes = append(nodes, struct { - node *node.Node - backend *eth.Ethereum - }{ - stack, - ethBackend, - }) + // Start tracking the node and its enode + nodes = append(nodes, ethBackend) enodes = append(enodes, stack.Server().Self()) // Inject the signer key and start sealing with it @@ -99,11 +90,11 @@ func main() { panic(err) } } - // Iterate over all the nodes and start signing with them - time.Sleep(3 * time.Second) + // Iterate over all the nodes and start mining + time.Sleep(3 * time.Second) for _, node := range nodes { - if err := node.backend.StartMining(1); err != nil { + if err := node.StartMining(1); err != nil { panic(err) } } @@ -112,9 +103,10 @@ func main() { // Start injecting transactions from the faucets like crazy nonces := make([]uint64, len(faucets)) for { + // Pick a random mining node index := rand.Intn(len(faucets)) - // Fetch the accessor for the relevant signer - backend := nodes[index%len(nodes)].backend + backend := nodes[index%len(nodes)] + // Create a self transaction and inject into the pool tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index]) if err != nil { From c70d5e64303dcf960037b2f5701ce1f2d9c56c6f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 22 Jul 2020 23:46:39 +0200 Subject: [PATCH 135/160] miner: simplify test scripts even more --- miner/stress_clique.go | 13 ++++--------- miner/stress_ethash.go | 11 +++-------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/miner/stress_clique.go b/miner/stress_clique.go index a4bbb9d22112..21538aaaed71 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -22,7 +22,6 @@ package main import ( "bytes" "crypto/ecdsa" - "fmt" "io/ioutil" "math/big" "math/rand" @@ -77,11 +76,11 @@ func main() { for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } - // Connect the node to al the previous ones + // Connect the node to all the previous ones for _, n := range enodes { stack.Server().AddPeer(n) } - // Start tracking the node and it's enode + // Start tracking the node and its enode nodes = append(nodes, ethBackend) enodes = append(enodes, stack.Server().Self()) @@ -117,17 +116,13 @@ func main() { if err != nil { panic(err) } - txpool := backend.TxPool() - if txpool == nil { - panic(fmt.Errorf("Ethereum service not running")) - } - if err := txpool.AddLocal(tx); err != nil { + if err := backend.TxPool().AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := txpool.Stats(); pend > 2048 { + if pend, _ := backend.TxPool().Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 1f4492a05d9f..5a7e7685a62b 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -21,7 +21,6 @@ package main import ( "crypto/ecdsa" - "fmt" "io/ioutil" "math/big" "math/rand" @@ -76,7 +75,7 @@ func main() { for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } - // Connect the node to al the previous ones + // Connect the node to all the previous ones for _, n := range enodes { stack.Server().AddPeer(n) } @@ -112,17 +111,13 @@ func main() { if err != nil { panic(err) } - txpool := backend.TxPool() - if txpool == nil { - panic(fmt.Errorf("Ethereum service not running")) - } - if err := txpool.AddLocal(tx); err != nil { + if err := backend.TxPool().AddLocal(tx); err != nil { panic(err) } nonces[index]++ // Wait if we're too saturated - if pend, _ := txpool.Stats(); pend > 2048 { + if pend, _ := backend.TxPool().Stats(); pend > 2048 { time.Sleep(100 * time.Millisecond) } } From 45f162cf5e1c2c0f6e525da9b19ecf5cbd605608 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 23 Jul 2020 10:52:54 +0200 Subject: [PATCH 136/160] node: remove atomic operations on n.state --- node/node.go | 53 +++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/node/node.go b/node/node.go index ad5927a0ee63..e9758338f7eb 100644 --- a/node/node.go +++ b/node/node.go @@ -50,7 +50,7 @@ type Node struct { stop chan struct{} // Channel to wait for termination notifications server *p2p.Server // Currently running P2P networking layer startStopLock sync.Mutex // Start/Stop are protected by an additional lock - stateFlag int32 // Tracks state of node lifecycle + state int // Tracks state of node lifecycle lock sync.Mutex lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle @@ -68,14 +68,6 @@ const ( closedState ) -func (n *Node) state() int32 { - return atomic.LoadInt32(&n.stateFlag) -} - -func (n *Node) setState(state int32) { - atomic.StoreInt32(&n.stateFlag, state) -} - // New creates a new P2P node, ready for protocol registration. func New(conf *Config) (*Node, error) { // Copy config and resolve the datadir so future changes to the current @@ -192,15 +184,16 @@ func (n *Node) Start() error { n.startStopLock.Lock() defer n.startStopLock.Unlock() - switch n.state() { + n.lock.Lock() + switch n.state { case runningState: + n.lock.Unlock() return ErrNodeRunning case closedState: + n.lock.Unlock() return ErrNodeStopped } - n.setState(runningState) - - n.lock.Lock() + n.state = runningState err := n.startNetworking() lifecycles := make([]Lifecycle, len(n.lifecycles)) copy(lifecycles, n.lifecycles) @@ -233,7 +226,10 @@ func (n *Node) Close() error { n.startStopLock.Lock() defer n.startStopLock.Unlock() - switch state := n.state(); state { + n.lock.Lock() + state := n.state + n.lock.Unlock() + switch state { case initializingState: // The node was never started. return n.doClose(nil) @@ -253,7 +249,12 @@ func (n *Node) Close() error { // doClose releases resources acquired by New(), collecting errors. func (n *Node) doClose(errs []error) error { - n.setState(closedState) + // Close databases. This needs the lock because it needs to + // synchronize with OpenDatabase*. + n.lock.Lock() + n.state = closedState + errs = append(errs, n.closeDatabases()...) + n.lock.Unlock() if err := n.accman.Close(); err != nil { errs = append(errs, err) @@ -264,12 +265,6 @@ func (n *Node) doClose(errs []error) error { } } - // Close databases. This needs the lock because it needs to - // synchronize with OpenDatabase*. - n.lock.Lock() - errs = append(errs, n.closeDatabases()...) - n.lock.Unlock() - // Release instance directory lock. n.closeDataDir() @@ -527,7 +522,7 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) { n.lock.Lock() defer n.lock.Unlock() - if n.state() != initializingState { + if n.state != initializingState { panic("can't register lifecycle on running/stopped node") } if containsLifecycle(n.lifecycles, lifecycle) { @@ -541,7 +536,7 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) { n.lock.Lock() defer n.lock.Unlock() - if n.state() != initializingState { + if n.state != initializingState { panic("can't register protocols on running/stopped node") } n.server.Protocols = append(n.server.Protocols, protocols...) @@ -552,7 +547,7 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { n.lock.Lock() defer n.lock.Unlock() - if n.state() != initializingState { + if n.state != initializingState { panic("can't register APIs on running/stopped node") } n.rpcAPIs = append(n.rpcAPIs, apis...) @@ -563,7 +558,7 @@ func (n *Node) RegisterPath(path string, handler http.Handler) string { n.lock.Lock() defer n.lock.Unlock() - if n.state() != initializingState { + if n.state != initializingState { panic("can't register HTTP handler on running/stopped node") } for _, server := range n.httpServers { @@ -586,7 +581,7 @@ func (n *Node) RPCHandler() (*rpc.Server, error) { n.lock.Lock() defer n.lock.Unlock() - if n.state() == closedState { + if n.state == closedState { return nil, ErrNodeStopped } return n.inprocHandler, nil @@ -654,8 +649,7 @@ func (n *Node) EventMux() *event.TypeMux { func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() - - if n.state() == closedState { + if n.state == closedState { return nil, ErrNodeStopped } @@ -681,8 +675,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() - - if n.state() == closedState { + if n.state == closedState { return nil, ErrNodeStopped } From 4a6faffd35655f0d1c668473a8462b37a0c5792f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 24 Jul 2020 15:00:40 +0200 Subject: [PATCH 137/160] node: WIP rewrite HTTP server setup The HTTP server code was very subtle and I couldn't really figure out if it's correct or not. This change rewrites it with the intention of simplifying the logic in node.go and reducing duplication in api.go. --- node/api.go | 161 ++++++++---------------- node/node.go | 255 +++++++++++--------------------------- node/node_test.go | 168 +++++++++++++------------ node/rpcstack.go | 279 ++++++++++++++++++++++++++++++++++-------- node/rpcstack_test.go | 109 ++++++++++------- 5 files changed, 493 insertions(+), 479 deletions(-) diff --git a/node/api.go b/node/api.go index 39ba48b2a1ee..a8b700df481a 100644 --- a/node/api.go +++ b/node/api.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "strings" - "sync/atomic" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -168,7 +167,7 @@ func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis api.node.lock.Lock() defer api.node.lock.Unlock() - // set host, port, and endpoint + // Determine host and port. if host == nil { h := DefaultHTTPHost if api.node.config.HTTPHost != "" { @@ -179,76 +178,48 @@ func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis if port == nil { port = &api.node.config.HTTPPort } - endpoint := fmt.Sprintf("%s:%d", *host, *port) - // check if HTTP server already exists - if server, exists := api.node.httpServers[endpoint]; exists { - if atomic.LoadInt32(&server.RPCAllowed) == 1 { - return false, fmt.Errorf("HTTP RPC already running on %v", server.Listener.Addr()) - } - } - allowedOrigins := api.node.config.HTTPCors + // Determine config. + config := httpConfig{ + CorsAllowedOrigins: api.node.config.HTTPCors, + Vhosts: api.node.config.HTTPVirtualHosts, + Modules: api.node.config.HTTPModules, + } if cors != nil { - allowedOrigins = nil + config.CorsAllowedOrigins = nil for _, origin := range strings.Split(*cors, ",") { - allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) + config.CorsAllowedOrigins = append(config.CorsAllowedOrigins, strings.TrimSpace(origin)) } } - - allowedVHosts := api.node.config.HTTPVirtualHosts if vhosts != nil { - allowedVHosts = nil + config.Vhosts = nil for _, vhost := range strings.Split(*host, ",") { - allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost)) + config.Vhosts = append(config.Vhosts, strings.TrimSpace(vhost)) } } - - modules := api.node.config.HTTPModules if apis != nil { - modules = nil + config.Modules = nil for _, m := range strings.Split(*apis, ",") { - modules = append(modules, strings.TrimSpace(m)) + config.Modules = append(config.Modules, strings.TrimSpace(m)) } } - // configure http server - httpServer := &httpServer{ - host: *host, - port: *port, - endpoint: endpoint, - Srv: rpc.NewServer(), - CorsAllowedOrigins: allowedOrigins, - Vhosts: allowedVHosts, - Whitelist: modules, + + if err := api.node.http.setListenAddr(*host, *port); err != nil { + return false, err + } + if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil { + return false, err } - // create handler - handler := NewHTTPHandlerStack(httpServer.Srv, httpServer.CorsAllowedOrigins, httpServer.Vhosts) - httpServer.srvMux.Handle("/", handler) - // create HTTP server - if err := api.node.createHTTPServer(httpServer, false); err != nil { + if err := api.node.http.start(); err != nil { return false, err } - // register the HTTP server - api.node.registerHTTPServer(endpoint, httpServer) - // start the HTTP server - httpServer.Start() - api.node.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", httpServer.Listener.Addr()), - "cors", strings.Join(httpServer.CorsAllowedOrigins, ","), - "vhosts", strings.Join(httpServer.Vhosts, ",")) return true, nil } -// StopRPC terminates an already running HTTP RPC API endpoint. +// StopRPC shuts down the HTTP server. func (api *privateAdminAPI) StopRPC() (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - for _, httpServer := range api.node.httpServers { - if atomic.LoadInt32(&httpServer.RPCAllowed) == 1 { - api.node.stopServer(httpServer) - return true, nil - } - } - return false, fmt.Errorf("HTTP RPC not running") + api.node.http.stop() + return true, nil } // StartWS starts the websocket RPC API server. @@ -256,13 +227,7 @@ func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str api.node.lock.Lock() defer api.node.lock.Unlock() - // check if an existing WS server already exists - for _, server := range api.node.httpServers { - if atomic.LoadInt32(&server.WSAllowed) == 1 { - return false, fmt.Errorf("WebSocket RPC already running on %v", server.Listener.Addr()) - } - } - // set host, port and endpoint + // Determine host and port. if host == nil { h := DefaultWSHost if api.node.config.WSHost != "" { @@ -273,74 +238,48 @@ func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if port == nil { port = &api.node.config.WSPort } - endpoint := fmt.Sprintf("%s:%d", *host, *port) - origins := api.node.config.WSOrigins - if allowedOrigins != nil { - origins = nil - for _, origin := range strings.Split(*allowedOrigins, ",") { - origins = append(origins, strings.TrimSpace(origin)) - } + // Determine config. + config := wsConfig{ + Modules: api.node.config.WSModules, + Origins: api.node.config.WSOrigins, + // ExposeAll: api.node.config.WSExposeAll, } - // check if an HTTP server exists on the given endpoint, and if so, enable websocket on that HTTP server - existingServer := api.node.existingHTTPServer(endpoint) - if existingServer != nil { - atomic.StoreInt32(&existingServer.WSAllowed, 1) - existingServer.WsOrigins = origins - - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", existingServer.Listener.Addr())) - return true, nil - } - - modules := api.node.config.WSModules if apis != nil { - modules = nil + config.Modules = nil for _, m := range strings.Split(*apis, ",") { - modules = append(modules, strings.TrimSpace(m)) + config.Modules = append(config.Modules, strings.TrimSpace(m)) } } - // configure http server - wsServer := &httpServer{ - Srv: rpc.NewServer(), - endpoint: endpoint, - host: *host, - port: *port, - Whitelist: modules, - WsOrigins: origins, - WSAllowed: 1, + if allowedOrigins != nil { + config.Origins = nil + for _, origin := range strings.Split(*allowedOrigins, ",") { + config.Origins = append(config.Origins, strings.TrimSpace(origin)) + } } - // create handler - handler := wsServer.Srv.WebsocketHandler(wsServer.WsOrigins) - wsServer.srvMux.Handle("/", handler) - // create the HTTP server - if err := api.node.createHTTPServer(wsServer, api.node.config.WSExposeAll); err != nil { + + // Enable WebSocket on the server. + server := api.node.wsServerForPort(*port) + if err := server.setListenAddr(*host, *port); err != nil { return false, err } - // register the HTTP server - api.node.registerHTTPServer(endpoint, wsServer) - // start the HTTP server - if err := wsServer.Start(); err != nil { + if err := server.enableWS(api.node.rpcAPIs, config); err != nil { return false, err } - api.node.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", wsServer.Listener.Addr())) + if err := server.start(); err != nil { + return false, err + } + api.node.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint()) return true, nil } -// StopWS terminates an already running websocket RPC API endpoint. +// StopWS terminates all WebSocket servers. func (api *privateAdminAPI) StopWS() (bool, error) { - api.node.lock.Lock() - defer api.node.lock.Unlock() - - for _, httpServer := range api.node.httpServers { - if atomic.SwapInt32(&httpServer.WSAllowed, 0) == 1 { - // if RPC is not enabled on the WS http server, shut it down - if atomic.LoadInt32(&httpServer.RPCAllowed) == 0 { - api.node.stopServer(httpServer) - } - return true, nil - } + if api.node.http.wsAllowed() { + api.node.http.disableWS() } - return false, fmt.Errorf("WebSocket RPC not running") + api.node.ws.stop() + return true, nil } // publicAdminAPI is the collection of administrative API methods exposed over diff --git a/node/node.go b/node/node.go index e9758338f7eb..d907d77d9eee 100644 --- a/node/node.go +++ b/node/node.go @@ -17,17 +17,14 @@ package node import ( - "context" "errors" "fmt" - "net" "net/http" "os" "path/filepath" "reflect" "strings" "sync" - "sync/atomic" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" @@ -54,10 +51,11 @@ type Node struct { lock sync.Mutex lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle - httpServers serverMap // serverMap stores information about the node's rpc, ws, and graphQL http servers. - inprocHandler *rpc.Server // In-process RPC request handler to process the API requests rpcAPIs []rpc.API // List of APIs currently provided by the node - ipc *httpServer // Stores information about the ipc http server + http *httpServer // + ws *httpServer // + ipc *ipcServer // Stores information about the ipc http server + inprocHandler *rpc.Server // In-process RPC request handler to process the API requests databases map[*closeTrackingDB]struct{} // All open databases } @@ -99,8 +97,6 @@ func New(conf *Config) (*Node, error) { node := &Node{ config: conf, - httpServers: make(serverMap), - ipc: &httpServer{endpoint: conf.IPCEndpoint()}, inprocHandler: rpc.NewServer(), eventmux: new(event.TypeMux), log: conf.Logger, @@ -139,41 +135,10 @@ func New(conf *Config) (*Node, error) { node.server.Config.NodeDatabase = node.config.NodeDB() } - // Configure HTTP servers. - if conf.HTTPHost != "" { - httpServ := &httpServer{ - CorsAllowedOrigins: conf.HTTPCors, - Vhosts: conf.HTTPVirtualHosts, - Whitelist: conf.HTTPModules, - Timeouts: conf.HTTPTimeouts, - Srv: rpc.NewServer(), - endpoint: conf.HTTPEndpoint(), - host: conf.HTTPHost, - port: conf.HTTPPort, - RPCAllowed: 1, - } - // Enable WebSocket on HTTP port if enabled. - if conf.WSHost != "" && conf.WSPort == conf.HTTPPort { - httpServ.WSAllowed = 1 - httpServ.WsOrigins = conf.WSOrigins - httpServ.Whitelist = append(httpServ.Whitelist, conf.WSModules...) - - node.httpServers[conf.HTTPEndpoint()] = httpServ - return node, nil - } - node.httpServers[conf.HTTPEndpoint()] = httpServ - } - if conf.WSHost != "" { - node.httpServers[conf.WSEndpoint()] = &httpServer{ - WsOrigins: conf.WSOrigins, - Whitelist: conf.WSModules, - Srv: rpc.NewServer(), - endpoint: conf.WSEndpoint(), - host: conf.WSHost, - port: conf.WSPort, - WSAllowed: 1, - } - } + // Configure RPC servers. + node.http = newHTTPServer(node.log, conf.HTTPTimeouts) + node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) + node.ipc = newIPCServer(node.log, conf.IPCEndpoint()) return node, nil } @@ -282,57 +247,15 @@ func (n *Node) doClose(errs []error) error { } } -// registerHTTPServer registers the given HTTP server on the node. -func (n *Node) registerHTTPServer(endpoint string, server *httpServer) { - n.httpServers[endpoint] = server -} - -// existingHTTPServer checks if an HTTP server is already configured on the given endpoint. -func (n *Node) existingHTTPServer(endpoint string) *httpServer { - if server, exists := n.httpServers[endpoint]; exists { - return server - } - return nil -} - -// createHTTPServer creates an http.Server and adds it to the given httpServer. -func (n *Node) createHTTPServer(h *httpServer, exposeAll bool) error { - // register apis and create handler stack - err := RegisterApisFromWhitelist(n.rpcAPIs, h.Whitelist, h.Srv, exposeAll) - if err != nil { - return err - } - - // start the HTTP listener - listener, err := net.Listen("tcp", h.endpoint) - if err != nil { - return err - } - // create the HTTP server - httpSrv := &http.Server{Handler: &h.srvMux} - // check timeouts if they exist - if h.Timeouts != (rpc.HTTPTimeouts{}) { - CheckTimeouts(&h.Timeouts) - httpSrv.ReadTimeout = h.Timeouts.ReadTimeout - httpSrv.WriteTimeout = h.Timeouts.WriteTimeout - httpSrv.IdleTimeout = h.Timeouts.IdleTimeout - } - // add listener and http.Server to httpServer - h.Listener = listener - h.Server = httpSrv - - return nil -} - // startNetworking starts all network endpoints. func (n *Node) startNetworking() error { n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) if err := n.server.Start(); err != nil { return convertFileLockError(err) } - err := n.configureRPC() + err := n.startRPC() if err != nil { - n.httpServers.Stop() + n.stopRPC() n.server.Stop() } return err @@ -351,8 +274,7 @@ func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool { // stopServices terminates running services, RPC and p2p networking. // It is the inverse of Start. func (n *Node) stopServices(running []Lifecycle) error { - n.stopIPC() - n.rpcAPIs = nil + n.stopRPC() // Stop running lifecycles in reverse order. failure := &StopError{Services: make(map[reflect.Type]error)} @@ -403,54 +325,66 @@ func (n *Node) closeDataDir() { // configureRPC is a helper method to configure all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. -func (n *Node) configureRPC() error { - // Start the various API endpoints, terminating all in case of errors +func (n *Node) startRPC() error { if err := n.startInProc(); err != nil { return err } - if err := n.startIPC(); err != nil { - n.stopInProc() - return err + + // Configure IPC. + if n.ipc.endpoint != "" { + if err := n.ipc.start(); err != nil { + return err + } + } + + // Configure HTTP. + if n.config.HTTPHost != "" { + config := httpConfig{ + CorsAllowedOrigins: n.config.HTTPCors, + Vhosts: n.config.HTTPVirtualHosts, + Modules: n.config.HTTPModules, + } + if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil { + return err + } + if err := n.http.enableRPC(n.rpcAPIs, config); err != nil { + return err + } } - // Configure HTTP servers. - for _, server := range n.httpServers { - handler := n.createHandler(server) - if handler != nil { - server.srvMux.Handle("/", handler) + // Configure WebSocket. + if n.config.WSHost != "" { + server := n.wsServerForPort(n.config.WSPort) + config := wsConfig{ + Modules: n.config.WSModules, + Origins: n.config.WSOrigins, } - if err := n.createHTTPServer(server, false); err != nil { + if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil { + return err + } + if err := server.enableWS(n.rpcAPIs, config); err != nil { return err } } - // Register HTTP server as a lifecycle if it has not already been registered. - if !containsLifecycle(n.lifecycles, &n.httpServers) { - n.lifecycles = append(n.lifecycles, &n.httpServers) + if err := n.http.start(); err != nil { + return err } - return nil + return n.ws.start() } -// createHandler creates the http.Handler for the given httpServer. -func (n *Node) createHandler(server *httpServer) http.Handler { - var handler http.Handler - if atomic.LoadInt32(&server.RPCAllowed) == 1 { - handler = NewHTTPHandlerStack(server.Srv, server.CorsAllowedOrigins, server.Vhosts) - // wrap ws handler just in case ws is enabled through the console after start-up - wsHandler := server.Srv.WebsocketHandler(server.WsOrigins) - handler = server.NewWebsocketUpgradeHandler(handler, wsHandler) - - n.log.Info("HTTP configured on endpoint ", "endpoint", fmt.Sprintf("http://%s/", server.endpoint)) - if atomic.LoadInt32(&server.WSAllowed) == 1 { - n.log.Info("Websocket configured on endpoint ", "endpoint", fmt.Sprintf("ws://%s/", server.endpoint)) - } - } - if (atomic.LoadInt32(&server.WSAllowed) == 1) && handler == nil { - handler = server.Srv.WebsocketHandler(server.WsOrigins) - n.log.Info("Websocket configured on endpoint ", "endpoint", fmt.Sprintf("ws://%s/", server.endpoint)) +func (n *Node) wsServerForPort(port int) *httpServer { + if n.config.HTTPHost == "" || n.http.port == port { + return n.http } + return n.ws +} - return handler +func (n *Node) stopRPC() { + n.http.stop() + n.ws.stop() + n.ipc.stop() + n.stopInProc() } // startInProc registers all RPC APIs on the inproc server. @@ -468,50 +402,6 @@ func (n *Node) stopInProc() { n.inprocHandler.Stop() } -// startIPC initializes and starts the IPC RPC endpoint. -func (n *Node) startIPC() error { - if n.ipc.endpoint == "" { - return nil // IPC disabled. - } - listener, handler, err := rpc.StartIPCEndpoint(n.ipc.endpoint, n.rpcAPIs) - if err != nil { - return err - } - n.ipc.Listener = listener - n.ipc.handler = handler - n.log.Info("IPC endpoint opened", "url", n.ipc.endpoint) - return nil -} - -// stopIPC terminates the IPC RPC endpoint. -func (n *Node) stopIPC() { - if n.ipc.Listener != nil { - n.ipc.Listener.Close() - n.ipc.Listener = nil - n.log.Info("IPC endpoint closed", "url", n.ipc.endpoint) - } - if n.ipc.Srv != nil { - n.ipc.Srv.Stop() - n.ipc.Srv = nil - } -} - -// stopServers terminates the given HTTP servers' endpoints -func (n *Node) stopServer(server *httpServer) { - if server.Server != nil { - url := fmt.Sprintf("http://%v/", server.Listener.Addr()) - // Don't bother imposing a timeout here. - server.Server.Shutdown(context.Background()) - n.log.Info("HTTP Endpoint closed", "url", url) - } - if server.Srv != nil { - server.Srv.Stop() - server.Srv = nil - } - // remove stopped http server from node's http servers - delete(n.httpServers, server.endpoint) -} - // Wait blocks until the node is closed. func (n *Node) Wait() { <-n.stop @@ -561,14 +451,8 @@ func (n *Node) RegisterPath(path string, handler http.Handler) string { if n.state != initializingState { panic("can't register HTTP handler on running/stopped node") } - for _, server := range n.httpServers { - if atomic.LoadInt32(&server.RPCAllowed) == 1 { - server.srvMux.Handle(path, handler) - return server.endpoint - } - } - n.log.Warn(fmt.Sprintf("HTTP server not configured on node, path %s cannot be enabled", path)) - return "" + n.http.mux.Handle(path, handler) + return n.http.endpoint } // Attach creates an RPC client attached to an in-process API handler. @@ -596,6 +480,9 @@ func (n *Node) Config() *Config { // only to inspect fields of the currently running server. Callers should not // start or stop the returned server. func (n *Node) Server() *p2p.Server { + n.lock.Lock() + defer n.lock.Unlock() + return n.server } @@ -620,21 +507,17 @@ func (n *Node) IPCEndpoint() string { return n.ipc.endpoint } +// HTTPEndpoint returns the URL of the HTTP server. +func (n *Node) HTTPEndpoint() string { + return "http://" + n.http.listenAddr() +} + // WSEndpoint retrieves the current WS endpoint used by the protocol stack. func (n *Node) WSEndpoint() string { - n.lock.Lock() - defer n.lock.Unlock() - - for _, httpServer := range n.httpServers { - if atomic.LoadInt32(&httpServer.WSAllowed) == 1 { - if httpServer.Listener != nil { - return httpServer.Listener.Addr().String() - } - return httpServer.endpoint - } + if n.http.wsAllowed() { + return "ws://" + n.http.listenAddr() } - - return n.config.WSEndpoint() + return "ws://" + n.ws.listenAddr() } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/node_test.go b/node/node_test.go index f8df4b518b57..f5d25360c23e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -18,13 +18,11 @@ package node import ( "errors" - "fmt" "io" "io/ioutil" "net/http" "os" "reflect" - "sync/atomic" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -388,44 +386,44 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stack.server.PrivateKey = testNodeKey } -// Tests whether a given httpServer can be registered on the node -func TestRegisterHTTPServer(t *testing.T) { - stack, err := New(testNodeConfig()) - if err != nil { - t.Fatalf("failed to create protocol stack: %v", err) - } - defer stack.Close() - - srv1 := &httpServer{ - host: "test1", - port: 0001, - } - endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) - stack.registerHTTPServer(endpoint1, srv1) - - srv2 := &httpServer{ - host: "test2", - port: 0002, - } - endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) - stack.registerHTTPServer(endpoint2, srv2) - - noop := &httpServer{ - host: "test", - port: 0000, - } - endpointNoop := fmt.Sprintf("%s:%d", noop.host, noop.port) - - if srv1 != stack.existingHTTPServer(endpoint1) { - t.Fatalf("server %v was not properly registered on the given endpoint %s", srv1, endpoint1) - } - if srv2 != stack.existingHTTPServer(endpoint2) { - t.Fatalf("server %v was not properly registered on the given endpoint %s", srv2, endpoint2) - } - if noop == stack.existingHTTPServer(endpointNoop) { - t.Fatalf("server %v was incorrectly registered on the given endpoint %s", noop, endpointNoop) - } -} +// // Tests whether a given httpServer can be registered on the node +// func TestRegisterHTTPServer(t *testing.T) { +// stack, err := New(testNodeConfig()) +// if err != nil { +// t.Fatalf("failed to create protocol stack: %v", err) +// } +// defer stack.Close() +// +// srv1 := &httpServer{ +// host: "test1", +// port: 0001, +// } +// endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) +// stack.registerHTTPServer(endpoint1, srv1) +// +// srv2 := &httpServer{ +// host: "test2", +// port: 0002, +// } +// endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) +// stack.registerHTTPServer(endpoint2, srv2) +// +// noop := &httpServer{ +// host: "test", +// port: 0000, +// } +// endpointNoop := fmt.Sprintf("%s:%d", noop.host, noop.port) +// +// if srv1 != stack.existingHTTPServer(endpoint1) { +// t.Fatalf("server %v was not properly registered on the given endpoint %s", srv1, endpoint1) +// } +// if srv2 != stack.existingHTTPServer(endpoint2) { +// t.Fatalf("server %v was not properly registered on the given endpoint %s", srv2, endpoint2) +// } +// if noop == stack.existingHTTPServer(endpointNoop) { +// t.Fatalf("server %v was incorrectly registered on the given endpoint %s", noop, endpointNoop) +// } +// } // Tests whether a handler can be successfully mounted on the canonical HTTP server // on the given path @@ -476,51 +474,51 @@ func TestRegisterPath_Unsuccessful(t *testing.T) { assert.Equal(t, "", endpoint) } -// Tests whether a node can successfully create and register HTTP server -// lifecycles on the node. -func TestHTTPServerCreateAndStop(t *testing.T) { - // test on same ports - node1 := startHTTP(t, 7453, 7453) - if len(node1.httpServers) != 1 { - t.Fatalf("node has more than 1 http server") - } - // check to make sure http servers are registered - if !containsLifecycle(node1.lifecycles, &node1.httpServers) { - t.Fatal("HTTP servers not registered as lifecycles on the node") - } - // check to make sure http servers are configured properly - for _, server := range node1.httpServers { - if atomic.LoadInt32(&server.WSAllowed) == 0 && atomic.LoadInt32(&server.RPCAllowed) == 0 { - t.Fatalf("node's http server is not configured to handle both rpc and ws") - } - node1.stopServer(server) - if node1.existingHTTPServer(server.endpoint) != nil { - t.Fatalf("failed to remove server %v from node after stopping it", server) - } - } - node1.Close() - - // test on separate ports - node2 := startHTTP(t, 7453, 9393) - if len(node2.httpServers) != 2 { - t.Fatalf("amount of http servers on the node is not equal to 2") - } - // check to make sure http servers are registered - if !containsLifecycle(node2.lifecycles, &node2.httpServers) { - t.Fatal("HTTP servers not registered as lifecycles on the node") - } - // check that neither http server has both ws and rpc enabled - for _, server := range node2.httpServers { - if atomic.LoadInt32(&server.WSAllowed) == 1 && atomic.LoadInt32(&server.RPCAllowed) == 1 { - t.Fatalf("both rpc and ws allowed on a single http server") - } - node2.stopServer(server) - if node2.existingHTTPServer(server.endpoint) != nil { - t.Fatalf("failed to remove server %v from node after stopping it", server) - } - } - node2.Close() -} +// // Tests whether a node can successfully create and register HTTP server +// // lifecycles on the node. +// func TestHTTPServerCreateAndStop(t *testing.T) { +// // test on same ports +// node1 := startHTTP(t, 7453, 7453) +// if len(node1.httpServers) != 1 { +// t.Fatalf("node has more than 1 http server") +// } +// // check to make sure http servers are registered +// if !containsLifecycle(node1.lifecycles, &node1.httpServers) { +// t.Fatal("HTTP servers not registered as lifecycles on the node") +// } +// // check to make sure http servers are configured properly +// for _, server := range node1.httpServers { +// if atomic.LoadInt32(&server.WSAllowed) == 0 && atomic.LoadInt32(&server.RPCAllowed) == 0 { +// t.Fatalf("node's http server is not configured to handle both rpc and ws") +// } +// node1.stopServer(server) +// if node1.existingHTTPServer(server.endpoint) != nil { +// t.Fatalf("failed to remove server %v from node after stopping it", server) +// } +// } +// node1.Close() +// +// // test on separate ports +// node2 := startHTTP(t, 7453, 9393) +// if len(node2.httpServers) != 2 { +// t.Fatalf("amount of http servers on the node is not equal to 2") +// } +// // check to make sure http servers are registered +// if !containsLifecycle(node2.lifecycles, &node2.httpServers) { +// t.Fatal("HTTP servers not registered as lifecycles on the node") +// } +// // check that neither http server has both ws and rpc enabled +// for _, server := range node2.httpServers { +// if atomic.LoadInt32(&server.WSAllowed) == 1 && atomic.LoadInt32(&server.RPCAllowed) == 1 { +// t.Fatalf("both rpc and ws allowed on a single http server") +// } +// node2.stopServer(server) +// if node2.existingHTTPServer(server.endpoint) != nil { +// t.Fatalf("failed to remove server %v from node after stopping it", server) +// } +// } +// node2.Close() +// } // Tests whether websocket requests can be handled on the same port as a regular http server. func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { diff --git a/node/rpcstack.go b/node/rpcstack.go index aad54fa1945f..d1541cd98650 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -55,73 +55,221 @@ func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server return nil } -type serverMap map[string]*httpServer // Stores information about all http servers (if any) by their endpoint, including http, ws, and graphql +// httpConfig is the JSON-RPC/HTTP configuration. +type httpConfig struct { + Modules []string + CorsAllowedOrigins []string + Vhosts []string +} + +// wsConfig is the JSON-RPC/Websocket configuration +type wsConfig struct { + Origins []string + Modules []string +} type httpServer struct { - srvMux http.ServeMux + log log.Logger + timeouts rpc.HTTPTimeouts + mux http.ServeMux // registered handlers go here - handler http.Handler - Srv *rpc.Server - Server *http.Server + mu sync.Mutex + server *http.Server + listener net.Listener // non-nil when server is running - Listener net.Listener + httpConfig httpConfig + httpRPC *rpc.Server + httpHandler http.Handler + + wsConfig wsConfig + wsRPC *rpc.Server + wsHandler http.Handler endpoint string host string port int + config httpConfig - Whitelist []string - - CorsAllowedOrigins []string - Vhosts []string - WsOrigins []string - Timeouts rpc.HTTPTimeouts + // atomic flags for the handler + rpcAllowedFlag int32 + wsAllowedFlag int32 +} - RPCAllowed int32 - WSAllowed int32 +func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { + return &httpServer{log: log, timeouts: timeouts} } -func (sm serverMap) Start() error { - for _, server := range sm { - if err := server.Start(); err != nil { - return sm.Stop() - } +// setListenAddr configures the listening address of the server. +// The address can only be set while the server isn't running. +func (h *httpServer) setListenAddr(host string, port int) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.listener != nil && (host != h.host || port != h.port) { + return fmt.Errorf("HTTP server already running on %s", h.endpoint) } + + h.host, h.port = host, port + h.endpoint = fmt.Sprintf("%s:%d", host, port) return nil } -func (sm serverMap) Stop() error { - for _, server := range sm { - if err := server.Stop(); err != nil { - return err - } +// listenAddr returns the listening address of the server. +func (h *httpServer) listenAddr() string { + h.mu.Lock() + defer h.mu.Unlock() + + if h.listener != nil { + return h.listener.Addr().String() } + return h.endpoint +} + +// enableRPC turns on JSON-RPC over HTTP on the server. +func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.httpRPC != nil { + return fmt.Errorf("JSON-RPC over HTTP is already enabled") + } + + // Create RPC server. + srv := rpc.NewServer() + if err := RegisterApisFromWhitelist(apis, h.httpConfig.Modules, srv, false); err != nil { + return err + } + // Create handler. + h.httpRPC = srv + h.httpHandler = NewHTTPHandlerStack(h.httpRPC, config.CorsAllowedOrigins, config.Vhosts) + atomic.StoreInt32(&h.rpcAllowedFlag, 1) return nil } -// Start starts the httpServer's http.Server -func (h *httpServer) Start() error { - go h.Server.Serve(h.Listener) - log.Info("HTTP endpoint successfully opened", "url", fmt.Sprintf("http://%v/", h.Listener.Addr())) +// enableWS turns on JSON-RPC over WebSocket on the server. +func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.wsRPC != nil { + return fmt.Errorf("JSON-RPC over WebSocket is already enabled") + } + + // Create RPC server. + srv := rpc.NewServer() + if err := RegisterApisFromWhitelist(apis, h.wsConfig.Modules, srv, false); err != nil { + return err + } + // Create handler. + h.wsRPC = rpc.NewServer() + h.wsHandler = h.wsRPC.WebsocketHandler(config.Origins) + atomic.StoreInt32(&h.wsAllowedFlag, 1) return nil } -// Stop shuts down the httpServer's http.Server -func (h *httpServer) Stop() error { - if h.Server != nil { - url := fmt.Sprintf("http://%v/", h.Listener.Addr()) - // Don't bother imposing a timeout here. - h.Server.Shutdown(context.Background()) - log.Info("HTTP Endpoint closed", "url", url) +// disableWS disables JSON-RPC over WebSocket. +func (h *httpServer) disableWS() { + h.mu.Lock() + defer h.mu.Unlock() + + atomic.StoreInt32(&h.wsAllowedFlag, 0) + h.wsRPC.Stop() + h.wsRPC = nil +} + +// start starts the HTTP server if it is enabled and not already running. +func (h *httpServer) start() error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.endpoint == "" || h.listener != nil { + return nil // already running or not configured } - if h.Srv != nil { - h.Srv.Stop() - h.Srv = nil + + // Initialize the server. + h.server = &http.Server{Handler: h} + if h.timeouts != (rpc.HTTPTimeouts{}) { + CheckTimeouts(&h.timeouts) + h.server.ReadTimeout = h.timeouts.ReadTimeout + h.server.WriteTimeout = h.timeouts.WriteTimeout + h.server.IdleTimeout = h.timeouts.IdleTimeout } + // Start the server. + listener, err := net.Listen("tcp", h.endpoint) + if err != nil { + return err + } + h.listener = listener + go h.server.Serve(listener) + h.log.Info("HTTP endpoint opened", + "url", fmt.Sprintf("http://%v/", listener.Addr()), + "cors", strings.Join(h.config.CorsAllowedOrigins, ","), + "vhosts", strings.Join(h.config.Vhosts, ","), + ) return nil } +// stop shuts down the HTTP server. +func (h *httpServer) stop() error { + h.mu.Lock() + defer h.mu.Unlock() + if h.listener == nil { + return nil // not running + } + + // Shut down the server. + if h.httpRPC != nil { + h.httpRPC.Stop() + } + if h.wsRPC != nil { + h.wsRPC.Stop() + } + h.server.Shutdown(context.Background()) + h.listener.Close() + h.log.Info("HTTP endpoint closed", "url", h.endpoint) + + // Clear out everything to allow re-configuring it later. + h.host, h.port, h.endpoint = "", 0, "" + h.server, h.listener = nil, nil + h.httpRPC, h.httpHandler, h.wsRPC, h.wsHandler = nil, nil, nil, nil + atomic.StoreInt32(&h.rpcAllowedFlag, 0) + atomic.StoreInt32(&h.wsAllowedFlag, 0) + return nil +} + +// rpcAllowed returns true when JSON-RPC over HTTP is enabled. +func (h *httpServer) rpcAllowed() bool { + return atomic.LoadInt32(&h.rpcAllowedFlag) == 1 +} + +// wsAllowed returns true when JSON-RPC over WebSocket is enabled. +func (h *httpServer) wsAllowed() bool { + return atomic.LoadInt32(&h.wsAllowedFlag) == 1 +} + +func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h.wsAllowed() && isWebsocket(r) { + h.wsHandler.ServeHTTP(w, r) + return + } + if r.RequestURI == "/" { + if h.rpcAllowed() { + h.httpHandler.ServeHTTP(w, r) + } else { + w.WriteHeader(404) + } + } else { + h.mux.ServeHTTP(w, r) + } +} + +// isWebsocket checks the header of an http request for a websocket upgrade request. +func isWebsocket(r *http.Request) bool { + return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && + strings.ToLower(r.Header.Get("Connection")) == "upgrade" +} + // NewHTTPHandlerStack returns wrapped http-related handlers func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { // Wrap the CORS-handler within a host-handler @@ -231,22 +379,47 @@ func newGzipHandler(next http.Handler) http.Handler { }) } -// NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade -// request to the websocket protocol. If not, serves the the request with the http handler. -func (hs *httpServer) NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if atomic.LoadInt32(&hs.WSAllowed) == 1 && isWebsocket(r) { - ws.ServeHTTP(w, r) - log.Debug("serving websocket request") - return - } +type ipcServer struct { + log log.Logger + endpoint string - h.ServeHTTP(w, r) - }) + mu sync.Mutex + listener net.Listener + srv *rpc.Server + apis []rpc.API } -// isWebsocket checks the header of an http request for a websocket upgrade request. -func isWebsocket(r *http.Request) bool { - return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && - strings.ToLower(r.Header.Get("Connection")) == "upgrade" +func newIPCServer(log log.Logger, endpoint string) *ipcServer { + return &ipcServer{log: log, endpoint: endpoint} +} + +// Start starts the httpServer's http.Server +func (is *ipcServer) start() error { + is.mu.Lock() + defer is.mu.Unlock() + + if is.listener != nil { + return nil // already running + } + listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, is.apis) + if err != nil { + return err + } + is.log.Info("IPC endpoint opened", "url", is.endpoint) + is.listener, is.srv = listener, srv + return nil +} + +func (is *ipcServer) stop() error { + is.mu.Lock() + defer is.mu.Unlock() + + if is.listener == nil { + return nil // not running + } + err := is.listener.Close() + is.srv.Stop() + is.listener, is.srv = nil, nil + is.log.Info("IPC endpoint closed", "url", is.endpoint) + return err } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index c00c4d665896..cd2dbf63b5f9 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -2,68 +2,89 @@ package node import ( "fmt" - "net/http" - "net/http/httptest" - "sync/atomic" + "strings" "testing" + "github.com/ethereum/go-ethereum/internal/testlog" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" ) -func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { - h := &httpServer{ - Srv: rpc.NewServer(), - WSAllowed: 1, - } - handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) - ts := httptest.NewServer(handler) - defer ts.Close() - - responses := make(chan *http.Response) - go func(responses chan *http.Response) { - client := &http.Client{} - - req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) - req.Header.Set("Connection", "upgrade") - req.Header.Set("Upgrade", "websocket") - req.Header.Set("Sec-WebSocket-Version", "13") - req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") - - resp, err := client.Do(req) - if err != nil { - t.Fatalf("could not issue a GET request to the test http server %v", err) - } - responses <- resp - }(responses) - - response := <-responses - assert.Equal(t, "websocket", response.Header.Get("Upgrade")) -} +// func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { +// h := &httpServer{ +// Srv: rpc.NewServer(), +// WSAllowed: 1, +// } +// handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) +// ts := httptest.NewServer(handler) +// defer ts.Close() +// +// responses := make(chan *http.Response) +// go func(responses chan *http.Response) { +// client := &http.Client{} +// +// req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) +// req.Header.Set("Connection", "upgrade") +// req.Header.Set("Upgrade", "websocket") +// req.Header.Set("Sec-WebSocket-Version", "13") +// req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") +// +// resp, err := client.Do(req) +// if err != nil { +// t.Fatalf("could not issue a GET request to the test http server %v", err) +// } +// responses <- resp +// }(responses) +// +// response := <-responses +// assert.Equal(t, "websocket", response.Header.Get("Upgrade")) +// } // Tests that a ws handler can be added to and enabled on an existing HTTPServer func TestWSAllowed(t *testing.T) { stack, err := New(&Config{ - HTTPHost: DefaultHTTPHost, - HTTPPort: 9393, - WSHost: DefaultHTTPHost, - WSPort: 9393, + HTTPHost: "127.0.0.1", + HTTPPort: 0, + WSHost: "127.0.0.1", + WSPort: 0, + Logger: testlog.Logger(t, log.LvlDebug), }) if err != nil { t.Fatalf("could not create node: %v", err) } defer stack.Close() + // start node err = stack.Start() if err != nil { t.Fatalf("could not start node: %v", err) } - // check that server was configured on the given endpoint - server := stack.existingHTTPServer(fmt.Sprintf("%s:%d", DefaultHTTPHost, 9393)) - if server == nil { - t.Fatalf("server was not started on the given endpoint: %v", err) + + // check that HTTP works on the endpoint. + url := stack.HTTPEndpoint() + if err := checkModules(url, stack.Config().WSModules); err != nil { + t.Fatal(err) + } + + // check that WS works on the same endpoint. + wsURL := strings.Replace(url, "http://", "ws://", 1) + if err := checkModules(wsURL, stack.Config().WSModules); err != nil { + t.Fatal(err) + } +} + +func checkModules(url string, want []string) error { + c, err := rpc.Dial(url) + if err != nil { + return fmt.Errorf("can't create RPC client: %v", err) } - // assert that both RPC and WS are allowed on the HTTP Server - assert.Equal(t, atomic.LoadInt32(&server.RPCAllowed), int32(1)) - assert.Equal(t, atomic.LoadInt32(&server.WSAllowed), int32(1)) + defer c.Close() + + _, err = c.SupportedModules() + if err != nil { + return fmt.Errorf("can't get modules: %v", err) + } + + // TODO: check module list + return nil } From a1a44545810d9a46b3e7f968fc019f73e2c715b4 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:36:10 +0200 Subject: [PATCH 138/160] descriptive logging --- go.mod | 23 +++--- go.sum | 184 ++++++++++++++++++++++++++++++++++++++++++--- graphql/service.go | 13 +--- node/node.go | 5 +- node/node_test.go | 6 +- node/rpcstack.go | 29 ++++++- 6 files changed, 218 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index eaff1e2ac5e6..9d418a2d58f9 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 + github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598 github.com/aws/aws-sdk-go v1.25.48 - github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 + github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 github.com/davecgh/go-spew v1.1.1 @@ -22,12 +22,11 @@ require ( github.com/fatih/color v1.3.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-ole/go-ole v1.2.4 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.3.3 github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf - github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.4 @@ -35,10 +34,8 @@ require ( github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 - github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 + github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 - github.com/kr/pretty v0.1.0 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 github.com/naoina/go-stringutil v0.1.0 // indirect @@ -50,7 +47,7 @@ require ( github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect - github.com/shirou/gopsutil v2.20.5+incompatible + github.com/shirou/gopsutil v2.20.6+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect @@ -58,12 +55,12 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect - golang.org/x/sync v0.0.0-20190423024810-112230192c58 - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c golang.org/x/text v0.3.2 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 diff --git a/go.sum b/go.sum index d0e87bd69cc3..f00dcfaad8d7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -21,30 +22,50 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= +github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= +github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598 h1:VbwKXgO1O1JSbI8o3PQqlC/KTem5t3YD7LqvfBT+0Gk= +github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598/go.mod h1:QZe5Yh80Hp1b6JxQdpfSEEe8X7hTyTEZSosSrFf/oJE= +github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -59,41 +80,62 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87 h1:OMbqMXf9OAXzH1dDH82mQMrddBE8LIIwDtxeK4wE1/A= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= @@ -105,18 +147,32 @@ github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7 github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -133,6 +189,11 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= @@ -144,25 +205,45 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= +github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= @@ -170,9 +251,12 @@ github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9Ac github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= -github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shirou/gopsutil v2.20.6+incompatible h1:P37G9YH8M4vqkKcwBosp+URN5O8Tay67D2MbR361ioY= +github.com/shirou/gopsutil v2.20.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -181,51 +265,121 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c= +golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= @@ -233,5 +387,11 @@ gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHO gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/graphql/service.go b/graphql/service.go index 302fd678f1d9..fb573a8d78c9 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,10 +17,7 @@ package graphql import ( - "fmt" - "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" @@ -47,13 +44,9 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) h := &relay.Handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) - _ = stack.RegisterPath("/graphql/ui", GraphiQL{}) - _ = stack.RegisterPath("/graphql", handler) - endpoint := stack.RegisterPath("/graphql/", handler) + stack.RegisterPath("GraphQL UI", "/graphql/ui", GraphiQL{}) + stack.RegisterPath( "GraphQL", "/graphql", handler) + stack.RegisterPath("GraphQL", "/graphql/", handler) - if endpoint != "" { - log.Info("GraphQL configured on endpoint", "endpoint", fmt.Sprintf("http://%s/graphql", endpoint)) - log.Info("GraphQL web UI enabled", "endpoint", fmt.Sprintf("http://%s/graphql/ui", endpoint)) - } return nil } diff --git a/node/node.go b/node/node.go index d907d77d9eee..edb161a4c7a9 100644 --- a/node/node.go +++ b/node/node.go @@ -444,7 +444,7 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { } // RegisterPath mounts the given handler on the given path on the canonical HTTP server. -func (n *Node) RegisterPath(path string, handler http.Handler) string { +func (n *Node) RegisterPath(name, path string, handler http.Handler) { n.lock.Lock() defer n.lock.Unlock() @@ -452,7 +452,8 @@ func (n *Node) RegisterPath(path string, handler http.Handler) string { panic("can't register HTTP handler on running/stopped node") } n.http.mux.Handle(path, handler) - return n.http.endpoint + + n.http.handlerNames[path] = name } // Attach creates an RPC client attached to an in-process API handler. diff --git a/node/node_test.go b/node/node_test.go index f5d25360c23e..f3f3cc1f3250 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -434,8 +434,7 @@ func TestRegisterPath_Successful(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("success")) }) - endpoint := node.RegisterPath("/test", handler) - assert.Equal(t, "127.0.0.1:7878", endpoint) + node.RegisterPath("test", "/test", handler) // start node if err := node.Start(); err != nil { @@ -470,8 +469,7 @@ func TestRegisterPath_Unsuccessful(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("success")) }) - endpoint := node.RegisterPath("/test", handler) - assert.Equal(t, "", endpoint) + node.RegisterPath("test", "/test", handler) } // // Tests whether a node can successfully create and register HTTP server diff --git a/node/rpcstack.go b/node/rpcstack.go index d1541cd98650..71ed2de09837 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -90,13 +90,15 @@ type httpServer struct { port int config httpConfig + handlerNames map[string]string + // atomic flags for the handler rpcAllowedFlag int32 wsAllowedFlag int32 } func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { - return &httpServer{log: log, timeouts: timeouts} + return &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} } // setListenAddr configures the listening address of the server. @@ -202,11 +204,36 @@ func (h *httpServer) start() error { } h.listener = listener go h.server.Serve(listener) + + // if server is websocket only, return after logging + if h.wsAllowed() && !h.rpcAllowed() { + h.log.Info("Websocket enabled", + "url", fmt.Sprintf("ws://%v/", listener.Addr()), + "cors", strings.Join(h.config.CorsAllowedOrigins, ","), + "vhosts", strings.Join(h.config.Vhosts, ","), + ) + return nil + } + // log http endpoint h.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), "cors", strings.Join(h.config.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.config.Vhosts, ","), ) + // log ws endpoint + if h.wsAllowed() { + h.log.Info("Websocket enabled", + "url", fmt.Sprintf("ws://%v/", listener.Addr()), + "cors", strings.Join(h.config.CorsAllowedOrigins, ","), + "vhosts", strings.Join(h.config.Vhosts, ","), + ) + return nil + } + // log all handlers mounted on server + for path, name := range h.handlerNames { + log.Info(name + " enabled", "url", "http://" + listener.Addr().String() + path) + } + return nil } From 2d8192978b8722eb5d62cbe5a99343da4617be72 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Jul 2020 16:23:07 +0200 Subject: [PATCH 139/160] removed ws log --- node/api.go | 2 +- node/rpcstack.go | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/node/api.go b/node/api.go index a8b700df481a..11a969b8b313 100644 --- a/node/api.go +++ b/node/api.go @@ -269,7 +269,7 @@ func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str if err := server.start(); err != nil { return false, err } - api.node.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint()) + api.node.http.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint()) return true, nil } diff --git a/node/rpcstack.go b/node/rpcstack.go index 71ed2de09837..4a60a0ce75ab 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -207,28 +207,15 @@ func (h *httpServer) start() error { // if server is websocket only, return after logging if h.wsAllowed() && !h.rpcAllowed() { - h.log.Info("Websocket enabled", - "url", fmt.Sprintf("ws://%v/", listener.Addr()), - "cors", strings.Join(h.config.CorsAllowedOrigins, ","), - "vhosts", strings.Join(h.config.Vhosts, ","), - ) + h.log.Info("Websocket enabled", "url", fmt.Sprintf("ws://%v/", listener.Addr())) return nil } // log http endpoint - h.log.Info("HTTP endpoint opened", - "url", fmt.Sprintf("http://%v/", listener.Addr()), + h.log.Info("HTTP server started", + "endpoint", listener.Addr(), "cors", strings.Join(h.config.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.config.Vhosts, ","), ) - // log ws endpoint - if h.wsAllowed() { - h.log.Info("Websocket enabled", - "url", fmt.Sprintf("ws://%v/", listener.Addr()), - "cors", strings.Join(h.config.CorsAllowedOrigins, ","), - "vhosts", strings.Join(h.config.Vhosts, ","), - ) - return nil - } // log all handlers mounted on server for path, name := range h.handlerNames { log.Info(name + " enabled", "url", "http://" + listener.Addr().String() + path) From c985bd4970932506c79c137583e2a06b8c527888 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 16:23:50 +0200 Subject: [PATCH 140/160] node: add test for StartRPC API --- node/api_test.go | 277 ++++++++++++++++++++++++++++++++++++++++++ node/rpcstack_test.go | 16 +++ 2 files changed, 293 insertions(+) create mode 100644 node/api_test.go diff --git a/node/api_test.go b/node/api_test.go new file mode 100644 index 000000000000..0fe3d4faccd2 --- /dev/null +++ b/node/api_test.go @@ -0,0 +1,277 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node + +import ( + "bytes" + "io" + "net" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +// This test uses the admin_startRPC API, checking whether the HTTP server is started +// correctly. +func TestStartRPC(t *testing.T) { + type test struct { + name string + cfg Config + fn func(*testing.T, *Node, *privateAdminAPI) + + // Checks. These run after the node is configured and all API calls have been made. + wantReachable bool // whether the HTTP server should be reachable at all + wantHandlers bool // whether RegisterPath handlers should be accessible + wantRPC bool // whether JSON-RPC/HTTP should be accessible + wantWS bool // whether JSON-RPC/WS should be accessible + } + + tests := []test{ + { + name: "all off", + cfg: Config{}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, + { + + name: "rpc enabled through config", + cfg: Config{HTTPHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: false, + }, + { + name: "rpc enabled through API", + cfg: Config{}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: false, + }, + { + name: "rpc stopped through API", + cfg: Config{HTTPHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StopRPC() + assert.NoError(t, err) + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, + { + name: "ws enabled through config", + cfg: Config{WSHost: "127.0.0.1"}, + wantReachable: true, + wantHandlers: false, + wantRPC: false, + wantWS: true, + }, + { + name: "ws enabled through API", + cfg: Config{}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: false, + wantRPC: false, + wantWS: true, + }, + { + name: "ws stopped through API", + cfg: Config{WSHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StopWS() + assert.NoError(t, err) + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, + { + name: "ws enabled after RPC", + cfg: Config{HTTPHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + wsport := n.http.port + _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: true, + }, + { + name: "ws enabled after RPC then stopped", + cfg: Config{HTTPHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + wsport := n.http.port + _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) + assert.NoError(t, err) + + _, err = api.StopWS() + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: false, + }, + { + name: "rpc stopped with ws enabled", + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) + assert.NoError(t, err) + + wsport := n.http.port + _, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) + assert.NoError(t, err) + + _, err = api.StopRPC() + assert.NoError(t, err) + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, + { + name: "rpc enabled after ws", + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) + assert.NoError(t, err) + + wsport := n.http.port + _, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil) + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Apply some sane defaults. + config := test.cfg + // config.Logger = testlog.Logger(t, log.LvlDebug) + config.NoUSB = true + config.P2P.NoDiscovery = true + + // Create Node. + stack, err := New(&config) + if err != nil { + t.Fatal("can't create node:", err) + } + defer stack.Close() + + // Register the test handler. + stack.RegisterPath("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) + })) + + if err := stack.Start(); err != nil { + t.Fatal("can't start node:", err) + } + + // Run the API call hook. + if test.fn != nil { + test.fn(t, stack, &privateAdminAPI{stack}) + } + + // Check if the HTTP endpoints are available. + baseURL := stack.HTTPEndpoint() + reachable := checkReachable(baseURL) + handlersAvailable := checkBodyOK(baseURL + "/test") + rpcAvailable := checkModules(baseURL, nil) == nil + wsAvailable := checkModules(strings.Replace(baseURL, "http://", "ws://", 1), nil) == nil + if reachable != test.wantReachable { + t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) + } + if handlersAvailable != test.wantHandlers { + t.Errorf("RegisterPath handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers)) + } + if rpcAvailable != test.wantRPC { + t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC)) + } + if wsAvailable != test.wantWS { + t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS)) + } + }) + } +} + +func checkReachable(rawurl string) bool { + u, err := url.Parse(rawurl) + if err != nil { + panic(err) + } + conn, err := net.Dial("tcp", u.Host) + if err != nil { + return false + } + conn.Close() + return true +} + +func checkBodyOK(url string) bool { + resp, err := http.Get(url) + if err != nil { + return false + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return false + } + buf := make([]byte, 2) + if _, err = io.ReadFull(resp.Body, buf); err != nil { + return false + } + return bytes.Equal(buf, []byte("OK")) +} + +func sp(s string) *string { return &s } +func ip(i int) *int { return &i } + +func not(ok bool) string { + if ok { + return "" + } + return "not " +} diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index cd2dbf63b5f9..3ca7015da55d 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -1,3 +1,19 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package node import ( From f2dbf7e7aae58c83c7e43f159ee4dc900794bb8e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 16:26:46 +0200 Subject: [PATCH 141/160] node: fix test --- node/api_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/api_test.go b/node/api_test.go index 0fe3d4faccd2..718af1ecbf49 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -201,7 +201,7 @@ func TestStartRPC(t *testing.T) { defer stack.Close() // Register the test handler. - stack.RegisterPath("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + stack.RegisterPath("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) })) From 47315c0abf2d8b4c89107861d66e2f2efb99086e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 18:20:07 +0200 Subject: [PATCH 142/160] node: delete redundant websocket enabling test --- node/api_test.go | 24 +++++++++++++++--- node/rpcstack_test.go | 59 ------------------------------------------- 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/node/api_test.go b/node/api_test.go index 718af1ecbf49..d0f5620f7add 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -25,11 +25,12 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" ) -// This test uses the admin_startRPC API, checking whether the HTTP server is started -// correctly. +// This test uses the admin_startRPC and admin_startWS APIs, +// checking whether the HTTP server is started correctly. func TestStartRPC(t *testing.T) { type test struct { name string @@ -218,8 +219,8 @@ func TestStartRPC(t *testing.T) { baseURL := stack.HTTPEndpoint() reachable := checkReachable(baseURL) handlersAvailable := checkBodyOK(baseURL + "/test") - rpcAvailable := checkModules(baseURL, nil) == nil - wsAvailable := checkModules(strings.Replace(baseURL, "http://", "ws://", 1), nil) == nil + rpcAvailable := checkModules(baseURL) + wsAvailable := checkModules(strings.Replace(baseURL, "http://", "ws://", 1)) if reachable != test.wantReachable { t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) } @@ -236,6 +237,7 @@ func TestStartRPC(t *testing.T) { } } +// checkReachable checks if the TCP endpoint in rawurl is open. func checkReachable(rawurl string) bool { u, err := url.Parse(rawurl) if err != nil { @@ -249,6 +251,7 @@ func checkReachable(rawurl string) bool { return true } +// checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK". func checkBodyOK(url string) bool { resp, err := http.Get(url) if err != nil { @@ -266,6 +269,19 @@ func checkBodyOK(url string) bool { return bytes.Equal(buf, []byte("OK")) } +// checkModules checks whether JSON-RPC works against the given URL. +func checkModules(url string) bool { + c, err := rpc.Dial(url) + if err != nil { + return false + } + defer c.Close() + + _, err = c.SupportedModules() + return err == nil +} + +// string/int pointer helpers. func sp(s string) *string { return &s } func ip(i int) *int { return &i } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 3ca7015da55d..009c613d3e96 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -16,16 +16,6 @@ package node -import ( - "fmt" - "strings" - "testing" - - "github.com/ethereum/go-ethereum/internal/testlog" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" -) - // func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { // h := &httpServer{ // Srv: rpc.NewServer(), @@ -55,52 +45,3 @@ import ( // response := <-responses // assert.Equal(t, "websocket", response.Header.Get("Upgrade")) // } - -// Tests that a ws handler can be added to and enabled on an existing HTTPServer -func TestWSAllowed(t *testing.T) { - stack, err := New(&Config{ - HTTPHost: "127.0.0.1", - HTTPPort: 0, - WSHost: "127.0.0.1", - WSPort: 0, - Logger: testlog.Logger(t, log.LvlDebug), - }) - if err != nil { - t.Fatalf("could not create node: %v", err) - } - defer stack.Close() - - // start node - err = stack.Start() - if err != nil { - t.Fatalf("could not start node: %v", err) - } - - // check that HTTP works on the endpoint. - url := stack.HTTPEndpoint() - if err := checkModules(url, stack.Config().WSModules); err != nil { - t.Fatal(err) - } - - // check that WS works on the same endpoint. - wsURL := strings.Replace(url, "http://", "ws://", 1) - if err := checkModules(wsURL, stack.Config().WSModules); err != nil { - t.Fatal(err) - } -} - -func checkModules(url string, want []string) error { - c, err := rpc.Dial(url) - if err != nil { - return fmt.Errorf("can't create RPC client: %v", err) - } - defer c.Close() - - _, err = c.SupportedModules() - if err != nil { - return fmt.Errorf("can't get modules: %v", err) - } - - // TODO: check module list - return nil -} From 9a1131ce1102d1628eb0319673df688d00db306f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 19:14:54 +0200 Subject: [PATCH 143/160] node: fix all the HTTP server API tests --- node/api.go | 4 +- node/api_test.go | 31 +++++++++++- node/rpcstack.go | 129 ++++++++++++++++++++++++++++------------------- 3 files changed, 107 insertions(+), 57 deletions(-) diff --git a/node/api.go b/node/api.go index 11a969b8b313..083784f4e4dd 100644 --- a/node/api.go +++ b/node/api.go @@ -275,9 +275,7 @@ func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str // StopWS terminates all WebSocket servers. func (api *privateAdminAPI) StopWS() (bool, error) { - if api.node.http.wsAllowed() { - api.node.http.disableWS() - } + api.node.http.stopWS() api.node.ws.stop() return true, nil } diff --git a/node/api_test.go b/node/api_test.go index d0f5620f7add..5e0cde7f050c 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -56,7 +56,6 @@ func TestStartRPC(t *testing.T) { wantWS: false, }, { - name: "rpc enabled through config", cfg: Config{HTTPHost: "127.0.0.1"}, fn: func(t *testing.T, n *Node, api *privateAdminAPI) { @@ -90,6 +89,21 @@ func TestStartRPC(t *testing.T) { wantRPC: false, wantWS: false, }, + { + name: "rpc stopped twice", + cfg: Config{HTTPHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StopRPC() + assert.NoError(t, err) + + _, err = api.StopRPC() + assert.NoError(t, err) + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, { name: "ws enabled through config", cfg: Config{WSHost: "127.0.0.1"}, @@ -122,6 +136,21 @@ func TestStartRPC(t *testing.T) { wantRPC: false, wantWS: false, }, + { + name: "ws stopped twice", + cfg: Config{WSHost: "127.0.0.1"}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + _, err := api.StopWS() + assert.NoError(t, err) + + _, err = api.StopWS() + assert.NoError(t, err) + }, + wantReachable: false, + wantHandlers: false, + wantRPC: false, + wantWS: false, + }, { name: "ws enabled after RPC", cfg: Config{HTTPHost: "127.0.0.1"}, diff --git a/node/rpcstack.go b/node/rpcstack.go index 4a60a0ce75ab..8fbabb66a312 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -77,28 +77,32 @@ type httpServer struct { server *http.Server listener net.Listener // non-nil when server is running + // HTTP RPC handler things. httpConfig httpConfig - httpRPC *rpc.Server - httpHandler http.Handler + httpHandler atomic.Value // *rpcHandler + // WebSocket handler things. wsConfig wsConfig - wsRPC *rpc.Server - wsHandler http.Handler + wsHandler atomic.Value // *rpcHandler + // These are set by setListenAddr. endpoint string host string port int - config httpConfig handlerNames map[string]string +} - // atomic flags for the handler - rpcAllowedFlag int32 - wsAllowedFlag int32 +type rpcHandler struct { + http.Handler + server *rpc.Server } func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { - return &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} + h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} + h.httpHandler.Store((*rpcHandler)(nil)) + h.wsHandler.Store((*rpcHandler)(nil)) + return h } // setListenAddr configures the listening address of the server. @@ -132,19 +136,20 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { h.mu.Lock() defer h.mu.Unlock() - if h.httpRPC != nil { + if h.rpcAllowed() { return fmt.Errorf("JSON-RPC over HTTP is already enabled") } - // Create RPC server. + // Create RPC server and handler. srv := rpc.NewServer() - if err := RegisterApisFromWhitelist(apis, h.httpConfig.Modules, srv, false); err != nil { + if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { return err } - // Create handler. - h.httpRPC = srv - h.httpHandler = NewHTTPHandlerStack(h.httpRPC, config.CorsAllowedOrigins, config.Vhosts) - atomic.StoreInt32(&h.rpcAllowedFlag, 1) + h.httpConfig = config + h.httpHandler.Store(&rpcHandler{ + Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts), + server: srv, + }) return nil } @@ -153,30 +158,37 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { h.mu.Lock() defer h.mu.Unlock() - if h.wsRPC != nil { + if h.wsAllowed() { return fmt.Errorf("JSON-RPC over WebSocket is already enabled") } - // Create RPC server. + // Create RPC server and handler. srv := rpc.NewServer() - if err := RegisterApisFromWhitelist(apis, h.wsConfig.Modules, srv, false); err != nil { + if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { return err } - // Create handler. - h.wsRPC = rpc.NewServer() - h.wsHandler = h.wsRPC.WebsocketHandler(config.Origins) - atomic.StoreInt32(&h.wsAllowedFlag, 1) + h.wsConfig = config + h.wsHandler.Store(&rpcHandler{ + Handler: srv.WebsocketHandler(config.Origins), + server: srv, + }) return nil } -// disableWS disables JSON-RPC over WebSocket. -func (h *httpServer) disableWS() { +// stopWS disables JSON-RPC over WebSocket. +func (h *httpServer) stopWS() { h.mu.Lock() defer h.mu.Unlock() - atomic.StoreInt32(&h.wsAllowedFlag, 0) - h.wsRPC.Stop() - h.wsRPC = nil + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil { + h.wsHandler.Store((*rpcHandler)(nil)) + ws.server.Stop() + // If this server only served WebSocket, stop the HTTP server as well. + if !h.rpcAllowed() { + h.doStop() + } + } } // start starts the HTTP server if it is enabled and not already running. @@ -213,69 +225,80 @@ func (h *httpServer) start() error { // log http endpoint h.log.Info("HTTP server started", "endpoint", listener.Addr(), - "cors", strings.Join(h.config.CorsAllowedOrigins, ","), - "vhosts", strings.Join(h.config.Vhosts, ","), + "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), + "vhosts", strings.Join(h.httpConfig.Vhosts, ","), ) // log all handlers mounted on server for path, name := range h.handlerNames { - log.Info(name + " enabled", "url", "http://" + listener.Addr().String() + path) + log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) } return nil } // stop shuts down the HTTP server. -func (h *httpServer) stop() error { +func (h *httpServer) stop() { h.mu.Lock() defer h.mu.Unlock() + h.doStop() +} + +func (h *httpServer) doStop() { if h.listener == nil { - return nil // not running + return // not running } // Shut down the server. - if h.httpRPC != nil { - h.httpRPC.Stop() + httpHandler := h.httpHandler.Load().(*rpcHandler) + wsHandler := h.httpHandler.Load().(*rpcHandler) + if httpHandler != nil { + h.httpHandler.Store((*rpcHandler)(nil)) + httpHandler.server.Stop() } - if h.wsRPC != nil { - h.wsRPC.Stop() + if wsHandler != nil { + h.wsHandler.Store((*rpcHandler)(nil)) + wsHandler.server.Stop() } h.server.Shutdown(context.Background()) h.listener.Close() - h.log.Info("HTTP endpoint closed", "url", h.endpoint) + h.log.Info("HTTP server stopped", "endpoint", h.listener.Addr()) // Clear out everything to allow re-configuring it later. h.host, h.port, h.endpoint = "", 0, "" h.server, h.listener = nil, nil - h.httpRPC, h.httpHandler, h.wsRPC, h.wsHandler = nil, nil, nil, nil - atomic.StoreInt32(&h.rpcAllowedFlag, 0) - atomic.StoreInt32(&h.wsAllowedFlag, 0) - return nil } // rpcAllowed returns true when JSON-RPC over HTTP is enabled. func (h *httpServer) rpcAllowed() bool { - return atomic.LoadInt32(&h.rpcAllowedFlag) == 1 + return h.httpHandler.Load().(*rpcHandler) != nil } // wsAllowed returns true when JSON-RPC over WebSocket is enabled. func (h *httpServer) wsAllowed() bool { - return atomic.LoadInt32(&h.wsAllowedFlag) == 1 + return h.wsHandler.Load().(*rpcHandler) != nil } func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if h.wsAllowed() && isWebsocket(r) { - h.wsHandler.ServeHTTP(w, r) - return - } + rpc := h.httpHandler.Load().(*rpcHandler) if r.RequestURI == "/" { - if h.rpcAllowed() { - h.httpHandler.ServeHTTP(w, r) - } else { - w.WriteHeader(404) + // Serve JSON-RPC on the root path. + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil && isWebsocket(r) { + ws.ServeHTTP(w, r) + return } - } else { + if rpc != nil { + rpc.ServeHTTP(w, r) + return + } + } else if rpc != nil { + // Requests to a path below root are handled by the mux, + // which has all the handlers registered via Node.RegisterPath. + // These are made available when RPC is enabled. h.mux.ServeHTTP(w, r) + return } + w.WriteHeader(404) } // isWebsocket checks the header of an http request for a websocket upgrade request. From 6d544c6b4b91a3a1deeca0894ff0bad18e6e1187 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 19:17:09 +0200 Subject: [PATCH 144/160] node: improve comments --- node/rpcstack.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 8fbabb66a312..9e92e9beb3bf 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -219,16 +219,16 @@ func (h *httpServer) start() error { // if server is websocket only, return after logging if h.wsAllowed() && !h.rpcAllowed() { - h.log.Info("Websocket enabled", "url", fmt.Sprintf("ws://%v/", listener.Addr())) + h.log.Info("WebSocket enabled", "url", fmt.Sprintf("ws://%v", listener.Addr())) return nil } - // log http endpoint + // Log http endpoint. h.log.Info("HTTP server started", "endpoint", listener.Addr(), "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.httpConfig.Vhosts, ","), ) - // log all handlers mounted on server + // Log all handlers mounted on server. for path, name := range h.handlerNames { log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) } From d7c12580a8091dd26b3044728204f1aab8c8863e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 27 Jul 2020 19:27:13 +0200 Subject: [PATCH 145/160] node: fix starting RPC after it failed to listen --- node/api_test.go | 28 ++++++++++++++++++++++++++++ node/rpcstack.go | 29 +++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/node/api_test.go b/node/api_test.go index 5e0cde7f050c..74aa40a930a5 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -77,6 +77,34 @@ func TestStartRPC(t *testing.T) { wantRPC: true, wantWS: false, }, + { + name: "rpc start again after failure", + cfg: Config{}, + fn: func(t *testing.T, n *Node, api *privateAdminAPI) { + // Listen on a random port. + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal("can't listen:", err) + } + defer listener.Close() + port := listener.Addr().(*net.TCPAddr).Port + + // Now try to start RPC on that port. This should fail. + _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) + if err == nil { + t.Fatal("StartRPC should have failed on port", port) + } + + // Try again after unblocking the port. It should work this time. + listener.Close() + _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) + assert.NoError(t, err) + }, + wantReachable: true, + wantHandlers: true, + wantRPC: true, + wantWS: false, + }, { name: "rpc stopped through API", cfg: Config{HTTPHost: "127.0.0.1"}, diff --git a/node/rpcstack.go b/node/rpcstack.go index 9e92e9beb3bf..6a572beabf9e 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -153,6 +153,16 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { return nil } +// disableRPC stops the HTTP RPC handler. The caller must hold h.mu. +func (h *httpServer) disableRPC() bool { + handler := h.httpHandler.Load().(*rpcHandler) + if handler != nil { + h.httpHandler.Store((*rpcHandler)(nil)) + handler.server.Stop() + } + return handler != nil +} + // enableWS turns on JSON-RPC over WebSocket on the server. func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { h.mu.Lock() @@ -180,10 +190,7 @@ func (h *httpServer) stopWS() { h.mu.Lock() defer h.mu.Unlock() - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil { - h.wsHandler.Store((*rpcHandler)(nil)) - ws.server.Stop() + if h.disableWS() { // If this server only served WebSocket, stop the HTTP server as well. if !h.rpcAllowed() { h.doStop() @@ -191,6 +198,16 @@ func (h *httpServer) stopWS() { } } +// disableWS stops the WebSocket handler. The caller must hold h.mu. +func (h *httpServer) disableWS() bool { + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil { + h.wsHandler.Store((*rpcHandler)(nil)) + ws.server.Stop() + } + return ws != nil +} + // start starts the HTTP server if it is enabled and not already running. func (h *httpServer) start() error { h.mu.Lock() @@ -212,6 +229,10 @@ func (h *httpServer) start() error { // Start the server. listener, err := net.Listen("tcp", h.endpoint) if err != nil { + // If the server fails to start, we need to clear out the RPC and WS + // configuration so they can be configured another time. + h.disableRPC() + h.disableWS() return err } h.listener = listener From b8b1046dd394ba1aa2f9f99c670ee6db5fca03ce Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Jul 2020 20:02:21 +0200 Subject: [PATCH 146/160] changed node tests --- node/api_test.go | 8 +-- node/node_test.go | 147 ++++++++++++------------------------------ node/rpcstack_test.go | 42 ++++-------- 3 files changed, 58 insertions(+), 139 deletions(-) diff --git a/node/api_test.go b/node/api_test.go index 74aa40a930a5..a576b2387557 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -276,8 +276,8 @@ func TestStartRPC(t *testing.T) { baseURL := stack.HTTPEndpoint() reachable := checkReachable(baseURL) handlersAvailable := checkBodyOK(baseURL + "/test") - rpcAvailable := checkModules(baseURL) - wsAvailable := checkModules(strings.Replace(baseURL, "http://", "ws://", 1)) + rpcAvailable := checkRPC(baseURL) + wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1)) if reachable != test.wantReachable { t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) } @@ -326,8 +326,8 @@ func checkBodyOK(url string) bool { return bytes.Equal(buf, []byte("OK")) } -// checkModules checks whether JSON-RPC works against the given URL. -func checkModules(url string) bool { +// checkRPC checks whether JSON-RPC works against the given URL. +func checkRPC(url string) bool { c, err := rpc.Dial(url) if err != nil { return false diff --git a/node/node_test.go b/node/node_test.go index f3f3cc1f3250..e7fbe8f835e6 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -18,11 +18,14 @@ package node import ( "errors" + "fmt" "io" "io/ioutil" + "net" "net/http" "os" "reflect" + "strings" "testing" "github.com/ethereum/go-ethereum/crypto" @@ -348,7 +351,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { failer := &InstrumentedService{stop: failure} stack.RegisterLifecycle(failer) - // Start the protocol stack, and ensure that a failing shut down terminates all // TODO, deleting loop because constructors no longer stored on node. + // Start the protocol stack, and ensure that a failing shut down terminates all // Start the stack and make sure all is online if err := stack.Start(); err != nil { t.Fatalf("failed to start protocol stack: %v", err) @@ -386,45 +389,6 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { stack.server.PrivateKey = testNodeKey } -// // Tests whether a given httpServer can be registered on the node -// func TestRegisterHTTPServer(t *testing.T) { -// stack, err := New(testNodeConfig()) -// if err != nil { -// t.Fatalf("failed to create protocol stack: %v", err) -// } -// defer stack.Close() -// -// srv1 := &httpServer{ -// host: "test1", -// port: 0001, -// } -// endpoint1 := fmt.Sprintf("%s:%d", srv1.host, srv1.port) -// stack.registerHTTPServer(endpoint1, srv1) -// -// srv2 := &httpServer{ -// host: "test2", -// port: 0002, -// } -// endpoint2 := fmt.Sprintf("%s:%d", srv2.host, srv2.port) -// stack.registerHTTPServer(endpoint2, srv2) -// -// noop := &httpServer{ -// host: "test", -// port: 0000, -// } -// endpointNoop := fmt.Sprintf("%s:%d", noop.host, noop.port) -// -// if srv1 != stack.existingHTTPServer(endpoint1) { -// t.Fatalf("server %v was not properly registered on the given endpoint %s", srv1, endpoint1) -// } -// if srv2 != stack.existingHTTPServer(endpoint2) { -// t.Fatalf("server %v was not properly registered on the given endpoint %s", srv2, endpoint2) -// } -// if noop == stack.existingHTTPServer(endpointNoop) { -// t.Fatalf("server %v was incorrectly registered on the given endpoint %s", noop, endpointNoop) -// } -// } - // Tests whether a handler can be successfully mounted on the canonical HTTP server // on the given path func TestRegisterPath_Successful(t *testing.T) { @@ -472,83 +436,54 @@ func TestRegisterPath_Unsuccessful(t *testing.T) { node.RegisterPath("test", "/test", handler) } -// // Tests whether a node can successfully create and register HTTP server -// // lifecycles on the node. -// func TestHTTPServerCreateAndStop(t *testing.T) { -// // test on same ports -// node1 := startHTTP(t, 7453, 7453) -// if len(node1.httpServers) != 1 { -// t.Fatalf("node has more than 1 http server") -// } -// // check to make sure http servers are registered -// if !containsLifecycle(node1.lifecycles, &node1.httpServers) { -// t.Fatal("HTTP servers not registered as lifecycles on the node") -// } -// // check to make sure http servers are configured properly -// for _, server := range node1.httpServers { -// if atomic.LoadInt32(&server.WSAllowed) == 0 && atomic.LoadInt32(&server.RPCAllowed) == 0 { -// t.Fatalf("node's http server is not configured to handle both rpc and ws") -// } -// node1.stopServer(server) -// if node1.existingHTTPServer(server.endpoint) != nil { -// t.Fatalf("failed to remove server %v from node after stopping it", server) -// } -// } -// node1.Close() -// -// // test on separate ports -// node2 := startHTTP(t, 7453, 9393) -// if len(node2.httpServers) != 2 { -// t.Fatalf("amount of http servers on the node is not equal to 2") -// } -// // check to make sure http servers are registered -// if !containsLifecycle(node2.lifecycles, &node2.httpServers) { -// t.Fatal("HTTP servers not registered as lifecycles on the node") -// } -// // check that neither http server has both ws and rpc enabled -// for _, server := range node2.httpServers { -// if atomic.LoadInt32(&server.WSAllowed) == 1 && atomic.LoadInt32(&server.RPCAllowed) == 1 { -// t.Fatalf("both rpc and ws allowed on a single http server") -// } -// node2.stopServer(server) -// if node2.existingHTTPServer(server.endpoint) != nil { -// t.Fatalf("failed to remove server %v from node after stopping it", server) -// } -// } -// node2.Close() -// } - // Tests whether websocket requests can be handled on the same port as a regular http server. func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { - node := startHTTP(t, 7453, 7453) + node := startHTTP(t, 0, 0) defer node.Close() - wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) - if err != nil { - t.Error("could not issue new http request ", err) - } - wsReq.Header.Set("Connection", "upgrade") - wsReq.Header.Set("Upgrade", "websocket") - wsReq.Header.Set("Sec-WebSocket-Version", "13") - wsReq.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") + ws := strings.Replace(node.HTTPEndpoint() , "http://", "ws://", 1) - resp := doHTTPRequest(t, wsReq) - assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) + if node.WSEndpoint() != ws { + t.Fatalf("endpoints should be the same") + } + if !checkRPC(ws) { + t.Fatalf("ws request failed") + } + if !checkRPC(node.HTTPEndpoint()) { + t.Fatalf("http request failed") + } } -// Tests whether http requests can be handled successfully. -func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { - node := startHTTP(t, 7453, 7453) +func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { + // try and get a free port + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal("can't listen:", err) + } + port := listener.Addr().(*net.TCPAddr).Port + listener.Close() + + node := startHTTP(t, 0, port) defer node.Close() - httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) - if err != nil { - t.Error("could not issue new http request ", err) + wsOnHTTP := strings.Replace(node.HTTPEndpoint() , "http://", "ws://", 1) + ws := fmt.Sprintf("ws://127.0.0.1:%d", port) + + if node.WSEndpoint() == wsOnHTTP { + t.Fatalf("endpoints should not be the same") + } + // ensure ws endpoint matches the expected endpoint + if node.WSEndpoint() != ws { + t.Fatalf("ws endpoint is incorrect: expected %s, got %s", ws, node.WSEndpoint()) + } + + if !checkRPC(ws) { + t.Fatalf("ws request failed") + } + if !checkRPC(node.HTTPEndpoint()) { + t.Fatalf("http request failed") } - httpReq.Header.Set("Accept-Encoding", "gzip") - resp := doHTTPRequest(t, httpReq) - assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) } func createNode(t *testing.T, httpPort, wsPort int) *Node { diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 009c613d3e96..4b09aa7460e4 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -16,32 +16,16 @@ package node -// func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { -// h := &httpServer{ -// Srv: rpc.NewServer(), -// WSAllowed: 1, -// } -// handler := h.NewWebsocketUpgradeHandler(nil, h.Srv.WebsocketHandler([]string{})) -// ts := httptest.NewServer(handler) -// defer ts.Close() -// -// responses := make(chan *http.Response) -// go func(responses chan *http.Response) { -// client := &http.Client{} -// -// req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) -// req.Header.Set("Connection", "upgrade") -// req.Header.Set("Upgrade", "websocket") -// req.Header.Set("Sec-WebSocket-Version", "13") -// req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") -// -// resp, err := client.Do(req) -// if err != nil { -// t.Fatalf("could not issue a GET request to the test http server %v", err) -// } -// responses <- resp -// }(responses) -// -// response := <-responses -// assert.Equal(t, "websocket", response.Header.Get("Upgrade")) -// } +import ( + "github.com/ethereum/go-ethereum/internal/testlog" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "testing" +) + +func TestCorsHandler(t *testing.T) { + srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) + +} + + From 858096248446c6edc99700ac03e0d78d4bdd38e6 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Mon, 27 Jul 2020 20:25:16 +0200 Subject: [PATCH 147/160] rpcstack tests --- node/node_test.go | 2 +- node/rpcstack_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index e7fbe8f835e6..c78f5e7b063b 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -511,7 +511,7 @@ func startHTTP(t *testing.T, httpPort, wsPort int) *Node { } func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { - client := &http.Client{} + client := http.DefaultClient resp, err := client.Do(req) if err != nil { t.Fatalf("could not issue a GET request to the given endpoint: %v", err) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 4b09aa7460e4..9b4af6135e29 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -17,15 +17,58 @@ package node import ( + "bytes" "github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/assert" + "net/http" "testing" ) func TestCorsHandler(t *testing.T) { + // TODO put this in separate func srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) - + assert.NoError(t, srv.enableRPC(nil, httpConfig{ + CorsAllowedOrigins: []string{"test", "test.com"}, + })) + assert.NoError(t, srv.setListenAddr("localhost", 0)) + assert.NoError(t, srv.start()) + defer srv.stop() + + resp := testRequest(t, "test.com", srv) + if resp.Header.Get("Access-Control-Allow-Origin") != "test.com" { + t.Fatalf("cors not recognized") + } + resp2 := testRequest(t, "bad", srv) + if resp2.Header.Get("Access-Control-Allow-Origin") != ""{ + t.Fatalf("cors not properly set, bad cors recognized") + } +} + +func testRequest(t *testing.T, origin string, srv *httpServer) *http.Response { + t.Helper() + + body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`)) + req, _ := http.NewRequest("POST", "http://" + srv.listenAddr(), body) + req.Header.Set("content-type", "application/json") + req.Header.Set("origin", origin) + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + + return resp +} + +func TestVhosts(t *testing.T) { + // TODO +} + +func TestWebsocketOrigins(t *testing.T) { + // TODO } From 68cc90c5ecd3ac17693cba2483f0d7c5ddb7d9e1 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Jul 2020 18:19:16 +0200 Subject: [PATCH 148/160] linted and new tests --- go.mod | 5 ++- go.sum | 6 +++ graphql/service.go | 2 +- node/node_test.go | 4 +- node/rpcstack_test.go | 95 ++++++++++++++++++++++++++++++------------- 5 files changed, 79 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 9d418a2d58f9..f2cba91c9765 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,8 @@ require ( github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 + github.com/reiver/go-oi v1.0.0 + github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect @@ -51,12 +53,13 @@ require ( github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect + github.com/streadway/amqp v1.0.0 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect + golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c golang.org/x/text v0.3.2 diff --git a/go.sum b/go.sum index f00dcfaad8d7..39f6419b99a1 100644 --- a/go.sum +++ b/go.sum @@ -244,6 +244,10 @@ github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+G github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM= +github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas= +github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= @@ -264,6 +268,8 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/graphql/service.go b/graphql/service.go index fb573a8d78c9..60aeca37e38b 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -45,7 +45,7 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) handler := node.NewHTTPHandlerStack(h, cors, vhosts) stack.RegisterPath("GraphQL UI", "/graphql/ui", GraphiQL{}) - stack.RegisterPath( "GraphQL", "/graphql", handler) + stack.RegisterPath("GraphQL", "/graphql", handler) stack.RegisterPath("GraphQL", "/graphql/", handler) return nil diff --git a/node/node_test.go b/node/node_test.go index c78f5e7b063b..92575fb76c20 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -441,7 +441,7 @@ func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { node := startHTTP(t, 0, 0) defer node.Close() - ws := strings.Replace(node.HTTPEndpoint() , "http://", "ws://", 1) + ws := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1) if node.WSEndpoint() != ws { t.Fatalf("endpoints should be the same") @@ -466,7 +466,7 @@ func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { node := startHTTP(t, 0, port) defer node.Close() - wsOnHTTP := strings.Replace(node.HTTPEndpoint() , "http://", "ws://", 1) + wsOnHTTP := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1) ws := fmt.Sprintf("ws://127.0.0.1:%d", port) if node.WSEndpoint() == wsOnHTTP { diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 9b4af6135e29..ad0deea82b8b 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -18,57 +18,94 @@ package node import ( "bytes" + "net/http" + "testing" + "github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + + "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "net/http" - "testing" ) +// TestCorsHandler makes sure CORS are properly handled on the http server. func TestCorsHandler(t *testing.T) { - // TODO put this in separate func + srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{}) + defer srv.stop() + + resp := testRequest(t, "origin", "test.com", "", srv) + assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin")) + + resp2 := testRequest(t, "origin", "bad", "", srv) + assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin")) +} + +// TestVhosts makes sure vhosts are properly handled on the http server. +func TestVhosts(t *testing.T) { + srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{}) + defer srv.stop() + + resp := testRequest(t, "", "", "test", srv) + assert.Equal(t, resp.StatusCode, http.StatusOK) + + resp2 := testRequest(t, "", "", "bad", srv) + assert.Equal(t, resp2.StatusCode, http.StatusForbidden) +} + +// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. +func TestWebsocketOrigins(t *testing.T) { + srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: []string{"test"}}) + defer srv.stop() + + dialer := websocket.DefaultDialer + _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ + "Content-type": []string{"application/json"}, + "Sec-WebSocket-Version": []string{"13"}, + "Origin": []string{"test"}, + }) + assert.NoError(t, err) + + _, _, err = dialer.Dial("ws://"+srv.listenAddr(), http.Header{ + "Content-type": []string{"application/json"}, + "Sec-WebSocket-Version": []string{"13"}, + "Origin": []string{"bad"}, + }) + assert.Error(t, err) +} + +func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer { + t.Helper() + srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) - assert.NoError(t, srv.enableRPC(nil, httpConfig{ - CorsAllowedOrigins: []string{"test", "test.com"}, - })) + + assert.NoError(t, srv.enableRPC(nil, conf)) + if ws { + assert.NoError(t, srv.enableWS(nil, wsConf)) + } assert.NoError(t, srv.setListenAddr("localhost", 0)) assert.NoError(t, srv.start()) - defer srv.stop() - resp := testRequest(t, "test.com", srv) - if resp.Header.Get("Access-Control-Allow-Origin") != "test.com" { - t.Fatalf("cors not recognized") - } - resp2 := testRequest(t, "bad", srv) - if resp2.Header.Get("Access-Control-Allow-Origin") != ""{ - t.Fatalf("cors not properly set, bad cors recognized") - } + return srv } -func testRequest(t *testing.T, origin string, srv *httpServer) *http.Response { +func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response { t.Helper() body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`)) - req, _ := http.NewRequest("POST", "http://" + srv.listenAddr(), body) + req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body) req.Header.Set("content-type", "application/json") - req.Header.Set("origin", origin) + if key != "" && value != "" { + req.Header.Set(key, value) + } + if host != "" { + req.Host = host + } client := http.DefaultClient resp, err := client.Do(req) if err != nil { t.Fatal(err) } - return resp } - -func TestVhosts(t *testing.T) { - // TODO -} - -func TestWebsocketOrigins(t *testing.T) { - // TODO -} - - From 917135fc01c7630dc782ec745440d8ea0e549b91 Mon Sep 17 00:00:00 2001 From: renaynay <41963722+renaynay@users.noreply.github.com> Date: Tue, 28 Jul 2020 18:24:11 +0200 Subject: [PATCH 149/160] reshuffling some funcs in rpcstack --- node/rpcstack.go | 234 +++++++++++++++++++++++------------------------ 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 6a572beabf9e..0f4529c4a4bc 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -33,28 +33,6 @@ import ( "github.com/rs/cors" ) -// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, -// and then registers all of the APIs exposed by the services. -func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { - if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { - log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) - } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := srv.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - } - } - return nil -} - // httpConfig is the JSON-RPC/HTTP configuration. type httpConfig struct { Modules []string @@ -68,6 +46,11 @@ type wsConfig struct { Modules []string } +type rpcHandler struct { + http.Handler + server *rpc.Server +} + type httpServer struct { log log.Logger timeouts rpc.HTTPTimeouts @@ -93,11 +76,6 @@ type httpServer struct { handlerNames map[string]string } -type rpcHandler struct { - http.Handler - server *rpc.Server -} - func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} h.httpHandler.Store((*rpcHandler)(nil)) @@ -131,73 +109,6 @@ func (h *httpServer) listenAddr() string { return h.endpoint } -// enableRPC turns on JSON-RPC over HTTP on the server. -func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { - h.mu.Lock() - defer h.mu.Unlock() - - if h.rpcAllowed() { - return fmt.Errorf("JSON-RPC over HTTP is already enabled") - } - - // Create RPC server and handler. - srv := rpc.NewServer() - if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { - return err - } - h.httpConfig = config - h.httpHandler.Store(&rpcHandler{ - Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts), - server: srv, - }) - return nil -} - -// disableRPC stops the HTTP RPC handler. The caller must hold h.mu. -func (h *httpServer) disableRPC() bool { - handler := h.httpHandler.Load().(*rpcHandler) - if handler != nil { - h.httpHandler.Store((*rpcHandler)(nil)) - handler.server.Stop() - } - return handler != nil -} - -// enableWS turns on JSON-RPC over WebSocket on the server. -func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { - h.mu.Lock() - defer h.mu.Unlock() - - if h.wsAllowed() { - return fmt.Errorf("JSON-RPC over WebSocket is already enabled") - } - - // Create RPC server and handler. - srv := rpc.NewServer() - if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { - return err - } - h.wsConfig = config - h.wsHandler.Store(&rpcHandler{ - Handler: srv.WebsocketHandler(config.Origins), - server: srv, - }) - return nil -} - -// stopWS disables JSON-RPC over WebSocket. -func (h *httpServer) stopWS() { - h.mu.Lock() - defer h.mu.Unlock() - - if h.disableWS() { - // If this server only served WebSocket, stop the HTTP server as well. - if !h.rpcAllowed() { - h.doStop() - } - } -} - // disableWS stops the WebSocket handler. The caller must hold h.mu. func (h *httpServer) disableWS() bool { ws := h.wsHandler.Load().(*rpcHandler) @@ -257,6 +168,29 @@ func (h *httpServer) start() error { return nil } +func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + rpc := h.httpHandler.Load().(*rpcHandler) + if r.RequestURI == "/" { + // Serve JSON-RPC on the root path. + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil && isWebsocket(r) { + ws.ServeHTTP(w, r) + return + } + if rpc != nil { + rpc.ServeHTTP(w, r) + return + } + } else if rpc != nil { + // Requests to a path below root are handled by the mux, + // which has all the handlers registered via Node.RegisterPath. + // These are made available when RPC is enabled. + h.mux.ServeHTTP(w, r) + return + } + w.WriteHeader(404) +} + // stop shuts down the HTTP server. func (h *httpServer) stop() { h.mu.Lock() @@ -289,6 +223,73 @@ func (h *httpServer) doStop() { h.server, h.listener = nil, nil } +// enableRPC turns on JSON-RPC over HTTP on the server. +func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.rpcAllowed() { + return fmt.Errorf("JSON-RPC over HTTP is already enabled") + } + + // Create RPC server and handler. + srv := rpc.NewServer() + if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { + return err + } + h.httpConfig = config + h.httpHandler.Store(&rpcHandler{ + Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts), + server: srv, + }) + return nil +} + +// disableRPC stops the HTTP RPC handler. The caller must hold h.mu. +func (h *httpServer) disableRPC() bool { + handler := h.httpHandler.Load().(*rpcHandler) + if handler != nil { + h.httpHandler.Store((*rpcHandler)(nil)) + handler.server.Stop() + } + return handler != nil +} + +// enableWS turns on JSON-RPC over WebSocket on the server. +func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.wsAllowed() { + return fmt.Errorf("JSON-RPC over WebSocket is already enabled") + } + + // Create RPC server and handler. + srv := rpc.NewServer() + if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { + return err + } + h.wsConfig = config + h.wsHandler.Store(&rpcHandler{ + Handler: srv.WebsocketHandler(config.Origins), + server: srv, + }) + return nil +} + +// stopWS disables JSON-RPC over WebSocket. +func (h *httpServer) stopWS() { + h.mu.Lock() + defer h.mu.Unlock() + + if h.disableWS() { + // If this server only served WebSocket, stop the HTTP server as well. + if !h.rpcAllowed() { + h.doStop() + } + } +} + // rpcAllowed returns true when JSON-RPC over HTTP is enabled. func (h *httpServer) rpcAllowed() bool { return h.httpHandler.Load().(*rpcHandler) != nil @@ -299,29 +300,6 @@ func (h *httpServer) wsAllowed() bool { return h.wsHandler.Load().(*rpcHandler) != nil } -func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - rpc := h.httpHandler.Load().(*rpcHandler) - if r.RequestURI == "/" { - // Serve JSON-RPC on the root path. - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil && isWebsocket(r) { - ws.ServeHTTP(w, r) - return - } - if rpc != nil { - rpc.ServeHTTP(w, r) - return - } - } else if rpc != nil { - // Requests to a path below root are handled by the mux, - // which has all the handlers registered via Node.RegisterPath. - // These are made available when RPC is enabled. - h.mux.ServeHTTP(w, r) - return - } - w.WriteHeader(404) -} - // isWebsocket checks the header of an http request for a websocket upgrade request. func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && @@ -481,3 +459,25 @@ func (is *ipcServer) stop() error { is.log.Info("IPC endpoint closed", "url", is.endpoint) return err } + +// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, +// and then registers all of the APIs exposed by the services. +func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { + if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { + log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) + } + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := srv.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + } + return nil +} From 3664dd5efce06c1cd5e9e4995079d899860e914f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 11:39:03 +0200 Subject: [PATCH 150/160] node: move disableWS down as well --- node/rpcstack.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 0f4529c4a4bc..b47ddc871b23 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -109,16 +109,6 @@ func (h *httpServer) listenAddr() string { return h.endpoint } -// disableWS stops the WebSocket handler. The caller must hold h.mu. -func (h *httpServer) disableWS() bool { - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil { - h.wsHandler.Store((*rpcHandler)(nil)) - ws.server.Stop() - } - return ws != nil -} - // start starts the HTTP server if it is enabled and not already running. func (h *httpServer) start() error { h.mu.Lock() @@ -277,13 +267,22 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { return nil } -// stopWS disables JSON-RPC over WebSocket. +// disableWS stops the WebSocket handler. The caller must hold h.mu. +func (h *httpServer) disableWS() bool { + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil { + h.wsHandler.Store((*rpcHandler)(nil)) + ws.server.Stop() + } + return ws != nil +} + +// stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket. func (h *httpServer) stopWS() { h.mu.Lock() defer h.mu.Unlock() if h.disableWS() { - // If this server only served WebSocket, stop the HTTP server as well. if !h.rpcAllowed() { h.doStop() } From de11bb1a4ca5866e5726add24221d23cf172b4e5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 11:41:00 +0200 Subject: [PATCH 151/160] node: improve comments --- node/rpcstack.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index b47ddc871b23..80a91e4bd7e8 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -235,7 +235,7 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { return nil } -// disableRPC stops the HTTP RPC handler. The caller must hold h.mu. +// disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu. func (h *httpServer) disableRPC() bool { handler := h.httpHandler.Load().(*rpcHandler) if handler != nil { @@ -267,16 +267,6 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { return nil } -// disableWS stops the WebSocket handler. The caller must hold h.mu. -func (h *httpServer) disableWS() bool { - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil { - h.wsHandler.Store((*rpcHandler)(nil)) - ws.server.Stop() - } - return ws != nil -} - // stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket. func (h *httpServer) stopWS() { h.mu.Lock() @@ -289,6 +279,16 @@ func (h *httpServer) stopWS() { } } +// disableWS disables the WebSocket handler. This is internal, the caller must hold h.mu. +func (h *httpServer) disableWS() bool { + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil { + h.wsHandler.Store((*rpcHandler)(nil)) + ws.server.Stop() + } + return ws != nil +} + // rpcAllowed returns true when JSON-RPC over HTTP is enabled. func (h *httpServer) rpcAllowed() bool { return h.httpHandler.Load().(*rpcHandler) != nil From 98cf053b77f707ee7a5ee7cb2ebd40c1e8b95d23 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 11:50:39 +0200 Subject: [PATCH 152/160] go.mod: revert dependency updates --- go.mod | 32 ++++----- go.sum | 202 +++++---------------------------------------------------- 2 files changed, 33 insertions(+), 201 deletions(-) diff --git a/go.mod b/go.mod index f2cba91c9765..2299eb501753 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect - github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598 + github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 github.com/aws/aws-sdk-go v1.25.48 - github.com/btcsuite/btcd v0.20.1-beta + github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 github.com/davecgh/go-spew v1.1.1 @@ -22,11 +22,12 @@ require ( github.com/fatih/color v1.3.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-ole/go-ole v1.2.4 // indirect + github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 - github.com/golang/protobuf v1.3.3 - github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf + github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c + github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 + github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.4 @@ -34,8 +35,10 @@ require ( github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 - github.com/julienschmidt/httprouter v1.2.0 + github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 + github.com/kr/pretty v0.1.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 github.com/naoina/go-stringutil v0.1.0 // indirect @@ -44,26 +47,23 @@ require ( github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 - github.com/reiver/go-oi v1.0.0 - github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect - github.com/shirou/gopsutil v2.20.6+incompatible + github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect - github.com/streadway/amqp v1.0.0 github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 - golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/text v0.3.2 - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 diff --git a/go.sum b/go.sum index 39f6419b99a1..4c46eeb5afb0 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -22,50 +21,30 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= -github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= -github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598 h1:VbwKXgO1O1JSbI8o3PQqlC/KTem5t3YD7LqvfBT+0Gk= -github.com/aristanetworks/goarista v0.0.0-20200609010056-95bcf8053598/go.mod h1:QZe5Yh80Hp1b6JxQdpfSEEe8X7hTyTEZSosSrFf/oJE= -github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -80,62 +59,40 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87 h1:OMbqMXf9OAXzH1dDH82mQMrddBE8LIIwDtxeK4wE1/A= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c h1:zqAKixg3cTcIasAMJV+EcfVbWwLpOZ7LeoWJvcuD/5Q= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf h1:gFVkHXmVAhEbxZVDln5V9GKrLaluNoFHDbrZwAWZgws= -github.com/golang/snappy v0.0.2-0.20190904063534-ff6b7dc882cf/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= +github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= @@ -147,32 +104,18 @@ github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7 github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -189,11 +132,6 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= @@ -205,49 +143,25 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= -github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.10/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM= -github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI= -github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas= -github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= @@ -255,12 +169,9 @@ github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9Ac github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shirou/gopsutil v2.20.6+incompatible h1:P37G9YH8M4vqkKcwBosp+URN5O8Tay67D2MbR361ioY= -github.com/shirou/gopsutil v2.20.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -268,124 +179,51 @@ github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= -github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= -github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= -github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= -github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= -github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= -github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c= -golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= @@ -393,11 +231,5 @@ gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHO gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From b265e2ced02803bfa68f16969b5ce47b386346ea Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 11:52:12 +0200 Subject: [PATCH 153/160] eth: fix compile issue in backend.go --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index fa03f547e167..3fd027137c7f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -162,7 +162,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, - TrieCleanJournal: ctx.ResolvePath(config.TrieCleanCacheJournal), + TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), TrieCleanRejournal: config.TrieCleanCacheRejournal, TrieCleanNoPrefetch: config.NoPrefetch, TrieDirtyLimit: config.TrieDirtyCache, From 1c639bd6d7fa797787c247f7a179fd248ee4039a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 12:03:19 +0200 Subject: [PATCH 154/160] node: fix IPC API registration --- node/node.go | 2 +- node/rpcstack.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index edb161a4c7a9..400589b35ad3 100644 --- a/node/node.go +++ b/node/node.go @@ -332,7 +332,7 @@ func (n *Node) startRPC() error { // Configure IPC. if n.ipc.endpoint != "" { - if err := n.ipc.start(); err != nil { + if err := n.ipc.start(n.rpcAPIs); err != nil { return err } } diff --git a/node/rpcstack.go b/node/rpcstack.go index 80a91e4bd7e8..554421d87aed 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -421,7 +421,6 @@ type ipcServer struct { mu sync.Mutex listener net.Listener srv *rpc.Server - apis []rpc.API } func newIPCServer(log log.Logger, endpoint string) *ipcServer { @@ -429,14 +428,14 @@ func newIPCServer(log log.Logger, endpoint string) *ipcServer { } // Start starts the httpServer's http.Server -func (is *ipcServer) start() error { +func (is *ipcServer) start(apis []rpc.API) error { is.mu.Lock() defer is.mu.Unlock() if is.listener != nil { return nil // already running } - listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, is.apis) + listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis) if err != nil { return err } From e11bc43a10b1b5d3c2fa2e8466b8c492b22aea8a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 12:07:26 +0200 Subject: [PATCH 155/160] graphql: fix test --- graphql/graphql_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 091102889144..5ba9c955375d 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -86,8 +86,8 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { t.Fatalf("could not read from response body: %v", err) } // make sure the request is not handled successfully - expected := "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32600,\"message\":\"invalid request\"}}\n" - assert.Equal(t, string(bodyBytes), expected) + assert.Equal(t, 404, resp.StatusCode) + assert.Equal(t, "404 page not found\n", string(bodyBytes)) } func createNode(t *testing.T, gqlEnabled bool) *node.Node { From f1b1b333a07dd79db4f4d7e61e93eb8bf4c42721 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 12:21:33 +0200 Subject: [PATCH 156/160] node: remove blank line --- node/rpcstack_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index ad0deea82b8b..efab5b37dc5a 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" - "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" ) From 3001cf53f87989c2562d26c3b32059c49371cab4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 12:39:41 +0200 Subject: [PATCH 157/160] node: sort+dedup logged HTTP handlers --- node/node.go | 1 - node/rpcstack.go | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/node/node.go b/node/node.go index 400589b35ad3..63d32adff44c 100644 --- a/node/node.go +++ b/node/node.go @@ -452,7 +452,6 @@ func (n *Node) RegisterPath(name, path string, handler http.Handler) { panic("can't register HTTP handler on running/stopped node") } n.http.mux.Handle(path, handler) - n.http.handlerNames[path] = name } diff --git a/node/rpcstack.go b/node/rpcstack.go index 554421d87aed..1a6f2666c657 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "net" "net/http" + "sort" "strings" "sync" "sync/atomic" @@ -150,11 +151,21 @@ func (h *httpServer) start() error { "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.httpConfig.Vhosts, ","), ) + // Log all handlers mounted on server. - for path, name := range h.handlerNames { - log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) + var paths []string + for path, _ := range h.handlerNames { + paths = append(paths, path) + } + sort.Strings(paths) + logged := make(map[string]bool, len(paths)) + for _, path := range paths { + name := h.handlerNames[path] + if !logged[name] { + log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) + logged[name] = true + } } - return nil } @@ -321,8 +332,8 @@ func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { c := cors.New(cors.Options{ AllowedOrigins: allowedOrigins, AllowedMethods: []string{http.MethodPost, http.MethodGet}, - MaxAge: 600, AllowedHeaders: []string{"*"}, + MaxAge: 600, }) return c.Handler(srv) } From 4d42e9f043e000e5e65d8dba4da359820c849813 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 14:09:43 +0200 Subject: [PATCH 158/160] node: fix lint issue --- node/rpcstack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 1a6f2666c657..047de65dca55 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -154,7 +154,7 @@ func (h *httpServer) start() error { // Log all handlers mounted on server. var paths []string - for path, _ := range h.handlerNames { + for path := range h.handlerNames { paths = append(paths, path) } sort.Strings(paths) From 2b5bd5ee2759b7b286c0f935982e8d078a68e8ed Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 15:01:42 +0200 Subject: [PATCH 159/160] node: rename RegisterPath -> RegisterHandler I'm not happy with 'path' for this because we also have ResolvePath to find file paths in the datadir. --- graphql/service.go | 6 +++--- node/api_test.go | 6 +++--- node/node.go | 4 ++-- node/node_test.go | 8 ++++---- node/rpcstack.go | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index 60aeca37e38b..ae962e5b365a 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -44,9 +44,9 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) h := &relay.Handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) - stack.RegisterPath("GraphQL UI", "/graphql/ui", GraphiQL{}) - stack.RegisterPath("GraphQL", "/graphql", handler) - stack.RegisterPath("GraphQL", "/graphql/", handler) + stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{}) + stack.RegisterHandler("GraphQL", "/graphql", handler) + stack.RegisterHandler("GraphQL", "/graphql/", handler) return nil } diff --git a/node/api_test.go b/node/api_test.go index a576b2387557..e4c08962c3a3 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -39,7 +39,7 @@ func TestStartRPC(t *testing.T) { // Checks. These run after the node is configured and all API calls have been made. wantReachable bool // whether the HTTP server should be reachable at all - wantHandlers bool // whether RegisterPath handlers should be accessible + wantHandlers bool // whether RegisterHandler handlers should be accessible wantRPC bool // whether JSON-RPC/HTTP should be accessible wantWS bool // whether JSON-RPC/WS should be accessible } @@ -259,7 +259,7 @@ func TestStartRPC(t *testing.T) { defer stack.Close() // Register the test handler. - stack.RegisterPath("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) })) @@ -282,7 +282,7 @@ func TestStartRPC(t *testing.T) { t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) } if handlersAvailable != test.wantHandlers { - t.Errorf("RegisterPath handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers)) + t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers)) } if rpcAvailable != test.wantRPC { t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC)) diff --git a/node/node.go b/node/node.go index 63d32adff44c..7ab2520b1b8b 100644 --- a/node/node.go +++ b/node/node.go @@ -443,8 +443,8 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { n.rpcAPIs = append(n.rpcAPIs, apis...) } -// RegisterPath mounts the given handler on the given path on the canonical HTTP server. -func (n *Node) RegisterPath(name, path string, handler http.Handler) { +// RegisterHandler mounts a handler on the given path on the canonical HTTP server. +func (n *Node) RegisterHandler(name, path string, handler http.Handler) { n.lock.Lock() defer n.lock.Unlock() diff --git a/node/node_test.go b/node/node_test.go index 92575fb76c20..8f306ef0219d 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -391,14 +391,14 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { // Tests whether a handler can be successfully mounted on the canonical HTTP server // on the given path -func TestRegisterPath_Successful(t *testing.T) { +func TestRegisterHandler_Successful(t *testing.T) { node := createNode(t, 7878, 7979) // create and mount handler handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("success")) }) - node.RegisterPath("test", "/test", handler) + node.RegisterHandler("test", "/test", handler) // start node if err := node.Start(); err != nil { @@ -423,7 +423,7 @@ func TestRegisterPath_Successful(t *testing.T) { // Tests that the given handler will not be successfully mounted since no HTTP server // is enabled for RPC -func TestRegisterPath_Unsuccessful(t *testing.T) { +func TestRegisterHandler_Unsuccessful(t *testing.T) { node, err := New(&DefaultConfig) if err != nil { t.Fatalf("could not create new node: %v", err) @@ -433,7 +433,7 @@ func TestRegisterPath_Unsuccessful(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("success")) }) - node.RegisterPath("test", "/test", handler) + node.RegisterHandler("test", "/test", handler) } // Tests whether websocket requests can be handled on the same port as a regular http server. diff --git a/node/rpcstack.go b/node/rpcstack.go index 047de65dca55..caf7e5b7a83d 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -184,7 +184,7 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } else if rpc != nil { // Requests to a path below root are handled by the mux, - // which has all the handlers registered via Node.RegisterPath. + // which has all the handlers registered via Node.RegisterHandler. // These are made available when RPC is enabled. h.mux.ServeHTTP(w, r) return From 74e2aa93a71477536865c9fd7f09ceea8e90346e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Aug 2020 15:04:50 +0200 Subject: [PATCH 160/160] node: explain RegisterHandler 'name' parameter in docs --- node/node.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/node.go b/node/node.go index 7ab2520b1b8b..c66ebb89d016 100644 --- a/node/node.go +++ b/node/node.go @@ -444,6 +444,9 @@ func (n *Node) RegisterAPIs(apis []rpc.API) { } // RegisterHandler mounts a handler on the given path on the canonical HTTP server. +// +// The name of the handler is shown in a log message when the HTTP server starts +// and should be a descriptive term for the service provided by the handler. func (n *Node) RegisterHandler(name, path string, handler http.Handler) { n.lock.Lock() defer n.lock.Unlock()