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