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