github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/cmd/swarm/main.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 main 13 14 import ( 15 "crypto/ecdsa" 16 "fmt" 17 "io/ioutil" 18 "os" 19 "os/signal" 20 "runtime" 21 "sort" 22 "strconv" 23 "strings" 24 "syscall" 25 26 "github.com/Sberex/go-sberex/accounts" 27 "github.com/Sberex/go-sberex/accounts/keystore" 28 "github.com/Sberex/go-sberex/cmd/utils" 29 "github.com/Sberex/go-sberex/common" 30 "github.com/Sberex/go-sberex/console" 31 "github.com/Sberex/go-sberex/crypto" 32 "github.com/Sberex/go-sberex/ethclient" 33 "github.com/Sberex/go-sberex/internal/debug" 34 "github.com/Sberex/go-sberex/log" 35 "github.com/Sberex/go-sberex/node" 36 "github.com/Sberex/go-sberex/p2p" 37 "github.com/Sberex/go-sberex/p2p/discover" 38 "github.com/Sberex/go-sberex/params" 39 "github.com/Sberex/go-sberex/swarm" 40 bzzapi "github.com/Sberex/go-sberex/swarm/api" 41 swarmmetrics "github.com/Sberex/go-sberex/swarm/metrics" 42 43 "gopkg.in/urfave/cli.v1" 44 ) 45 46 const clientIdentifier = "swarm" 47 48 var ( 49 gitCommit string // Git SHA1 commit hash of the release (set via linker flags) 50 testbetBootNodes = []string{ 51 "enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429", 52 "enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430", 53 "enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431", 54 "enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432", 55 "enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433", 56 } 57 ) 58 59 var ( 60 ChequebookAddrFlag = cli.StringFlag{ 61 Name: "chequebook", 62 Usage: "chequebook contract address", 63 EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR, 64 } 65 SwarmAccountFlag = cli.StringFlag{ 66 Name: "bzzaccount", 67 Usage: "Swarm account key file", 68 EnvVar: SWARM_ENV_ACCOUNT, 69 } 70 SwarmListenAddrFlag = cli.StringFlag{ 71 Name: "httpaddr", 72 Usage: "Swarm HTTP API listening interface", 73 EnvVar: SWARM_ENV_LISTEN_ADDR, 74 } 75 SwarmPortFlag = cli.StringFlag{ 76 Name: "bzzport", 77 Usage: "Swarm local http api port", 78 EnvVar: SWARM_ENV_PORT, 79 } 80 SwarmNetworkIdFlag = cli.IntFlag{ 81 Name: "bzznetworkid", 82 Usage: "Network identifier (integer, default 3=swarm testnet)", 83 EnvVar: SWARM_ENV_NETWORK_ID, 84 } 85 SwarmConfigPathFlag = cli.StringFlag{ 86 Name: "bzzconfig", 87 Usage: "DEPRECATED: please use --config path/to/TOML-file", 88 } 89 SwarmSwapEnabledFlag = cli.BoolFlag{ 90 Name: "swap", 91 Usage: "Swarm SWAP enabled (default false)", 92 EnvVar: SWARM_ENV_SWAP_ENABLE, 93 } 94 SwarmSwapAPIFlag = cli.StringFlag{ 95 Name: "swap-api", 96 Usage: "URL of the Sberex API provider to use to settle SWAP payments", 97 EnvVar: SWARM_ENV_SWAP_API, 98 } 99 SwarmSyncEnabledFlag = cli.BoolTFlag{ 100 Name: "sync", 101 Usage: "Swarm Syncing enabled (default true)", 102 EnvVar: SWARM_ENV_SYNC_ENABLE, 103 } 104 EnsAPIFlag = cli.StringSliceFlag{ 105 Name: "ens-api", 106 Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url", 107 EnvVar: SWARM_ENV_ENS_API, 108 } 109 SwarmApiFlag = cli.StringFlag{ 110 Name: "bzzapi", 111 Usage: "Swarm HTTP endpoint", 112 Value: "http://127.0.0.1:8500", 113 } 114 SwarmRecursiveUploadFlag = cli.BoolFlag{ 115 Name: "recursive", 116 Usage: "Upload directories recursively", 117 } 118 SwarmWantManifestFlag = cli.BoolTFlag{ 119 Name: "manifest", 120 Usage: "Automatic manifest upload", 121 } 122 SwarmUploadDefaultPath = cli.StringFlag{ 123 Name: "defaultpath", 124 Usage: "path to file served for empty url path (none)", 125 } 126 SwarmUpFromStdinFlag = cli.BoolFlag{ 127 Name: "stdin", 128 Usage: "reads data to be uploaded from stdin", 129 } 130 SwarmUploadMimeType = cli.StringFlag{ 131 Name: "mime", 132 Usage: "force mime type", 133 } 134 CorsStringFlag = cli.StringFlag{ 135 Name: "corsdomain", 136 Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", 137 EnvVar: SWARM_ENV_CORS, 138 } 139 140 // the following flags are deprecated and should be removed in the future 141 DeprecatedEthAPIFlag = cli.StringFlag{ 142 Name: "ethapi", 143 Usage: "DEPRECATED: please use --ens-api and --swap-api", 144 } 145 DeprecatedEnsAddrFlag = cli.StringFlag{ 146 Name: "ens-addr", 147 Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format", 148 } 149 ) 150 151 //declare a few constant error messages, useful for later error check comparisons in test 152 var ( 153 SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables" 154 SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set" 155 ) 156 157 var defaultNodeConfig = node.DefaultConfig 158 159 // This init function sets defaults so cmd/swarm can run alongside geth. 160 func init() { 161 defaultNodeConfig.Name = clientIdentifier 162 defaultNodeConfig.Version = params.VersionWithCommit(gitCommit) 163 defaultNodeConfig.P2P.ListenAddr = ":30399" 164 defaultNodeConfig.IPCPath = "bzzd.ipc" 165 // Set flag defaults for --help display. 166 utils.ListenPortFlag.Value = 30399 167 } 168 169 var app = utils.NewApp(gitCommit, "Sberex Swarm") 170 171 // This init function creates the cli.App. 172 func init() { 173 app.Action = bzzd 174 app.HideVersion = true // we have a command to print the version 175 app.Copyright = "Copyright 2018 The go-sberex Authors" 176 app.Commands = []cli.Command{ 177 { 178 Action: version, 179 Name: "version", 180 Usage: "Print version numbers", 181 ArgsUsage: " ", 182 Description: ` 183 The output of this command is supposed to be machine-readable. 184 `, 185 }, 186 { 187 Action: upload, 188 Name: "up", 189 Usage: "upload a file or directory to swarm using the HTTP API", 190 ArgsUsage: " <file>", 191 Description: ` 192 "upload a file or directory to swarm using the HTTP API and prints the root hash", 193 `, 194 }, 195 { 196 Action: list, 197 Name: "ls", 198 Usage: "list files and directories contained in a manifest", 199 ArgsUsage: " <manifest> [<prefix>]", 200 Description: ` 201 Lists files and directories contained in a manifest. 202 `, 203 }, 204 { 205 Action: hash, 206 Name: "hash", 207 Usage: "print the swarm hash of a file or directory", 208 ArgsUsage: " <file>", 209 Description: ` 210 Prints the swarm hash of file or directory. 211 `, 212 }, 213 { 214 Name: "manifest", 215 Usage: "update a MANIFEST", 216 ArgsUsage: "manifest COMMAND", 217 Description: ` 218 Updates a MANIFEST by adding/removing/updating the hash of a path. 219 `, 220 Subcommands: []cli.Command{ 221 { 222 Action: add, 223 Name: "add", 224 Usage: "add a new path to the manifest", 225 ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]", 226 Description: ` 227 Adds a new path to the manifest 228 `, 229 }, 230 { 231 Action: update, 232 Name: "update", 233 Usage: "update the hash for an already existing path in the manifest", 234 ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]", 235 Description: ` 236 Update the hash for an already existing path in the manifest 237 `, 238 }, 239 { 240 Action: remove, 241 Name: "remove", 242 Usage: "removes a path from the manifest", 243 ArgsUsage: "<MANIFEST> <path>", 244 Description: ` 245 Removes a path from the manifest 246 `, 247 }, 248 }, 249 }, 250 { 251 Name: "db", 252 Usage: "manage the local chunk database", 253 ArgsUsage: "db COMMAND", 254 Description: ` 255 Manage the local chunk database. 256 `, 257 Subcommands: []cli.Command{ 258 { 259 Action: dbExport, 260 Name: "export", 261 Usage: "export a local chunk database as a tar archive (use - to send to stdout)", 262 ArgsUsage: "<chunkdb> <file>", 263 Description: ` 264 Export a local chunk database as a tar archive (use - to send to stdout). 265 266 swarm db export ~/.sberex/swarm/bzz-KEY/chunks chunks.tar 267 268 The export may be quite large, consider piping the output through the Unix 269 pv(1) tool to get a progress bar: 270 271 swarm db export ~/.sberex/swarm/bzz-KEY/chunks - | pv > chunks.tar 272 `, 273 }, 274 { 275 Action: dbImport, 276 Name: "import", 277 Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)", 278 ArgsUsage: "<chunkdb> <file>", 279 Description: ` 280 Import chunks from a tar archive into a local chunk database (use - to read from stdin). 281 282 swarm db import ~/.sberex/swarm/bzz-KEY/chunks chunks.tar 283 284 The import may be quite large, consider piping the input through the Unix 285 pv(1) tool to get a progress bar: 286 287 pv chunks.tar | swarm db import ~/.sberex/swarm/bzz-KEY/chunks - 288 `, 289 }, 290 { 291 Action: dbClean, 292 Name: "clean", 293 Usage: "remove corrupt entries from a local chunk database", 294 ArgsUsage: "<chunkdb>", 295 Description: ` 296 Remove corrupt entries from a local chunk database. 297 `, 298 }, 299 }, 300 }, 301 { 302 Action: func(ctx *cli.Context) { 303 utils.Fatalf("ERROR: 'swarm cleandb' has been removed, please use 'swarm db clean'.") 304 }, 305 Name: "cleandb", 306 Usage: "DEPRECATED: use 'swarm db clean'", 307 ArgsUsage: " ", 308 Description: ` 309 DEPRECATED: use 'swarm db clean'. 310 `, 311 }, 312 // See config.go 313 DumpConfigCommand, 314 } 315 sort.Sort(cli.CommandsByName(app.Commands)) 316 317 app.Flags = []cli.Flag{ 318 utils.IdentityFlag, 319 utils.DataDirFlag, 320 utils.BootnodesFlag, 321 utils.KeyStoreDirFlag, 322 utils.ListenPortFlag, 323 utils.NoDiscoverFlag, 324 utils.DiscoveryV5Flag, 325 utils.NetrestrictFlag, 326 utils.NodeKeyFileFlag, 327 utils.NodeKeyHexFlag, 328 utils.MaxPeersFlag, 329 utils.NATFlag, 330 utils.IPCDisabledFlag, 331 utils.IPCPathFlag, 332 utils.PasswordFileFlag, 333 // bzzd-specific flags 334 CorsStringFlag, 335 EnsAPIFlag, 336 SwarmTomlConfigPathFlag, 337 SwarmConfigPathFlag, 338 SwarmSwapEnabledFlag, 339 SwarmSwapAPIFlag, 340 SwarmSyncEnabledFlag, 341 SwarmListenAddrFlag, 342 SwarmPortFlag, 343 SwarmAccountFlag, 344 SwarmNetworkIdFlag, 345 ChequebookAddrFlag, 346 // upload flags 347 SwarmApiFlag, 348 SwarmRecursiveUploadFlag, 349 SwarmWantManifestFlag, 350 SwarmUploadDefaultPath, 351 SwarmUpFromStdinFlag, 352 SwarmUploadMimeType, 353 //deprecated flags 354 DeprecatedEthAPIFlag, 355 DeprecatedEnsAddrFlag, 356 } 357 app.Flags = append(app.Flags, debug.Flags...) 358 app.Flags = append(app.Flags, swarmmetrics.Flags...) 359 app.Before = func(ctx *cli.Context) error { 360 runtime.GOMAXPROCS(runtime.NumCPU()) 361 if err := debug.Setup(ctx); err != nil { 362 return err 363 } 364 swarmmetrics.Setup(ctx) 365 return nil 366 } 367 app.After = func(ctx *cli.Context) error { 368 debug.Exit() 369 return nil 370 } 371 } 372 373 func main() { 374 if err := app.Run(os.Args); err != nil { 375 fmt.Fprintln(os.Stderr, err) 376 os.Exit(1) 377 } 378 } 379 380 func version(ctx *cli.Context) error { 381 fmt.Println(strings.Title(clientIdentifier)) 382 fmt.Println("Version:", params.Version) 383 if gitCommit != "" { 384 fmt.Println("Git Commit:", gitCommit) 385 } 386 fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name)) 387 fmt.Println("Go Version:", runtime.Version()) 388 fmt.Println("OS:", runtime.GOOS) 389 fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) 390 fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) 391 return nil 392 } 393 394 func bzzd(ctx *cli.Context) error { 395 //build a valid bzzapi.Config from all available sources: 396 //default config, file config, command line and env vars 397 bzzconfig, err := buildConfig(ctx) 398 if err != nil { 399 utils.Fatalf("unable to configure swarm: %v", err) 400 } 401 402 cfg := defaultNodeConfig 403 //geth only supports --datadir via command line 404 //in order to be consistent within swarm, if we pass --datadir via environment variable 405 //or via config file, we get the same directory for geth and swarm 406 if _, err := os.Stat(bzzconfig.Path); err == nil { 407 cfg.DataDir = bzzconfig.Path 408 } 409 //setup the sberex node 410 utils.SetNodeConfig(ctx, &cfg) 411 stack, err := node.New(&cfg) 412 if err != nil { 413 utils.Fatalf("can't create node: %v", err) 414 } 415 //a few steps need to be done after the config phase is completed, 416 //due to overriding behavior 417 initSwarmNode(bzzconfig, stack, ctx) 418 //register BZZ as node.Service in the sberex node 419 registerBzzService(bzzconfig, ctx, stack) 420 //start the node 421 utils.StartNode(stack) 422 423 go func() { 424 sigc := make(chan os.Signal, 1) 425 signal.Notify(sigc, syscall.SIGTERM) 426 defer signal.Stop(sigc) 427 <-sigc 428 log.Info("Got sigterm, shutting swarm down...") 429 stack.Stop() 430 }() 431 432 // Add bootnodes as initial peers. 433 if bzzconfig.BootNodes != "" { 434 bootnodes := strings.Split(bzzconfig.BootNodes, ",") 435 injectBootnodes(stack.Server(), bootnodes) 436 } else { 437 if bzzconfig.NetworkId == 3 { 438 injectBootnodes(stack.Server(), testbetBootNodes) 439 } 440 } 441 442 stack.Wait() 443 return nil 444 } 445 446 func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) { 447 448 //define the swarm service boot function 449 boot := func(ctx *node.ServiceContext) (node.Service, error) { 450 var swapClient *ethclient.Client 451 var err error 452 if bzzconfig.SwapApi != "" { 453 log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi) 454 swapClient, err = ethclient.Dial(bzzconfig.SwapApi) 455 if err != nil { 456 return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err) 457 } 458 } 459 460 return swarm.NewSwarm(ctx, swapClient, bzzconfig) 461 } 462 //register within the sberex node 463 if err := stack.Register(boot); err != nil { 464 utils.Fatalf("Failed to register the Swarm service: %v", err) 465 } 466 } 467 468 func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey { 469 //an account is mandatory 470 if bzzaccount == "" { 471 utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT) 472 } 473 // Try to load the arg as a hex key file. 474 if key, err := crypto.LoadECDSA(bzzaccount); err == nil { 475 log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey)) 476 return key 477 } 478 // Otherwise try getting it from the keystore. 479 am := stack.AccountManager() 480 ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) 481 482 return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx)) 483 } 484 485 func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey { 486 var a accounts.Account 487 var err error 488 if common.IsHexAddress(account) { 489 a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)}) 490 } else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 { 491 if accounts := ks.Accounts(); len(accounts) > ix { 492 a = accounts[ix] 493 } else { 494 err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts)) 495 } 496 } else { 497 utils.Fatalf("Can't find swarm account key %s", account) 498 } 499 if err != nil { 500 utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account) 501 } 502 keyjson, err := ioutil.ReadFile(a.URL.Path) 503 if err != nil { 504 utils.Fatalf("Can't load swarm account key: %v", err) 505 } 506 for i := 0; i < 3; i++ { 507 password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords) 508 key, err := keystore.DecryptKey(keyjson, password) 509 if err == nil { 510 return key.PrivateKey 511 } 512 } 513 utils.Fatalf("Can't decrypt swarm account key") 514 return nil 515 } 516 517 // getPassPhrase retrieves the password associated with bzz account, either by fetching 518 // from a list of pre-loaded passwords, or by requesting it interactively from user. 519 func getPassPhrase(prompt string, i int, passwords []string) string { 520 // non-interactive 521 if len(passwords) > 0 { 522 if i < len(passwords) { 523 return passwords[i] 524 } 525 return passwords[len(passwords)-1] 526 } 527 528 // fallback to interactive mode 529 if prompt != "" { 530 fmt.Println(prompt) 531 } 532 password, err := console.Stdin.PromptPassword("Passphrase: ") 533 if err != nil { 534 utils.Fatalf("Failed to read passphrase: %v", err) 535 } 536 return password 537 } 538 539 func injectBootnodes(srv *p2p.Server, nodes []string) { 540 for _, url := range nodes { 541 n, err := discover.ParseNode(url) 542 if err != nil { 543 log.Error("Invalid swarm bootnode", "err", err) 544 continue 545 } 546 srv.AddPeer(n) 547 } 548 }