github.com/vantum/vantum@v0.0.0-20180815184342-fe37d5f7a990/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 "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/vantum/vantum/accounts" 35 "github.com/vantum/vantum/accounts/keystore" 36 "github.com/vantum/vantum/cmd/utils" 37 "github.com/vantum/vantum/common" 38 "github.com/vantum/vantum/console" 39 "github.com/vantum/vantum/contracts/ens" 40 "github.com/vantum/vantum/crypto" 41 "github.com/vantum/vantum/ethclient" 42 "github.com/vantum/vantum/internal/debug" 43 "github.com/vantum/vantum/log" 44 "github.com/vantum/vantum/node" 45 "github.com/vantum/vantum/p2p" 46 "github.com/vantum/vantum/p2p/discover" 47 "github.com/vantum/vantum/params" 48 "github.com/vantum/vantum/rpc" 49 "github.com/vantum/vantum/swarm" 50 bzzapi "github.com/vantum/vantum/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 go-ethereum 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 }