main.go 16 KB

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