github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/cmd/swarm/main.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package main
    13  
    14  import (
    15  	"crypto/ecdsa"
    16  	"fmt"
    17  	"io/ioutil"
    18  	"os"
    19  	"os/signal"
    20  	"runtime"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  	"syscall"
    25  
    26  	"github.com/Sberex/go-sberex/accounts"
    27  	"github.com/Sberex/go-sberex/accounts/keystore"
    28  	"github.com/Sberex/go-sberex/cmd/utils"
    29  	"github.com/Sberex/go-sberex/common"
    30  	"github.com/Sberex/go-sberex/console"
    31  	"github.com/Sberex/go-sberex/crypto"
    32  	"github.com/Sberex/go-sberex/ethclient"
    33  	"github.com/Sberex/go-sberex/internal/debug"
    34  	"github.com/Sberex/go-sberex/log"
    35  	"github.com/Sberex/go-sberex/node"
    36  	"github.com/Sberex/go-sberex/p2p"
    37  	"github.com/Sberex/go-sberex/p2p/discover"
    38  	"github.com/Sberex/go-sberex/params"
    39  	"github.com/Sberex/go-sberex/swarm"
    40  	bzzapi "github.com/Sberex/go-sberex/swarm/api"
    41  	swarmmetrics "github.com/Sberex/go-sberex/swarm/metrics"
    42  
    43  	"gopkg.in/urfave/cli.v1"
    44  )
    45  
    46  const clientIdentifier = "swarm"
    47  
    48  var (
    49  	gitCommit        string // Git SHA1 commit hash of the release (set via linker flags)
    50  	testbetBootNodes = []string{
    51  		"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
    52  		"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
    53  		"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
    54  		"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
    55  		"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
    56  	}
    57  )
    58  
    59  var (
    60  	ChequebookAddrFlag = cli.StringFlag{
    61  		Name:   "chequebook",
    62  		Usage:  "chequebook contract address",
    63  		EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
    64  	}
    65  	SwarmAccountFlag = cli.StringFlag{
    66  		Name:   "bzzaccount",
    67  		Usage:  "Swarm account key file",
    68  		EnvVar: SWARM_ENV_ACCOUNT,
    69  	}
    70  	SwarmListenAddrFlag = cli.StringFlag{
    71  		Name:   "httpaddr",
    72  		Usage:  "Swarm HTTP API listening interface",
    73  		EnvVar: SWARM_ENV_LISTEN_ADDR,
    74  	}
    75  	SwarmPortFlag = cli.StringFlag{
    76  		Name:   "bzzport",
    77  		Usage:  "Swarm local http api port",
    78  		EnvVar: SWARM_ENV_PORT,
    79  	}
    80  	SwarmNetworkIdFlag = cli.IntFlag{
    81  		Name:   "bzznetworkid",
    82  		Usage:  "Network identifier (integer, default 3=swarm testnet)",
    83  		EnvVar: SWARM_ENV_NETWORK_ID,
    84  	}
    85  	SwarmConfigPathFlag = cli.StringFlag{
    86  		Name:  "bzzconfig",
    87  		Usage: "DEPRECATED: please use --config path/to/TOML-file",
    88  	}
    89  	SwarmSwapEnabledFlag = cli.BoolFlag{
    90  		Name:   "swap",
    91  		Usage:  "Swarm SWAP enabled (default false)",
    92  		EnvVar: SWARM_ENV_SWAP_ENABLE,
    93  	}
    94  	SwarmSwapAPIFlag = cli.StringFlag{
    95  		Name:   "swap-api",
    96  		Usage:  "URL of the Sberex API provider to use to settle SWAP payments",
    97  		EnvVar: SWARM_ENV_SWAP_API,
    98  	}
    99  	SwarmSyncEnabledFlag = cli.BoolTFlag{
   100  		Name:   "sync",
   101  		Usage:  "Swarm Syncing enabled (default true)",
   102  		EnvVar: SWARM_ENV_SYNC_ENABLE,
   103  	}
   104  	EnsAPIFlag = cli.StringSliceFlag{
   105  		Name:   "ens-api",
   106  		Usage:  "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
   107  		EnvVar: SWARM_ENV_ENS_API,
   108  	}
   109  	SwarmApiFlag = cli.StringFlag{
   110  		Name:  "bzzapi",
   111  		Usage: "Swarm HTTP endpoint",
   112  		Value: "http://127.0.0.1:8500",
   113  	}
   114  	SwarmRecursiveUploadFlag = cli.BoolFlag{
   115  		Name:  "recursive",
   116  		Usage: "Upload directories recursively",
   117  	}
   118  	SwarmWantManifestFlag = cli.BoolTFlag{
   119  		Name:  "manifest",
   120  		Usage: "Automatic manifest upload",
   121  	}
   122  	SwarmUploadDefaultPath = cli.StringFlag{
   123  		Name:  "defaultpath",
   124  		Usage: "path to file served for empty url path (none)",
   125  	}
   126  	SwarmUpFromStdinFlag = cli.BoolFlag{
   127  		Name:  "stdin",
   128  		Usage: "reads data to be uploaded from stdin",
   129  	}
   130  	SwarmUploadMimeType = cli.StringFlag{
   131  		Name:  "mime",
   132  		Usage: "force mime type",
   133  	}
   134  	CorsStringFlag = cli.StringFlag{
   135  		Name:   "corsdomain",
   136  		Usage:  "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
   137  		EnvVar: SWARM_ENV_CORS,
   138  	}
   139  
   140  	// the following flags are deprecated and should be removed in the future
   141  	DeprecatedEthAPIFlag = cli.StringFlag{
   142  		Name:  "ethapi",
   143  		Usage: "DEPRECATED: please use --ens-api and --swap-api",
   144  	}
   145  	DeprecatedEnsAddrFlag = cli.StringFlag{
   146  		Name:  "ens-addr",
   147  		Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format",
   148  	}
   149  )
   150  
   151  //declare a few constant error messages, useful for later error check comparisons in test
   152  var (
   153  	SWARM_ERR_NO_BZZACCOUNT   = "bzzaccount option is required but not set; check your config file, command line or environment variables"
   154  	SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
   155  )
   156  
   157  var defaultNodeConfig = node.DefaultConfig
   158  
   159  // This init function sets defaults so cmd/swarm can run alongside geth.
   160  func init() {
   161  	defaultNodeConfig.Name = clientIdentifier
   162  	defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
   163  	defaultNodeConfig.P2P.ListenAddr = ":30399"
   164  	defaultNodeConfig.IPCPath = "bzzd.ipc"
   165  	// Set flag defaults for --help display.
   166  	utils.ListenPortFlag.Value = 30399
   167  }
   168  
   169  var app = utils.NewApp(gitCommit, "Sberex Swarm")
   170  
   171  // This init function creates the cli.App.
   172  func init() {
   173  	app.Action = bzzd
   174  	app.HideVersion = true // we have a command to print the version
   175  	app.Copyright = "Copyright 2018 The go-sberex Authors"
   176  	app.Commands = []cli.Command{
   177  		{
   178  			Action:    version,
   179  			Name:      "version",
   180  			Usage:     "Print version numbers",
   181  			ArgsUsage: " ",
   182  			Description: `
   183  The output of this command is supposed to be machine-readable.
   184  `,
   185  		},
   186  		{
   187  			Action:    upload,
   188  			Name:      "up",
   189  			Usage:     "upload a file or directory to swarm using the HTTP API",
   190  			ArgsUsage: " <file>",
   191  			Description: `
   192  "upload a file or directory to swarm using the HTTP API and prints the root hash",
   193  `,
   194  		},
   195  		{
   196  			Action:    list,
   197  			Name:      "ls",
   198  			Usage:     "list files and directories contained in a manifest",
   199  			ArgsUsage: " <manifest> [<prefix>]",
   200  			Description: `
   201  Lists files and directories contained in a manifest.
   202  `,
   203  		},
   204  		{
   205  			Action:    hash,
   206  			Name:      "hash",
   207  			Usage:     "print the swarm hash of a file or directory",
   208  			ArgsUsage: " <file>",
   209  			Description: `
   210  Prints the swarm hash of file or directory.
   211  `,
   212  		},
   213  		{
   214  			Name:      "manifest",
   215  			Usage:     "update a MANIFEST",
   216  			ArgsUsage: "manifest COMMAND",
   217  			Description: `
   218  Updates a MANIFEST by adding/removing/updating the hash of a path.
   219  `,
   220  			Subcommands: []cli.Command{
   221  				{
   222  					Action:    add,
   223  					Name:      "add",
   224  					Usage:     "add a new path to the manifest",
   225  					ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]",
   226  					Description: `
   227  Adds a new path to the manifest
   228  `,
   229  				},
   230  				{
   231  					Action:    update,
   232  					Name:      "update",
   233  					Usage:     "update the hash for an already existing path in the manifest",
   234  					ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]",
   235  					Description: `
   236  Update the hash for an already existing path in the manifest
   237  `,
   238  				},
   239  				{
   240  					Action:    remove,
   241  					Name:      "remove",
   242  					Usage:     "removes a path from the manifest",
   243  					ArgsUsage: "<MANIFEST> <path>",
   244  					Description: `
   245  Removes a path from the manifest
   246  `,
   247  				},
   248  			},
   249  		},
   250  		{
   251  			Name:      "db",
   252  			Usage:     "manage the local chunk database",
   253  			ArgsUsage: "db COMMAND",
   254  			Description: `
   255  Manage the local chunk database.
   256  `,
   257  			Subcommands: []cli.Command{
   258  				{
   259  					Action:    dbExport,
   260  					Name:      "export",
   261  					Usage:     "export a local chunk database as a tar archive (use - to send to stdout)",
   262  					ArgsUsage: "<chunkdb> <file>",
   263  					Description: `
   264  Export a local chunk database as a tar archive (use - to send to stdout).
   265  
   266      swarm db export ~/.sberex/swarm/bzz-KEY/chunks chunks.tar
   267  
   268  The export may be quite large, consider piping the output through the Unix
   269  pv(1) tool to get a progress bar:
   270  
   271      swarm db export ~/.sberex/swarm/bzz-KEY/chunks - | pv > chunks.tar
   272  `,
   273  				},
   274  				{
   275  					Action:    dbImport,
   276  					Name:      "import",
   277  					Usage:     "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
   278  					ArgsUsage: "<chunkdb> <file>",
   279  					Description: `
   280  Import chunks from a tar archive into a local chunk database (use - to read from stdin).
   281  
   282      swarm db import ~/.sberex/swarm/bzz-KEY/chunks chunks.tar
   283  
   284  The import may be quite large, consider piping the input through the Unix
   285  pv(1) tool to get a progress bar:
   286  
   287      pv chunks.tar | swarm db import ~/.sberex/swarm/bzz-KEY/chunks -
   288  `,
   289  				},
   290  				{
   291  					Action:    dbClean,
   292  					Name:      "clean",
   293  					Usage:     "remove corrupt entries from a local chunk database",
   294  					ArgsUsage: "<chunkdb>",
   295  					Description: `
   296  Remove corrupt entries from a local chunk database.
   297  `,
   298  				},
   299  			},
   300  		},
   301  		{
   302  			Action: func(ctx *cli.Context) {
   303  				utils.Fatalf("ERROR: 'swarm cleandb' has been removed, please use 'swarm db clean'.")
   304  			},
   305  			Name:      "cleandb",
   306  			Usage:     "DEPRECATED: use 'swarm db clean'",
   307  			ArgsUsage: " ",
   308  			Description: `
   309  DEPRECATED: use 'swarm db clean'.
   310  `,
   311  		},
   312  		// See config.go
   313  		DumpConfigCommand,
   314  	}
   315  	sort.Sort(cli.CommandsByName(app.Commands))
   316  
   317  	app.Flags = []cli.Flag{
   318  		utils.IdentityFlag,
   319  		utils.DataDirFlag,
   320  		utils.BootnodesFlag,
   321  		utils.KeyStoreDirFlag,
   322  		utils.ListenPortFlag,
   323  		utils.NoDiscoverFlag,
   324  		utils.DiscoveryV5Flag,
   325  		utils.NetrestrictFlag,
   326  		utils.NodeKeyFileFlag,
   327  		utils.NodeKeyHexFlag,
   328  		utils.MaxPeersFlag,
   329  		utils.NATFlag,
   330  		utils.IPCDisabledFlag,
   331  		utils.IPCPathFlag,
   332  		utils.PasswordFileFlag,
   333  		// bzzd-specific flags
   334  		CorsStringFlag,
   335  		EnsAPIFlag,
   336  		SwarmTomlConfigPathFlag,
   337  		SwarmConfigPathFlag,
   338  		SwarmSwapEnabledFlag,
   339  		SwarmSwapAPIFlag,
   340  		SwarmSyncEnabledFlag,
   341  		SwarmListenAddrFlag,
   342  		SwarmPortFlag,
   343  		SwarmAccountFlag,
   344  		SwarmNetworkIdFlag,
   345  		ChequebookAddrFlag,
   346  		// upload flags
   347  		SwarmApiFlag,
   348  		SwarmRecursiveUploadFlag,
   349  		SwarmWantManifestFlag,
   350  		SwarmUploadDefaultPath,
   351  		SwarmUpFromStdinFlag,
   352  		SwarmUploadMimeType,
   353  		//deprecated flags
   354  		DeprecatedEthAPIFlag,
   355  		DeprecatedEnsAddrFlag,
   356  	}
   357  	app.Flags = append(app.Flags, debug.Flags...)
   358  	app.Flags = append(app.Flags, swarmmetrics.Flags...)
   359  	app.Before = func(ctx *cli.Context) error {
   360  		runtime.GOMAXPROCS(runtime.NumCPU())
   361  		if err := debug.Setup(ctx); err != nil {
   362  			return err
   363  		}
   364  		swarmmetrics.Setup(ctx)
   365  		return nil
   366  	}
   367  	app.After = func(ctx *cli.Context) error {
   368  		debug.Exit()
   369  		return nil
   370  	}
   371  }
   372  
   373  func main() {
   374  	if err := app.Run(os.Args); err != nil {
   375  		fmt.Fprintln(os.Stderr, err)
   376  		os.Exit(1)
   377  	}
   378  }
   379  
   380  func version(ctx *cli.Context) error {
   381  	fmt.Println(strings.Title(clientIdentifier))
   382  	fmt.Println("Version:", params.Version)
   383  	if gitCommit != "" {
   384  		fmt.Println("Git Commit:", gitCommit)
   385  	}
   386  	fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
   387  	fmt.Println("Go Version:", runtime.Version())
   388  	fmt.Println("OS:", runtime.GOOS)
   389  	fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
   390  	fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
   391  	return nil
   392  }
   393  
   394  func bzzd(ctx *cli.Context) error {
   395  	//build a valid bzzapi.Config from all available sources:
   396  	//default config, file config, command line and env vars
   397  	bzzconfig, err := buildConfig(ctx)
   398  	if err != nil {
   399  		utils.Fatalf("unable to configure swarm: %v", err)
   400  	}
   401  
   402  	cfg := defaultNodeConfig
   403  	//geth only supports --datadir via command line
   404  	//in order to be consistent within swarm, if we pass --datadir via environment variable
   405  	//or via config file, we get the same directory for geth and swarm
   406  	if _, err := os.Stat(bzzconfig.Path); err == nil {
   407  		cfg.DataDir = bzzconfig.Path
   408  	}
   409  	//setup the sberex node
   410  	utils.SetNodeConfig(ctx, &cfg)
   411  	stack, err := node.New(&cfg)
   412  	if err != nil {
   413  		utils.Fatalf("can't create node: %v", err)
   414  	}
   415  	//a few steps need to be done after the config phase is completed,
   416  	//due to overriding behavior
   417  	initSwarmNode(bzzconfig, stack, ctx)
   418  	//register BZZ as node.Service in the sberex node
   419  	registerBzzService(bzzconfig, ctx, stack)
   420  	//start the node
   421  	utils.StartNode(stack)
   422  
   423  	go func() {
   424  		sigc := make(chan os.Signal, 1)
   425  		signal.Notify(sigc, syscall.SIGTERM)
   426  		defer signal.Stop(sigc)
   427  		<-sigc
   428  		log.Info("Got sigterm, shutting swarm down...")
   429  		stack.Stop()
   430  	}()
   431  
   432  	// Add bootnodes as initial peers.
   433  	if bzzconfig.BootNodes != "" {
   434  		bootnodes := strings.Split(bzzconfig.BootNodes, ",")
   435  		injectBootnodes(stack.Server(), bootnodes)
   436  	} else {
   437  		if bzzconfig.NetworkId == 3 {
   438  			injectBootnodes(stack.Server(), testbetBootNodes)
   439  		}
   440  	}
   441  
   442  	stack.Wait()
   443  	return nil
   444  }
   445  
   446  func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
   447  
   448  	//define the swarm service boot function
   449  	boot := func(ctx *node.ServiceContext) (node.Service, error) {
   450  		var swapClient *ethclient.Client
   451  		var err error
   452  		if bzzconfig.SwapApi != "" {
   453  			log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
   454  			swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
   455  			if err != nil {
   456  				return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
   457  			}
   458  		}
   459  
   460  		return swarm.NewSwarm(ctx, swapClient, bzzconfig)
   461  	}
   462  	//register within the sberex node
   463  	if err := stack.Register(boot); err != nil {
   464  		utils.Fatalf("Failed to register the Swarm service: %v", err)
   465  	}
   466  }
   467  
   468  func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
   469  	//an account is mandatory
   470  	if bzzaccount == "" {
   471  		utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
   472  	}
   473  	// Try to load the arg as a hex key file.
   474  	if key, err := crypto.LoadECDSA(bzzaccount); 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, bzzaccount, 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 - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
   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  }