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  }