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