diff options
Diffstat (limited to 'node/config.go')
-rw-r--r-- | node/config.go | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/node/config.go b/node/config.go new file mode 100644 index 0000000..1905ac7 --- /dev/null +++ b/node/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 <http://www.gnu.org/licenses/>. + +package node + +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 +} |