github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/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  	"crypto/ecdsa"
    21  	"encoding/hex"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/signal"
    26  	"runtime"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  	"syscall"
    31  
    32  	"github.com/ShyftNetwork/go-empyrean/accounts"
    33  	"github.com/ShyftNetwork/go-empyrean/accounts/keystore"
    34  	"github.com/ShyftNetwork/go-empyrean/cmd/utils"
    35  	"github.com/ShyftNetwork/go-empyrean/common"
    36  	"github.com/ShyftNetwork/go-empyrean/console"
    37  	"github.com/ShyftNetwork/go-empyrean/crypto"
    38  	"github.com/ShyftNetwork/go-empyrean/internal/debug"
    39  	"github.com/ShyftNetwork/go-empyrean/log"
    40  	"github.com/ShyftNetwork/go-empyrean/node"
    41  	"github.com/ShyftNetwork/go-empyrean/p2p/enode"
    42  	"github.com/ShyftNetwork/go-empyrean/swarm"
    43  	bzzapi "github.com/ShyftNetwork/go-empyrean/swarm/api"
    44  	swarmmetrics "github.com/ShyftNetwork/go-empyrean/swarm/metrics"
    45  	"github.com/ShyftNetwork/go-empyrean/swarm/tracing"
    46  	sv "github.com/ShyftNetwork/go-empyrean/swarm/version"
    47  	"gopkg.in/urfave/cli.v1"
    48  )
    49  
    50  const clientIdentifier = "swarm"
    51  const helpTemplate = `NAME:
    52  {{.HelpName}} - {{.Usage}}
    53  
    54  USAGE:
    55  {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
    56  
    57  CATEGORY:
    58  {{.Category}}{{end}}{{if .Description}}
    59  
    60  DESCRIPTION:
    61  {{.Description}}{{end}}{{if .VisibleFlags}}
    62  
    63  OPTIONS:
    64  {{range .VisibleFlags}}{{.}}
    65  {{end}}{{end}}
    66  `
    67  
    68  var (
    69  	gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
    70  )
    71  
    72  //declare a few constant error messages, useful for later error check comparisons in test
    73  var (
    74  	SWARM_ERR_NO_BZZACCOUNT   = "bzzaccount option is required but not set; check your config file, command line or environment variables"
    75  	SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
    76  )
    77  
    78  // this help command gets added to any subcommand that does not define it explicitly
    79  var defaultSubcommandHelp = cli.Command{
    80  	Action:             func(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "", 1) },
    81  	CustomHelpTemplate: helpTemplate,
    82  	Name:               "help",
    83  	Usage:              "shows this help",
    84  	Hidden:             true,
    85  }
    86  
    87  var defaultNodeConfig = node.DefaultConfig
    88  
    89  // This init function sets defaults so cmd/swarm can run alongside geth.
    90  func init() {
    91  	defaultNodeConfig.Name = clientIdentifier
    92  	defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit)
    93  	defaultNodeConfig.P2P.ListenAddr = ":30399"
    94  	defaultNodeConfig.IPCPath = "bzzd.ipc"
    95  	// Set flag defaults for --help display.
    96  	utils.ListenPortFlag.Value = 30399
    97  }
    98  
    99  var app = utils.NewApp("", "Ethereum Swarm")
   100  
   101  // This init function creates the cli.App.
   102  func init() {
   103  	app.Action = bzzd
   104  	app.Version = sv.ArchiveVersion(gitCommit)
   105  	app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
   106  	app.Commands = []cli.Command{
   107  		{
   108  			Action:             version,
   109  			CustomHelpTemplate: helpTemplate,
   110  			Name:               "version",
   111  			Usage:              "Print version numbers",
   112  			Description:        "The output of this command is supposed to be machine-readable",
   113  		},
   114  		{
   115  			Action:             keys,
   116  			CustomHelpTemplate: helpTemplate,
   117  			Name:               "print-keys",
   118  			Flags:              []cli.Flag{SwarmCompressedFlag},
   119  			Usage:              "Print public key information",
   120  			Description:        "The output of this command is supposed to be machine-readable",
   121  		},
   122  		// See upload.go
   123  		upCommand,
   124  		// See access.go
   125  		accessCommand,
   126  		// See feeds.go
   127  		feedCommand,
   128  		// See list.go
   129  		listCommand,
   130  		// See hash.go
   131  		hashCommand,
   132  		// See download.go
   133  		downloadCommand,
   134  		// See manifest.go
   135  		manifestCommand,
   136  		// See fs.go
   137  		fsCommand,
   138  		// See db.go
   139  		dbCommand,
   140  		// See config.go
   141  		DumpConfigCommand,
   142  	}
   143  
   144  	// append a hidden help subcommand to all commands that have subcommands
   145  	// if a help command was already defined above, that one will take precedence.
   146  	addDefaultHelpSubcommands(app.Commands)
   147  
   148  	sort.Sort(cli.CommandsByName(app.Commands))
   149  
   150  	app.Flags = []cli.Flag{
   151  		utils.IdentityFlag,
   152  		utils.DataDirFlag,
   153  		utils.BootnodesFlag,
   154  		utils.KeyStoreDirFlag,
   155  		utils.ListenPortFlag,
   156  		utils.NoDiscoverFlag,
   157  		utils.DiscoveryV5Flag,
   158  		utils.NetrestrictFlag,
   159  		utils.NodeKeyFileFlag,
   160  		utils.NodeKeyHexFlag,
   161  		utils.MaxPeersFlag,
   162  		utils.NATFlag,
   163  		utils.IPCDisabledFlag,
   164  		utils.IPCPathFlag,
   165  		utils.PasswordFileFlag,
   166  		// bzzd-specific flags
   167  		CorsStringFlag,
   168  		EnsAPIFlag,
   169  		SwarmTomlConfigPathFlag,
   170  		SwarmSwapEnabledFlag,
   171  		SwarmSwapAPIFlag,
   172  		SwarmSyncDisabledFlag,
   173  		SwarmSyncUpdateDelay,
   174  		SwarmMaxStreamPeerServersFlag,
   175  		SwarmLightNodeEnabled,
   176  		SwarmDeliverySkipCheckFlag,
   177  		SwarmListenAddrFlag,
   178  		SwarmPortFlag,
   179  		SwarmAccountFlag,
   180  		SwarmNetworkIdFlag,
   181  		ChequebookAddrFlag,
   182  		// upload flags
   183  		SwarmApiFlag,
   184  		SwarmRecursiveFlag,
   185  		SwarmWantManifestFlag,
   186  		SwarmUploadDefaultPath,
   187  		SwarmUpFromStdinFlag,
   188  		SwarmUploadMimeType,
   189  		// storage flags
   190  		SwarmStorePath,
   191  		SwarmStoreCapacity,
   192  		SwarmStoreCacheCapacity,
   193  	}
   194  	rpcFlags := []cli.Flag{
   195  		utils.WSEnabledFlag,
   196  		utils.WSListenAddrFlag,
   197  		utils.WSPortFlag,
   198  		utils.WSApiFlag,
   199  		utils.WSAllowedOriginsFlag,
   200  	}
   201  	app.Flags = append(app.Flags, rpcFlags...)
   202  	app.Flags = append(app.Flags, debug.Flags...)
   203  	app.Flags = append(app.Flags, swarmmetrics.Flags...)
   204  	app.Flags = append(app.Flags, tracing.Flags...)
   205  	app.Before = func(ctx *cli.Context) error {
   206  		runtime.GOMAXPROCS(runtime.NumCPU())
   207  		if err := debug.Setup(ctx, ""); err != nil {
   208  			return err
   209  		}
   210  		swarmmetrics.Setup(ctx)
   211  		tracing.Setup(ctx)
   212  		return nil
   213  	}
   214  	app.After = func(ctx *cli.Context) error {
   215  		debug.Exit()
   216  		return nil
   217  	}
   218  }
   219  
   220  func main() {
   221  	if err := app.Run(os.Args); err != nil {
   222  		fmt.Fprintln(os.Stderr, err)
   223  		os.Exit(1)
   224  	}
   225  }
   226  
   227  func keys(ctx *cli.Context) error {
   228  	privateKey := getPrivKey(ctx)
   229  	pub := hex.EncodeToString(crypto.FromECDSAPub(&privateKey.PublicKey))
   230  	pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
   231  	if !ctx.Bool(SwarmCompressedFlag.Name) {
   232  		fmt.Println(fmt.Sprintf("publicKey=%s", pub))
   233  	}
   234  	fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed))
   235  	return nil
   236  }
   237  
   238  func version(ctx *cli.Context) error {
   239  	fmt.Println(strings.Title(clientIdentifier))
   240  	fmt.Println("Version:", sv.VersionWithMeta)
   241  	if gitCommit != "" {
   242  		fmt.Println("Git Commit:", gitCommit)
   243  	}
   244  	fmt.Println("Go Version:", runtime.Version())
   245  	fmt.Println("OS:", runtime.GOOS)
   246  	return nil
   247  }
   248  
   249  func bzzd(ctx *cli.Context) error {
   250  	//build a valid bzzapi.Config from all available sources:
   251  	//default config, file config, command line and env vars
   252  
   253  	bzzconfig, err := buildConfig(ctx)
   254  	if err != nil {
   255  		utils.Fatalf("unable to configure swarm: %v", err)
   256  	}
   257  
   258  	cfg := defaultNodeConfig
   259  
   260  	//pss operates on ws
   261  	cfg.WSModules = append(cfg.WSModules, "pss")
   262  
   263  	//geth only supports --datadir via command line
   264  	//in order to be consistent within swarm, if we pass --datadir via environment variable
   265  	//or via config file, we get the same directory for geth and swarm
   266  	if _, err := os.Stat(bzzconfig.Path); err == nil {
   267  		cfg.DataDir = bzzconfig.Path
   268  	}
   269  
   270  	//optionally set the bootnodes before configuring the node
   271  	setSwarmBootstrapNodes(ctx, &cfg)
   272  	//setup the ethereum node
   273  	utils.SetNodeConfig(ctx, &cfg)
   274  	stack, err := node.New(&cfg)
   275  	if err != nil {
   276  		utils.Fatalf("can't create node: %v", err)
   277  	}
   278  
   279  	//a few steps need to be done after the config phase is completed,
   280  	//due to overriding behavior
   281  	initSwarmNode(bzzconfig, stack, ctx)
   282  	//register BZZ as node.Service in the ethereum node
   283  	registerBzzService(bzzconfig, stack)
   284  	//start the node
   285  	utils.StartNode(stack)
   286  
   287  	go func() {
   288  		sigc := make(chan os.Signal, 1)
   289  		signal.Notify(sigc, syscall.SIGTERM)
   290  		defer signal.Stop(sigc)
   291  		<-sigc
   292  		log.Info("Got sigterm, shutting swarm down...")
   293  		stack.Stop()
   294  	}()
   295  
   296  	stack.Wait()
   297  	return nil
   298  }
   299  
   300  func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
   301  	//define the swarm service boot function
   302  	boot := func(_ *node.ServiceContext) (node.Service, error) {
   303  		// In production, mockStore must be always nil.
   304  		return swarm.NewSwarm(bzzconfig, nil)
   305  	}
   306  	//register within the ethereum node
   307  	if err := stack.Register(boot); err != nil {
   308  		utils.Fatalf("Failed to register the Swarm service: %v", err)
   309  	}
   310  }
   311  
   312  func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
   313  	//an account is mandatory
   314  	if bzzaccount == "" {
   315  		utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
   316  	}
   317  	// Try to load the arg as a hex key file.
   318  	if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
   319  		log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
   320  		return key
   321  	}
   322  	// Otherwise try getting it from the keystore.
   323  	am := stack.AccountManager()
   324  	ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   325  
   326  	return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
   327  }
   328  
   329  // getPrivKey returns the private key of the specified bzzaccount
   330  // Used only by client commands, such as `feed`
   331  func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
   332  	// booting up the swarm node just as we do in bzzd action
   333  	bzzconfig, err := buildConfig(ctx)
   334  	if err != nil {
   335  		utils.Fatalf("unable to configure swarm: %v", err)
   336  	}
   337  	cfg := defaultNodeConfig
   338  	if _, err := os.Stat(bzzconfig.Path); err == nil {
   339  		cfg.DataDir = bzzconfig.Path
   340  	}
   341  	utils.SetNodeConfig(ctx, &cfg)
   342  	stack, err := node.New(&cfg)
   343  	if err != nil {
   344  		utils.Fatalf("can't create node: %v", err)
   345  	}
   346  	return getAccount(bzzconfig.BzzAccount, ctx, stack)
   347  }
   348  
   349  func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
   350  	var a accounts.Account
   351  	var err error
   352  	if common.IsHexAddress(account) {
   353  		a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
   354  	} else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
   355  		if accounts := ks.Accounts(); len(accounts) > ix {
   356  			a = accounts[ix]
   357  		} else {
   358  			err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
   359  		}
   360  	} else {
   361  		utils.Fatalf("Can't find swarm account key %s", account)
   362  	}
   363  	if err != nil {
   364  		utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
   365  	}
   366  	keyjson, err := ioutil.ReadFile(a.URL.Path)
   367  	if err != nil {
   368  		utils.Fatalf("Can't load swarm account key: %v", err)
   369  	}
   370  	for i := 0; i < 3; i++ {
   371  		password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
   372  		key, err := keystore.DecryptKey(keyjson, password)
   373  		if err == nil {
   374  			return key.PrivateKey
   375  		}
   376  	}
   377  	utils.Fatalf("Can't decrypt swarm account key")
   378  	return nil
   379  }
   380  
   381  // getPassPhrase retrieves the password associated with bzz account, either by fetching
   382  // from a list of pre-loaded passwords, or by requesting it interactively from user.
   383  func getPassPhrase(prompt string, i int, passwords []string) string {
   384  	// non-interactive
   385  	if len(passwords) > 0 {
   386  		if i < len(passwords) {
   387  			return passwords[i]
   388  		}
   389  		return passwords[len(passwords)-1]
   390  	}
   391  
   392  	// fallback to interactive mode
   393  	if prompt != "" {
   394  		fmt.Println(prompt)
   395  	}
   396  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   397  	if err != nil {
   398  		utils.Fatalf("Failed to read passphrase: %v", err)
   399  	}
   400  	return password
   401  }
   402  
   403  // addDefaultHelpSubcommand scans through defined CLI commands and adds
   404  // a basic help subcommand to each
   405  // if a help command is already defined, it will take precedence over the default.
   406  func addDefaultHelpSubcommands(commands []cli.Command) {
   407  	for i := range commands {
   408  		cmd := &commands[i]
   409  		if cmd.Subcommands != nil {
   410  			cmd.Subcommands = append(cmd.Subcommands, defaultSubcommandHelp)
   411  			addDefaultHelpSubcommands(cmd.Subcommands)
   412  		}
   413  	}
   414  }
   415  
   416  func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
   417  	if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) {
   418  		return
   419  	}
   420  
   421  	cfg.P2P.BootstrapNodes = []*enode.Node{}
   422  
   423  	for _, url := range SwarmBootnodes {
   424  		node, err := enode.ParseV4(url)
   425  		if err != nil {
   426  			log.Error("Bootstrap URL invalid", "enode", url, "err", err)
   427  		}
   428  		cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node)
   429  	}
   430  	log.Debug("added default swarm bootnodes", "length", len(cfg.P2P.BootstrapNodes))
   431  }