github.com/myafeier/go-ethereum@v1.6.8-0.20170719123245-3e0dbe0eaa72/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 "strconv" 29 "strings" 30 "syscall" 31 "time" 32 33 "github.com/ethereum/go-ethereum/accounts" 34 "github.com/ethereum/go-ethereum/accounts/keystore" 35 "github.com/ethereum/go-ethereum/cmd/utils" 36 "github.com/ethereum/go-ethereum/common" 37 "github.com/ethereum/go-ethereum/console" 38 "github.com/ethereum/go-ethereum/contracts/ens" 39 "github.com/ethereum/go-ethereum/crypto" 40 "github.com/ethereum/go-ethereum/ethclient" 41 "github.com/ethereum/go-ethereum/internal/debug" 42 "github.com/ethereum/go-ethereum/log" 43 "github.com/ethereum/go-ethereum/node" 44 "github.com/ethereum/go-ethereum/p2p" 45 "github.com/ethereum/go-ethereum/p2p/discover" 46 "github.com/ethereum/go-ethereum/params" 47 "github.com/ethereum/go-ethereum/rpc" 48 "github.com/ethereum/go-ethereum/swarm" 49 bzzapi "github.com/ethereum/go-ethereum/swarm/api" 50 "gopkg.in/urfave/cli.v1" 51 ) 52 53 const clientIdentifier = "swarm" 54 55 var ( 56 gitCommit string // Git SHA1 commit hash of the release (set via linker flags) 57 testbetBootNodes = []string{ 58 "enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429", 59 "enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430", 60 "enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431", 61 "enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432", 62 "enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433", 63 } 64 ) 65 66 var ( 67 ChequebookAddrFlag = cli.StringFlag{ 68 Name: "chequebook", 69 Usage: "chequebook contract address", 70 } 71 SwarmAccountFlag = cli.StringFlag{ 72 Name: "bzzaccount", 73 Usage: "Swarm account key file", 74 } 75 SwarmListenAddrFlag = cli.StringFlag{ 76 Name: "httpaddr", 77 Usage: "Swarm HTTP API listening interface", 78 } 79 SwarmPortFlag = cli.StringFlag{ 80 Name: "bzzport", 81 Usage: "Swarm local http api port", 82 } 83 SwarmNetworkIdFlag = cli.IntFlag{ 84 Name: "bzznetworkid", 85 Usage: "Network identifier (integer, default 3=swarm testnet)", 86 } 87 SwarmConfigPathFlag = cli.StringFlag{ 88 Name: "bzzconfig", 89 Usage: "Swarm config file path (datadir/bzz)", 90 } 91 SwarmSwapEnabledFlag = cli.BoolFlag{ 92 Name: "swap", 93 Usage: "Swarm SWAP enabled (default false)", 94 } 95 SwarmSwapAPIFlag = cli.StringFlag{ 96 Name: "swap-api", 97 Usage: "URL of the Ethereum API provider to use to settle SWAP payments", 98 } 99 SwarmSyncEnabledFlag = cli.BoolTFlag{ 100 Name: "sync", 101 Usage: "Swarm Syncing enabled (default true)", 102 } 103 EnsAPIFlag = cli.StringFlag{ 104 Name: "ens-api", 105 Usage: "URL of the Ethereum API provider to use for ENS record lookups", 106 Value: node.DefaultIPCEndpoint("geth"), 107 } 108 EnsAddrFlag = cli.StringFlag{ 109 Name: "ens-addr", 110 Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)", 111 } 112 SwarmApiFlag = cli.StringFlag{ 113 Name: "bzzapi", 114 Usage: "Swarm HTTP endpoint", 115 Value: "http://127.0.0.1:8500", 116 } 117 SwarmRecursiveUploadFlag = cli.BoolFlag{ 118 Name: "recursive", 119 Usage: "Upload directories recursively", 120 } 121 SwarmWantManifestFlag = cli.BoolTFlag{ 122 Name: "manifest", 123 Usage: "Automatic manifest upload", 124 } 125 SwarmUploadDefaultPath = cli.StringFlag{ 126 Name: "defaultpath", 127 Usage: "path to file served for empty url path (none)", 128 } 129 SwarmUpFromStdinFlag = cli.BoolFlag{ 130 Name: "stdin", 131 Usage: "reads data to be uploaded from stdin", 132 } 133 SwarmUploadMimeType = cli.StringFlag{ 134 Name: "mime", 135 Usage: "force mime type", 136 } 137 CorsStringFlag = cli.StringFlag{ 138 Name: "corsdomain", 139 Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", 140 } 141 142 // the following flags are deprecated and should be removed in the future 143 DeprecatedEthAPIFlag = cli.StringFlag{ 144 Name: "ethapi", 145 Usage: "DEPRECATED: please use --ens-api and --swap-api", 146 } 147 ) 148 149 var defaultNodeConfig = node.DefaultConfig 150 151 // This init function sets defaults so cmd/swarm can run alongside geth. 152 func init() { 153 defaultNodeConfig.Name = clientIdentifier 154 defaultNodeConfig.Version = params.VersionWithCommit(gitCommit) 155 defaultNodeConfig.P2P.ListenAddr = ":30399" 156 defaultNodeConfig.IPCPath = "bzzd.ipc" 157 // Set flag defaults for --help display. 158 utils.ListenPortFlag.Value = 30399 159 } 160 161 var app = utils.NewApp(gitCommit, "Ethereum Swarm") 162 163 // This init function creates the cli.App. 164 func init() { 165 app.Action = bzzd 166 app.HideVersion = true // we have a command to print the version 167 app.Copyright = "Copyright 2013-2016 The go-ethereum Authors" 168 app.Commands = []cli.Command{ 169 { 170 Action: version, 171 Name: "version", 172 Usage: "Print version numbers", 173 ArgsUsage: " ", 174 Description: ` 175 The output of this command is supposed to be machine-readable. 176 `, 177 }, 178 { 179 Action: upload, 180 Name: "up", 181 Usage: "upload a file or directory to swarm using the HTTP API", 182 ArgsUsage: " <file>", 183 Description: ` 184 "upload a file or directory to swarm using the HTTP API and prints the root hash", 185 `, 186 }, 187 { 188 Action: list, 189 Name: "ls", 190 Usage: "list files and directories contained in a manifest", 191 ArgsUsage: " <manifest> [<prefix>]", 192 Description: ` 193 Lists files and directories contained in a manifest. 194 `, 195 }, 196 { 197 Action: hash, 198 Name: "hash", 199 Usage: "print the swarm hash of a file or directory", 200 ArgsUsage: " <file>", 201 Description: ` 202 Prints the swarm hash of file or directory. 203 `, 204 }, 205 { 206 Name: "manifest", 207 Usage: "update a MANIFEST", 208 ArgsUsage: "manifest COMMAND", 209 Description: ` 210 Updates a MANIFEST by adding/removing/updating the hash of a path. 211 `, 212 Subcommands: []cli.Command{ 213 { 214 Action: add, 215 Name: "add", 216 Usage: "add a new path to the manifest", 217 ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]", 218 Description: ` 219 Adds a new path to the manifest 220 `, 221 }, 222 { 223 Action: update, 224 Name: "update", 225 Usage: "update the hash for an already existing path in the manifest", 226 ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]", 227 Description: ` 228 Update the hash for an already existing path in the manifest 229 `, 230 }, 231 { 232 Action: remove, 233 Name: "remove", 234 Usage: "removes a path from the manifest", 235 ArgsUsage: "<MANIFEST> <path>", 236 Description: ` 237 Removes a path from the manifest 238 `, 239 }, 240 }, 241 }, 242 { 243 Action: cleandb, 244 Name: "cleandb", 245 Usage: "Cleans database of corrupted entries", 246 ArgsUsage: " ", 247 Description: ` 248 Cleans database of corrupted entries. 249 `, 250 }, 251 } 252 253 app.Flags = []cli.Flag{ 254 utils.IdentityFlag, 255 utils.DataDirFlag, 256 utils.BootnodesFlag, 257 utils.KeyStoreDirFlag, 258 utils.ListenPortFlag, 259 utils.NoDiscoverFlag, 260 utils.DiscoveryV5Flag, 261 utils.NetrestrictFlag, 262 utils.NodeKeyFileFlag, 263 utils.NodeKeyHexFlag, 264 utils.MaxPeersFlag, 265 utils.NATFlag, 266 utils.IPCDisabledFlag, 267 utils.IPCPathFlag, 268 utils.PasswordFileFlag, 269 // bzzd-specific flags 270 CorsStringFlag, 271 EnsAPIFlag, 272 EnsAddrFlag, 273 SwarmConfigPathFlag, 274 SwarmSwapEnabledFlag, 275 SwarmSwapAPIFlag, 276 SwarmSyncEnabledFlag, 277 SwarmListenAddrFlag, 278 SwarmPortFlag, 279 SwarmAccountFlag, 280 SwarmNetworkIdFlag, 281 ChequebookAddrFlag, 282 // upload flags 283 SwarmApiFlag, 284 SwarmRecursiveUploadFlag, 285 SwarmWantManifestFlag, 286 SwarmUploadDefaultPath, 287 SwarmUpFromStdinFlag, 288 SwarmUploadMimeType, 289 //deprecated flags 290 DeprecatedEthAPIFlag, 291 } 292 app.Flags = append(app.Flags, debug.Flags...) 293 app.Before = func(ctx *cli.Context) error { 294 runtime.GOMAXPROCS(runtime.NumCPU()) 295 return debug.Setup(ctx) 296 } 297 app.After = func(ctx *cli.Context) error { 298 debug.Exit() 299 return nil 300 } 301 } 302 303 func main() { 304 if err := app.Run(os.Args); err != nil { 305 fmt.Fprintln(os.Stderr, err) 306 os.Exit(1) 307 } 308 } 309 310 func version(ctx *cli.Context) error { 311 fmt.Println(strings.Title(clientIdentifier)) 312 fmt.Println("Version:", params.Version) 313 if gitCommit != "" { 314 fmt.Println("Git Commit:", gitCommit) 315 } 316 fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name)) 317 fmt.Println("Go Version:", runtime.Version()) 318 fmt.Println("OS:", runtime.GOOS) 319 fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) 320 fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) 321 return nil 322 } 323 324 func bzzd(ctx *cli.Context) error { 325 // exit if the deprecated --ethapi flag is set 326 if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" { 327 utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.") 328 } 329 330 cfg := defaultNodeConfig 331 utils.SetNodeConfig(ctx, &cfg) 332 stack, err := node.New(&cfg) 333 if err != nil { 334 utils.Fatalf("can't create node: %v", err) 335 } 336 337 registerBzzService(ctx, stack) 338 utils.StartNode(stack) 339 340 go func() { 341 sigc := make(chan os.Signal, 1) 342 signal.Notify(sigc, syscall.SIGTERM) 343 defer signal.Stop(sigc) 344 <-sigc 345 log.Info("Got sigterm, shutting swarm down...") 346 stack.Stop() 347 }() 348 349 networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name) 350 // Add bootnodes as initial peers. 351 if ctx.GlobalIsSet(utils.BootnodesFlag.Name) { 352 bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",") 353 injectBootnodes(stack.Server(), bootnodes) 354 } else { 355 if networkId == 3 { 356 injectBootnodes(stack.Server(), testbetBootNodes) 357 } 358 } 359 360 stack.Wait() 361 return nil 362 } 363 364 // detectEnsAddr determines the ENS contract address by getting both the 365 // version and genesis hash using the client and matching them to either 366 // mainnet or testnet addresses 367 func detectEnsAddr(client *rpc.Client) (common.Address, error) { 368 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 369 defer cancel() 370 371 var version string 372 if err := client.CallContext(ctx, &version, "net_version"); err != nil { 373 return common.Address{}, err 374 } 375 376 block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0)) 377 if err != nil { 378 return common.Address{}, err 379 } 380 381 switch { 382 383 case version == "1" && block.Hash() == params.MainnetGenesisHash: 384 log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress) 385 return ens.MainNetAddress, nil 386 387 case version == "3" && block.Hash() == params.TestnetGenesisHash: 388 log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress) 389 return ens.TestNetAddress, nil 390 391 default: 392 return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash()) 393 } 394 } 395 396 func registerBzzService(ctx *cli.Context, stack *node.Node) { 397 prvkey := getAccount(ctx, stack) 398 399 chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name)) 400 bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name) 401 if bzzdir == "" { 402 bzzdir = stack.InstanceDir() 403 } 404 405 bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name)) 406 if err != nil { 407 utils.Fatalf("unable to configure swarm: %v", err) 408 } 409 bzzport := ctx.GlobalString(SwarmPortFlag.Name) 410 if len(bzzport) > 0 { 411 bzzconfig.Port = bzzport 412 } 413 if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" { 414 bzzconfig.ListenAddr = bzzaddr 415 } 416 swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name) 417 syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name) 418 419 swapapi := ctx.GlobalString(SwarmSwapAPIFlag.Name) 420 if swapEnabled && swapapi == "" { 421 utils.Fatalf("SWAP is enabled but --swap-api is not set") 422 } 423 424 ensapi := ctx.GlobalString(EnsAPIFlag.Name) 425 ensAddr := ctx.GlobalString(EnsAddrFlag.Name) 426 427 cors := ctx.GlobalString(CorsStringFlag.Name) 428 429 boot := func(ctx *node.ServiceContext) (node.Service, error) { 430 var swapClient *ethclient.Client 431 if swapapi != "" { 432 log.Info("connecting to SWAP API", "url", swapapi) 433 swapClient, err = ethclient.Dial(swapapi) 434 if err != nil { 435 return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err) 436 } 437 } 438 439 var ensClient *ethclient.Client 440 if ensapi != "" { 441 log.Info("connecting to ENS API", "url", ensapi) 442 client, err := rpc.Dial(ensapi) 443 if err != nil { 444 return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err) 445 } 446 ensClient = ethclient.NewClient(client) 447 448 if ensAddr != "" { 449 bzzconfig.EnsRoot = common.HexToAddress(ensAddr) 450 } else { 451 ensAddr, err := detectEnsAddr(client) 452 if err == nil { 453 bzzconfig.EnsRoot = ensAddr 454 } else { 455 log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", bzzconfig.EnsRoot), "err", err) 456 } 457 } 458 } 459 460 return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, swapEnabled, syncEnabled, cors) 461 } 462 if err := stack.Register(boot); err != nil { 463 utils.Fatalf("Failed to register the Swarm service: %v", err) 464 } 465 } 466 467 func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey { 468 keyid := ctx.GlobalString(SwarmAccountFlag.Name) 469 470 if keyid == "" { 471 utils.Fatalf("Option %q is required", SwarmAccountFlag.Name) 472 } 473 // Try to load the arg as a hex key file. 474 if key, err := crypto.LoadECDSA(keyid); 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, keyid, 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", err) 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 }