aboutsummaryrefslogtreecommitdiff
path: root/node/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'node/config.go')
-rw-r--r--node/config.go545
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
+}