github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/node/config.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package node 13 14 import ( 15 "crypto/ecdsa" 16 "fmt" 17 "io/ioutil" 18 "os" 19 "path/filepath" 20 "runtime" 21 "strings" 22 23 "github.com/Sberex/go-sberex/accounts" 24 "github.com/Sberex/go-sberex/accounts/keystore" 25 "github.com/Sberex/go-sberex/accounts/usbwallet" 26 "github.com/Sberex/go-sberex/common" 27 "github.com/Sberex/go-sberex/crypto" 28 "github.com/Sberex/go-sberex/log" 29 "github.com/Sberex/go-sberex/p2p" 30 "github.com/Sberex/go-sberex/p2p/discover" 31 ) 32 33 const ( 34 datadirPrivateKey = "nodekey" // Path within the datadir to the node's private key 35 datadirDefaultKeyStore = "keystore" // Path within the datadir to the keystore 36 datadirStaticNodes = "static-nodes.json" // Path within the datadir to the static node list 37 datadirTrustedNodes = "trusted-nodes.json" // Path within the datadir to the trusted node list 38 datadirNodeDatabase = "nodes" // Path within the datadir to store the node infos 39 ) 40 41 // Config represents a small collection of configuration values to fine tune the 42 // P2P network layer of a protocol stack. These values can be further extended by 43 // all registered services. 44 type Config struct { 45 // Name sets the instance name of the node. It must not contain the / character and is 46 // used in the devp2p node identifier. The instance name of geth is "geth". If no 47 // value is specified, the basename of the current executable is used. 48 Name string `toml:"-"` 49 50 // UserIdent, if set, is used as an additional component in the devp2p node identifier. 51 UserIdent string `toml:",omitempty"` 52 53 // Version should be set to the version number of the program. It is used 54 // in the devp2p node identifier. 55 Version string `toml:"-"` 56 57 // DataDir is the file system folder the node should use for any data storage 58 // requirements. The configured data directory will not be directly shared with 59 // registered services, instead those can use utility methods to create/access 60 // databases or flat files. This enables ephemeral nodes which can fully reside 61 // in memory. 62 DataDir string 63 64 // Configuration of peer-to-peer networking. 65 P2P p2p.Config 66 67 // KeyStoreDir is the file system folder that contains private keys. The directory can 68 // be specified as a relative path, in which case it is resolved relative to the 69 // current directory. 70 // 71 // If KeyStoreDir is empty, the default location is the "keystore" subdirectory of 72 // DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory 73 // is created by New and destroyed when the node is stopped. 74 KeyStoreDir string `toml:",omitempty"` 75 76 // UseLightweightKDF lowers the memory and CPU requirements of the key store 77 // scrypt KDF at the expense of security. 78 UseLightweightKDF bool `toml:",omitempty"` 79 80 // NoUSB disables hardware wallet monitoring and connectivity. 81 NoUSB bool `toml:",omitempty"` 82 83 // IPCPath is the requested location to place the IPC endpoint. If the path is 84 // a simple file name, it is placed inside the data directory (or on the root 85 // pipe path on Windows), whereas if it's a resolvable path name (absolute or 86 // relative), then that specific path is enforced. An empty path disables IPC. 87 IPCPath string `toml:",omitempty"` 88 89 // HTTPHost is the host interface on which to start the HTTP RPC server. If this 90 // field is empty, no HTTP API endpoint will be started. 91 HTTPHost string `toml:",omitempty"` 92 93 // HTTPPort is the TCP port number on which to start the HTTP RPC server. The 94 // default zero value is/ valid and will pick a port number randomly (useful 95 // for ephemeral nodes). 96 HTTPPort int `toml:",omitempty"` 97 98 // HTTPCors is the Cross-Origin Resource Sharing header to send to requesting 99 // clients. Please be aware that CORS is a browser enforced security, it's fully 100 // useless for custom HTTP clients. 101 HTTPCors []string `toml:",omitempty"` 102 103 // HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests. 104 // This is by default {'localhost'}. Using this prevents attacks like 105 // DNS rebinding, which bypasses SOP by simply masquerading as being within the same 106 // origin. These attacks do not utilize CORS, since they are not cross-domain. 107 // By explicitly checking the Host-header, the server will not allow requests 108 // made against the server with a malicious host domain. 109 // Requests using ip address directly are not affected 110 HTTPVirtualHosts []string `toml:",omitempty"` 111 112 // HTTPModules is a list of API modules to expose via the HTTP RPC interface. 113 // If the module list is empty, all RPC API endpoints designated public will be 114 // exposed. 115 HTTPModules []string `toml:",omitempty"` 116 117 // WSHost is the host interface on which to start the websocket RPC server. If 118 // this field is empty, no websocket API endpoint will be started. 119 WSHost string `toml:",omitempty"` 120 121 // WSPort is the TCP port number on which to start the websocket RPC server. The 122 // default zero value is/ valid and will pick a port number randomly (useful for 123 // ephemeral nodes). 124 WSPort int `toml:",omitempty"` 125 126 // WSOrigins is the list of domain to accept websocket requests from. Please be 127 // aware that the server can only act upon the HTTP request the client sends and 128 // cannot verify the validity of the request header. 129 WSOrigins []string `toml:",omitempty"` 130 131 // WSModules is a list of API modules to expose via the websocket RPC interface. 132 // If the module list is empty, all RPC API endpoints designated public will be 133 // exposed. 134 WSModules []string `toml:",omitempty"` 135 136 // WSExposeAll exposes all API modules via the WebSocket RPC interface rather 137 // than just the public ones. 138 // 139 // *WARNING* Only set this if the node is running in a trusted network, exposing 140 // private APIs to untrusted users is a major security risk. 141 WSExposeAll bool `toml:",omitempty"` 142 143 // Logger is a custom logger to use with the p2p.Server. 144 Logger log.Logger `toml:",omitempty"` 145 } 146 147 // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into 148 // account the set data folders as well as the designated platform we're currently 149 // running on. 150 func (c *Config) IPCEndpoint() string { 151 // Short circuit if IPC has not been enabled 152 if c.IPCPath == "" { 153 return "" 154 } 155 // On windows we can only use plain top-level pipes 156 if runtime.GOOS == "windows" { 157 if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) { 158 return c.IPCPath 159 } 160 return `\\.\pipe\` + c.IPCPath 161 } 162 // Resolve names into the data directory full paths otherwise 163 if filepath.Base(c.IPCPath) == c.IPCPath { 164 if c.DataDir == "" { 165 return filepath.Join(os.TempDir(), c.IPCPath) 166 } 167 return filepath.Join(c.DataDir, c.IPCPath) 168 } 169 return c.IPCPath 170 } 171 172 // NodeDB returns the path to the discovery node database. 173 func (c *Config) NodeDB() string { 174 if c.DataDir == "" { 175 return "" // ephemeral 176 } 177 return c.resolvePath(datadirNodeDatabase) 178 } 179 180 // DefaultIPCEndpoint returns the IPC path used by default. 181 func DefaultIPCEndpoint(clientIdentifier string) string { 182 if clientIdentifier == "" { 183 clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") 184 if clientIdentifier == "" { 185 panic("empty executable name") 186 } 187 } 188 config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"} 189 return config.IPCEndpoint() 190 } 191 192 // HTTPEndpoint resolves an HTTP endpoint based on the configured host interface 193 // and port parameters. 194 func (c *Config) HTTPEndpoint() string { 195 if c.HTTPHost == "" { 196 return "" 197 } 198 return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) 199 } 200 201 // DefaultHTTPEndpoint returns the HTTP endpoint used by default. 202 func DefaultHTTPEndpoint() string { 203 config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort} 204 return config.HTTPEndpoint() 205 } 206 207 // WSEndpoint resolves an websocket endpoint based on the configured host interface 208 // and port parameters. 209 func (c *Config) WSEndpoint() string { 210 if c.WSHost == "" { 211 return "" 212 } 213 return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) 214 } 215 216 // DefaultWSEndpoint returns the websocket endpoint used by default. 217 func DefaultWSEndpoint() string { 218 config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort} 219 return config.WSEndpoint() 220 } 221 222 // NodeName returns the devp2p node identifier. 223 func (c *Config) NodeName() string { 224 name := c.name() 225 // Backwards compatibility: previous versions used title-cased "Geth", keep that. 226 if name == "geth" || name == "geth-testnet" { 227 name = "Geth" 228 } 229 if c.UserIdent != "" { 230 name += "/" + c.UserIdent 231 } 232 if c.Version != "" { 233 name += "/v" + c.Version 234 } 235 name += "/" + runtime.GOOS + "-" + runtime.GOARCH 236 name += "/" + runtime.Version() 237 return name 238 } 239 240 func (c *Config) name() string { 241 if c.Name == "" { 242 progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") 243 if progname == "" { 244 panic("empty executable name, set Config.Name") 245 } 246 return progname 247 } 248 return c.Name 249 } 250 251 // These resources are resolved differently for "geth" instances. 252 var isOldGethResource = map[string]bool{ 253 "chaindata": true, 254 "nodes": true, 255 "nodekey": true, 256 "static-nodes.json": true, 257 "trusted-nodes.json": true, 258 } 259 260 // resolvePath resolves path in the instance directory. 261 func (c *Config) resolvePath(path string) string { 262 if filepath.IsAbs(path) { 263 return path 264 } 265 if c.DataDir == "" { 266 return "" 267 } 268 // Backwards-compatibility: ensure that data directory files created 269 // by geth 1.4 are used if they exist. 270 if c.name() == "geth" && isOldGethResource[path] { 271 oldpath := "" 272 if c.Name == "geth" { 273 oldpath = filepath.Join(c.DataDir, path) 274 } 275 if oldpath != "" && common.FileExist(oldpath) { 276 // TODO: print warning 277 return oldpath 278 } 279 } 280 return filepath.Join(c.instanceDir(), path) 281 } 282 283 func (c *Config) instanceDir() string { 284 if c.DataDir == "" { 285 return "" 286 } 287 return filepath.Join(c.DataDir, c.name()) 288 } 289 290 // NodeKey retrieves the currently configured private key of the node, checking 291 // first any manually set key, falling back to the one found in the configured 292 // data folder. If no key can be found, a new one is generated. 293 func (c *Config) NodeKey() *ecdsa.PrivateKey { 294 // Use any specifically configured key. 295 if c.P2P.PrivateKey != nil { 296 return c.P2P.PrivateKey 297 } 298 // Generate ephemeral key if no datadir is being used. 299 if c.DataDir == "" { 300 key, err := crypto.GenerateKey() 301 if err != nil { 302 log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err)) 303 } 304 return key 305 } 306 307 keyfile := c.resolvePath(datadirPrivateKey) 308 if key, err := crypto.LoadECDSA(keyfile); err == nil { 309 return key 310 } 311 // No persistent key found, generate and store a new one. 312 key, err := crypto.GenerateKey() 313 if err != nil { 314 log.Crit(fmt.Sprintf("Failed to generate node key: %v", err)) 315 } 316 instanceDir := filepath.Join(c.DataDir, c.name()) 317 if err := os.MkdirAll(instanceDir, 0700); err != nil { 318 log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) 319 return key 320 } 321 keyfile = filepath.Join(instanceDir, datadirPrivateKey) 322 if err := crypto.SaveECDSA(keyfile, key); err != nil { 323 log.Error(fmt.Sprintf("Failed to persist node key: %v", err)) 324 } 325 return key 326 } 327 328 // StaticNodes returns a list of node enode URLs configured as static nodes. 329 func (c *Config) StaticNodes() []*discover.Node { 330 return c.parsePersistentNodes(c.resolvePath(datadirStaticNodes)) 331 } 332 333 // TrustedNodes returns a list of node enode URLs configured as trusted nodes. 334 func (c *Config) TrustedNodes() []*discover.Node { 335 return c.parsePersistentNodes(c.resolvePath(datadirTrustedNodes)) 336 } 337 338 // parsePersistentNodes parses a list of discovery node URLs loaded from a .json 339 // file from within the data directory. 340 func (c *Config) parsePersistentNodes(path string) []*discover.Node { 341 // Short circuit if no node config is present 342 if c.DataDir == "" { 343 return nil 344 } 345 if _, err := os.Stat(path); err != nil { 346 return nil 347 } 348 // Load the nodes from the config file. 349 var nodelist []string 350 if err := common.LoadJSON(path, &nodelist); err != nil { 351 log.Error(fmt.Sprintf("Can't load node file %s: %v", path, err)) 352 return nil 353 } 354 // Interpret the list as a discovery node array 355 var nodes []*discover.Node 356 for _, url := range nodelist { 357 if url == "" { 358 continue 359 } 360 node, err := discover.ParseNode(url) 361 if err != nil { 362 log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err)) 363 continue 364 } 365 nodes = append(nodes, node) 366 } 367 return nodes 368 } 369 370 // AccountConfig determines the settings for scrypt and keydirectory 371 func (c *Config) AccountConfig() (int, int, string, error) { 372 scryptN := keystore.StandardScryptN 373 scryptP := keystore.StandardScryptP 374 if c.UseLightweightKDF { 375 scryptN = keystore.LightScryptN 376 scryptP = keystore.LightScryptP 377 } 378 379 var ( 380 keydir string 381 err error 382 ) 383 switch { 384 case filepath.IsAbs(c.KeyStoreDir): 385 keydir = c.KeyStoreDir 386 case c.DataDir != "": 387 if c.KeyStoreDir == "" { 388 keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore) 389 } else { 390 keydir, err = filepath.Abs(c.KeyStoreDir) 391 } 392 case c.KeyStoreDir != "": 393 keydir, err = filepath.Abs(c.KeyStoreDir) 394 } 395 return scryptN, scryptP, keydir, err 396 } 397 398 func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { 399 scryptN, scryptP, keydir, err := conf.AccountConfig() 400 var ephemeral string 401 if keydir == "" { 402 // There is no datadir. 403 keydir, err = ioutil.TempDir("", "go-sberex-keystore") 404 ephemeral = keydir 405 } 406 407 if err != nil { 408 return nil, "", err 409 } 410 if err := os.MkdirAll(keydir, 0700); err != nil { 411 return nil, "", err 412 } 413 // Assemble the account manager and supported backends 414 backends := []accounts.Backend{ 415 keystore.NewKeyStore(keydir, scryptN, scryptP), 416 } 417 if !conf.NoUSB { 418 // Start a USB hub for Ledger hardware wallets 419 if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { 420 log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) 421 } else { 422 backends = append(backends, ledgerhub) 423 } 424 // Start a USB hub for Trezor hardware wallets 425 if trezorhub, err := usbwallet.NewTrezorHub(); err != nil { 426 log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err)) 427 } else { 428 backends = append(backends, trezorhub) 429 } 430 } 431 return accounts.NewManager(backends...), ephemeral, nil 432 }