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  }