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