github.com/alanchchen/go-ethereum@v1.6.6-0.20170601190819-6171d01b1195/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  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/signal"
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"github.com/ethereum/go-ethereum/accounts"
    31  	"github.com/ethereum/go-ethereum/accounts/keystore"
    32  	"github.com/ethereum/go-ethereum/cmd/utils"
    33  	"github.com/ethereum/go-ethereum/common"
    34  	"github.com/ethereum/go-ethereum/console"
    35  	"github.com/ethereum/go-ethereum/crypto"
    36  	"github.com/ethereum/go-ethereum/ethclient"
    37  	"github.com/ethereum/go-ethereum/internal/debug"
    38  	"github.com/ethereum/go-ethereum/log"
    39  	"github.com/ethereum/go-ethereum/node"
    40  	"github.com/ethereum/go-ethereum/p2p"
    41  	"github.com/ethereum/go-ethereum/p2p/discover"
    42  	"github.com/ethereum/go-ethereum/params"
    43  	"github.com/ethereum/go-ethereum/swarm"
    44  	bzzapi "github.com/ethereum/go-ethereum/swarm/api"
    45  	"gopkg.in/urfave/cli.v1"
    46  )
    47  
    48  const clientIdentifier = "swarm"
    49  
    50  var (
    51  	gitCommit        string // Git SHA1 commit hash of the release (set via linker flags)
    52  	testbetBootNodes = []string{
    53  		"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
    54  		"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
    55  		"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
    56  		"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
    57  		"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
    58  	}
    59  )
    60  
    61  var (
    62  	ChequebookAddrFlag = cli.StringFlag{
    63  		Name:  "chequebook",
    64  		Usage: "chequebook contract address",
    65  	}
    66  	SwarmAccountFlag = cli.StringFlag{
    67  		Name:  "bzzaccount",
    68  		Usage: "Swarm account key file",
    69  	}
    70  	SwarmListenAddrFlag = cli.StringFlag{
    71  		Name:  "httpaddr",
    72  		Usage: "Swarm HTTP API listening interface",
    73  	}
    74  	SwarmPortFlag = cli.StringFlag{
    75  		Name:  "bzzport",
    76  		Usage: "Swarm local http api port",
    77  	}
    78  	SwarmNetworkIdFlag = cli.IntFlag{
    79  		Name:  "bzznetworkid",
    80  		Usage: "Network identifier (integer, default 3=swarm testnet)",
    81  	}
    82  	SwarmConfigPathFlag = cli.StringFlag{
    83  		Name:  "bzzconfig",
    84  		Usage: "Swarm config file path (datadir/bzz)",
    85  	}
    86  	SwarmSwapEnabledFlag = cli.BoolFlag{
    87  		Name:  "swap",
    88  		Usage: "Swarm SWAP enabled (default false)",
    89  	}
    90  	SwarmSyncEnabledFlag = cli.BoolTFlag{
    91  		Name:  "sync",
    92  		Usage: "Swarm Syncing enabled (default true)",
    93  	}
    94  	EthAPIFlag = cli.StringFlag{
    95  		Name:  "ethapi",
    96  		Usage: "URL of the Ethereum API provider",
    97  		Value: node.DefaultIPCEndpoint("geth"),
    98  	}
    99  	SwarmApiFlag = cli.StringFlag{
   100  		Name:  "bzzapi",
   101  		Usage: "Swarm HTTP endpoint",
   102  		Value: "http://127.0.0.1:8500",
   103  	}
   104  	SwarmRecursiveUploadFlag = cli.BoolFlag{
   105  		Name:  "recursive",
   106  		Usage: "Upload directories recursively",
   107  	}
   108  	SwarmWantManifestFlag = cli.BoolTFlag{
   109  		Name:  "manifest",
   110  		Usage: "Automatic manifest upload",
   111  	}
   112  	SwarmUploadDefaultPath = cli.StringFlag{
   113  		Name:  "defaultpath",
   114  		Usage: "path to file served for empty url path (none)",
   115  	}
   116  	SwarmUpFromStdinFlag = cli.BoolFlag{
   117  		Name:  "stdin",
   118  		Usage: "reads data to be uploaded from stdin",
   119  	}
   120  	SwarmUploadMimeType = cli.StringFlag{
   121  		Name:  "mime",
   122  		Usage: "force mime type",
   123  	}
   124  	CorsStringFlag = cli.StringFlag{
   125  		Name:  "corsdomain",
   126  		Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
   127  	}
   128  )
   129  
   130  var defaultNodeConfig = node.DefaultConfig
   131  
   132  // This init function sets defaults so cmd/swarm can run alongside geth.
   133  func init() {
   134  	defaultNodeConfig.Name = clientIdentifier
   135  	defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
   136  	defaultNodeConfig.P2P.ListenAddr = ":30399"
   137  	defaultNodeConfig.IPCPath = "bzzd.ipc"
   138  	// Set flag defaults for --help display.
   139  	utils.ListenPortFlag.Value = 30399
   140  }
   141  
   142  var app = utils.NewApp(gitCommit, "Ethereum Swarm")
   143  
   144  // This init function creates the cli.App.
   145  func init() {
   146  	app.Action = bzzd
   147  	app.HideVersion = true // we have a command to print the version
   148  	app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
   149  	app.Commands = []cli.Command{
   150  		{
   151  			Action:    version,
   152  			Name:      "version",
   153  			Usage:     "Print version numbers",
   154  			ArgsUsage: " ",
   155  			Description: `
   156  The output of this command is supposed to be machine-readable.
   157  `,
   158  		},
   159  		{
   160  			Action:    upload,
   161  			Name:      "up",
   162  			Usage:     "upload a file or directory to swarm using the HTTP API",
   163  			ArgsUsage: " <file>",
   164  			Description: `
   165  "upload a file or directory to swarm using the HTTP API and prints the root hash",
   166  `,
   167  		},
   168  		{
   169  			Action:    list,
   170  			Name:      "ls",
   171  			Usage:     "list files and directories contained in a manifest",
   172  			ArgsUsage: " <manifest> [<prefix>]",
   173  			Description: `
   174  Lists files and directories contained in a manifest.
   175  `,
   176  		},
   177  		{
   178  			Action:    hash,
   179  			Name:      "hash",
   180  			Usage:     "print the swarm hash of a file or directory",
   181  			ArgsUsage: " <file>",
   182  			Description: `
   183  Prints the swarm hash of file or directory.
   184  `,
   185  		},
   186  		{
   187  			Name:      "manifest",
   188  			Usage:     "update a MANIFEST",
   189  			ArgsUsage: "manifest COMMAND",
   190  			Description: `
   191  Updates a MANIFEST by adding/removing/updating the hash of a path.
   192  `,
   193  			Subcommands: []cli.Command{
   194  				{
   195  					Action:    add,
   196  					Name:      "add",
   197  					Usage:     "add a new path to the manifest",
   198  					ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]",
   199  					Description: `
   200  Adds a new path to the manifest
   201  `,
   202  				},
   203  				{
   204  					Action:    update,
   205  					Name:      "update",
   206  					Usage:     "update the hash for an already existing path in the manifest",
   207  					ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]",
   208  					Description: `
   209  Update the hash for an already existing path in the manifest
   210  `,
   211  				},
   212  				{
   213  					Action:    remove,
   214  					Name:      "remove",
   215  					Usage:     "removes a path from the manifest",
   216  					ArgsUsage: "<MANIFEST> <path>",
   217  					Description: `
   218  Removes a path from the manifest
   219  `,
   220  				},
   221  			},
   222  		},
   223  		{
   224  			Action:    cleandb,
   225  			Name:      "cleandb",
   226  			Usage:     "Cleans database of corrupted entries",
   227  			ArgsUsage: " ",
   228  			Description: `
   229  Cleans database of corrupted entries.
   230  `,
   231  		},
   232  	}
   233  
   234  	app.Flags = []cli.Flag{
   235  		utils.IdentityFlag,
   236  		utils.DataDirFlag,
   237  		utils.BootnodesFlag,
   238  		utils.KeyStoreDirFlag,
   239  		utils.ListenPortFlag,
   240  		utils.NoDiscoverFlag,
   241  		utils.DiscoveryV5Flag,
   242  		utils.NetrestrictFlag,
   243  		utils.NodeKeyFileFlag,
   244  		utils.NodeKeyHexFlag,
   245  		utils.MaxPeersFlag,
   246  		utils.NATFlag,
   247  		utils.IPCDisabledFlag,
   248  		utils.IPCPathFlag,
   249  		utils.PasswordFileFlag,
   250  		// bzzd-specific flags
   251  		CorsStringFlag,
   252  		EthAPIFlag,
   253  		SwarmConfigPathFlag,
   254  		SwarmSwapEnabledFlag,
   255  		SwarmSyncEnabledFlag,
   256  		SwarmListenAddrFlag,
   257  		SwarmPortFlag,
   258  		SwarmAccountFlag,
   259  		SwarmNetworkIdFlag,
   260  		ChequebookAddrFlag,
   261  		// upload flags
   262  		SwarmApiFlag,
   263  		SwarmRecursiveUploadFlag,
   264  		SwarmWantManifestFlag,
   265  		SwarmUploadDefaultPath,
   266  		SwarmUpFromStdinFlag,
   267  		SwarmUploadMimeType,
   268  	}
   269  	app.Flags = append(app.Flags, debug.Flags...)
   270  	app.Before = func(ctx *cli.Context) error {
   271  		runtime.GOMAXPROCS(runtime.NumCPU())
   272  		return debug.Setup(ctx)
   273  	}
   274  	app.After = func(ctx *cli.Context) error {
   275  		debug.Exit()
   276  		return nil
   277  	}
   278  }
   279  
   280  func main() {
   281  	if err := app.Run(os.Args); err != nil {
   282  		fmt.Fprintln(os.Stderr, err)
   283  		os.Exit(1)
   284  	}
   285  }
   286  
   287  func version(ctx *cli.Context) error {
   288  	fmt.Println(strings.Title(clientIdentifier))
   289  	fmt.Println("Version:", params.Version)
   290  	if gitCommit != "" {
   291  		fmt.Println("Git Commit:", gitCommit)
   292  	}
   293  	fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
   294  	fmt.Println("Go Version:", runtime.Version())
   295  	fmt.Println("OS:", runtime.GOOS)
   296  	fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
   297  	fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
   298  	return nil
   299  }
   300  
   301  func bzzd(ctx *cli.Context) error {
   302  	cfg := defaultNodeConfig
   303  	utils.SetNodeConfig(ctx, &cfg)
   304  	stack, err := node.New(&cfg)
   305  	if err != nil {
   306  		utils.Fatalf("can't create node: %v", err)
   307  	}
   308  
   309  	registerBzzService(ctx, stack)
   310  	utils.StartNode(stack)
   311  
   312  	go func() {
   313  		sigc := make(chan os.Signal, 1)
   314  		signal.Notify(sigc, syscall.SIGTERM)
   315  		defer signal.Stop(sigc)
   316  		<-sigc
   317  		log.Info("Got sigterm, shutting swarm down...")
   318  		stack.Stop()
   319  	}()
   320  
   321  	networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
   322  	// Add bootnodes as initial peers.
   323  	if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
   324  		bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
   325  		injectBootnodes(stack.Server(), bootnodes)
   326  	} else {
   327  		if networkId == 3 {
   328  			injectBootnodes(stack.Server(), testbetBootNodes)
   329  		}
   330  	}
   331  
   332  	stack.Wait()
   333  	return nil
   334  }
   335  
   336  func registerBzzService(ctx *cli.Context, stack *node.Node) {
   337  	prvkey := getAccount(ctx, stack)
   338  
   339  	chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
   340  	bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
   341  	if bzzdir == "" {
   342  		bzzdir = stack.InstanceDir()
   343  	}
   344  
   345  	bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name))
   346  	if err != nil {
   347  		utils.Fatalf("unable to configure swarm: %v", err)
   348  	}
   349  	bzzport := ctx.GlobalString(SwarmPortFlag.Name)
   350  	if len(bzzport) > 0 {
   351  		bzzconfig.Port = bzzport
   352  	}
   353  	if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
   354  		bzzconfig.ListenAddr = bzzaddr
   355  	}
   356  	swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
   357  	syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
   358  
   359  	ethapi := ctx.GlobalString(EthAPIFlag.Name)
   360  	cors := ctx.GlobalString(CorsStringFlag.Name)
   361  
   362  	boot := func(ctx *node.ServiceContext) (node.Service, error) {
   363  		var client *ethclient.Client
   364  		if len(ethapi) > 0 {
   365  			client, err = ethclient.Dial(ethapi)
   366  			if err != nil {
   367  				utils.Fatalf("Can't connect: %v", err)
   368  			}
   369  		} else {
   370  			swapEnabled = false
   371  		}
   372  		return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled, cors)
   373  	}
   374  	if err := stack.Register(boot); err != nil {
   375  		utils.Fatalf("Failed to register the Swarm service: %v", err)
   376  	}
   377  }
   378  
   379  func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
   380  	keyid := ctx.GlobalString(SwarmAccountFlag.Name)
   381  
   382  	if keyid == "" {
   383  		utils.Fatalf("Option %q is required", SwarmAccountFlag.Name)
   384  	}
   385  	// Try to load the arg as a hex key file.
   386  	if key, err := crypto.LoadECDSA(keyid); err == nil {
   387  		log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
   388  		return key
   389  	}
   390  	// Otherwise try getting it from the keystore.
   391  	am := stack.AccountManager()
   392  	ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   393  
   394  	return decryptStoreAccount(ks, keyid, utils.MakePasswordList(ctx))
   395  }
   396  
   397  func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
   398  	var a accounts.Account
   399  	var err error
   400  	if common.IsHexAddress(account) {
   401  		a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
   402  	} else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
   403  		if accounts := ks.Accounts(); len(accounts) > ix {
   404  			a = accounts[ix]
   405  		} else {
   406  			err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
   407  		}
   408  	} else {
   409  		utils.Fatalf("Can't find swarm account key %s", account)
   410  	}
   411  	if err != nil {
   412  		utils.Fatalf("Can't find swarm account key: %v", err)
   413  	}
   414  	keyjson, err := ioutil.ReadFile(a.URL.Path)
   415  	if err != nil {
   416  		utils.Fatalf("Can't load swarm account key: %v", err)
   417  	}
   418  	for i := 0; i < 3; i++ {
   419  		password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
   420  		key, err := keystore.DecryptKey(keyjson, password)
   421  		if err == nil {
   422  			return key.PrivateKey
   423  		}
   424  	}
   425  	utils.Fatalf("Can't decrypt swarm account key")
   426  	return nil
   427  }
   428  
   429  // getPassPhrase retrieves the password associated with bzz account, either by fetching
   430  // from a list of pre-loaded passwords, or by requesting it interactively from user.
   431  func getPassPhrase(prompt string, i int, passwords []string) string {
   432  	// non-interactive
   433  	if len(passwords) > 0 {
   434  		if i < len(passwords) {
   435  			return passwords[i]
   436  		}
   437  		return passwords[len(passwords)-1]
   438  	}
   439  
   440  	// fallback to interactive mode
   441  	if prompt != "" {
   442  		fmt.Println(prompt)
   443  	}
   444  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   445  	if err != nil {
   446  		utils.Fatalf("Failed to read passphrase: %v", err)
   447  	}
   448  	return password
   449  }
   450  
   451  func injectBootnodes(srv *p2p.Server, nodes []string) {
   452  	for _, url := range nodes {
   453  		n, err := discover.ParseNode(url)
   454  		if err != nil {
   455  			log.Error("Invalid swarm bootnode", "err", err)
   456  			continue
   457  		}
   458  		srv.AddPeer(n)
   459  	}
   460  }