github.com/niluplatform/go-nilu@v1.7.4-0.20200912082737-a0cb0776d52c/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  	"sort"
    27  	"strconv"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"github.com/NiluPlatform/go-nilu/accounts"
    32  	"github.com/NiluPlatform/go-nilu/accounts/keystore"
    33  	"github.com/NiluPlatform/go-nilu/cmd/utils"
    34  	"github.com/NiluPlatform/go-nilu/common"
    35  	"github.com/NiluPlatform/go-nilu/console"
    36  	"github.com/NiluPlatform/go-nilu/crypto"
    37  	"github.com/NiluPlatform/go-nilu/ethclient"
    38  	"github.com/NiluPlatform/go-nilu/internal/debug"
    39  	"github.com/NiluPlatform/go-nilu/log"
    40  	"github.com/NiluPlatform/go-nilu/node"
    41  	"github.com/NiluPlatform/go-nilu/p2p"
    42  	"github.com/NiluPlatform/go-nilu/p2p/discover"
    43  	"github.com/NiluPlatform/go-nilu/params"
    44  	"github.com/NiluPlatform/go-nilu/swarm"
    45  	bzzapi "github.com/NiluPlatform/go-nilu/swarm/api"
    46  	swarmmetrics "github.com/NiluPlatform/go-nilu/swarm/metrics"
    47  
    48  	"gopkg.in/urfave/cli.v1"
    49  )
    50  
    51  const clientIdentifier = "swarm"
    52  
    53  var (
    54  	gitCommit        string // Git SHA1 commit hash of the release (set via linker flags)
    55  	testbetBootNodes = []string{
    56  		"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
    57  		"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
    58  		"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
    59  		"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
    60  		"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
    61  	}
    62  )
    63  
    64  var (
    65  	ChequebookAddrFlag = cli.StringFlag{
    66  		Name:   "chequebook",
    67  		Usage:  "chequebook contract address",
    68  		EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
    69  	}
    70  	SwarmAccountFlag = cli.StringFlag{
    71  		Name:   "bzzaccount",
    72  		Usage:  "Swarm account key file",
    73  		EnvVar: SWARM_ENV_ACCOUNT,
    74  	}
    75  	SwarmListenAddrFlag = cli.StringFlag{
    76  		Name:   "httpaddr",
    77  		Usage:  "Swarm HTTP API listening interface",
    78  		EnvVar: SWARM_ENV_LISTEN_ADDR,
    79  	}
    80  	SwarmPortFlag = cli.StringFlag{
    81  		Name:   "bzzport",
    82  		Usage:  "Swarm local http api port",
    83  		EnvVar: SWARM_ENV_PORT,
    84  	}
    85  	SwarmNetworkIdFlag = cli.IntFlag{
    86  		Name:   "bzznetworkid",
    87  		Usage:  "Network identifier (integer, default 3=swarm testnet)",
    88  		EnvVar: SWARM_ENV_NETWORK_ID,
    89  	}
    90  	SwarmConfigPathFlag = cli.StringFlag{
    91  		Name:  "bzzconfig",
    92  		Usage: "DEPRECATED: please use --config path/to/TOML-file",
    93  	}
    94  	SwarmSwapEnabledFlag = cli.BoolFlag{
    95  		Name:   "swap",
    96  		Usage:  "Swarm SWAP enabled (default false)",
    97  		EnvVar: SWARM_ENV_SWAP_ENABLE,
    98  	}
    99  	SwarmSwapAPIFlag = cli.StringFlag{
   100  		Name:   "swap-api",
   101  		Usage:  "URL of the Ethereum API provider to use to settle SWAP payments",
   102  		EnvVar: SWARM_ENV_SWAP_API,
   103  	}
   104  	SwarmSyncEnabledFlag = cli.BoolTFlag{
   105  		Name:   "sync",
   106  		Usage:  "Swarm Syncing enabled (default true)",
   107  		EnvVar: SWARM_ENV_SYNC_ENABLE,
   108  	}
   109  	EnsAPIFlag = cli.StringSliceFlag{
   110  		Name:   "ens-api",
   111  		Usage:  "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
   112  		EnvVar: SWARM_ENV_ENS_API,
   113  	}
   114  	SwarmApiFlag = cli.StringFlag{
   115  		Name:  "bzzapi",
   116  		Usage: "Swarm HTTP endpoint",
   117  		Value: "http://127.0.0.1:8500",
   118  	}
   119  	SwarmRecursiveUploadFlag = cli.BoolFlag{
   120  		Name:  "recursive",
   121  		Usage: "Upload directories recursively",
   122  	}
   123  	SwarmWantManifestFlag = cli.BoolTFlag{
   124  		Name:  "manifest",
   125  		Usage: "Automatic manifest upload",
   126  	}
   127  	SwarmUploadDefaultPath = cli.StringFlag{
   128  		Name:  "defaultpath",
   129  		Usage: "path to file served for empty url path (none)",
   130  	}
   131  	SwarmUpFromStdinFlag = cli.BoolFlag{
   132  		Name:  "stdin",
   133  		Usage: "reads data to be uploaded from stdin",
   134  	}
   135  	SwarmUploadMimeType = cli.StringFlag{
   136  		Name:  "mime",
   137  		Usage: "force mime type",
   138  	}
   139  	CorsStringFlag = cli.StringFlag{
   140  		Name:   "corsdomain",
   141  		Usage:  "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
   142  		EnvVar: SWARM_ENV_CORS,
   143  	}
   144  
   145  	// the following flags are deprecated and should be removed in the future
   146  	DeprecatedEthAPIFlag = cli.StringFlag{
   147  		Name:  "ethapi",
   148  		Usage: "DEPRECATED: please use --ens-api and --swap-api",
   149  	}
   150  	DeprecatedEnsAddrFlag = cli.StringFlag{
   151  		Name:  "ens-addr",
   152  		Usage: "DEPRECATED: ENS contract address, please use --ens-api with contract address according to its format",
   153  	}
   154  )
   155  
   156  //declare a few constant error messages, useful for later error check comparisons in test
   157  var (
   158  	SWARM_ERR_NO_BZZACCOUNT   = "bzzaccount option is required but not set; check your config file, command line or environment variables"
   159  	SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
   160  )
   161  
   162  var defaultNodeConfig = node.DefaultConfig
   163  
   164  // This init function sets defaults so cmd/swarm can run alongside gilu.
   165  func init() {
   166  	defaultNodeConfig.Name = clientIdentifier
   167  	defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
   168  	defaultNodeConfig.P2P.ListenAddr = ":30399"
   169  	defaultNodeConfig.IPCPath = "bzzd.ipc"
   170  	// Set flag defaults for --help display.
   171  	utils.ListenPortFlag.Value = 30399
   172  }
   173  
   174  var app = utils.NewApp(gitCommit, "Ethereum Swarm")
   175  
   176  // This init function creates the cli.App.
   177  func init() {
   178  	app.Action = bzzd
   179  	app.HideVersion = true // we have a command to print the version
   180  	app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
   181  	app.Commands = []cli.Command{
   182  		{
   183  			Action:    version,
   184  			Name:      "version",
   185  			Usage:     "Print version numbers",
   186  			ArgsUsage: " ",
   187  			Description: `
   188  The output of this command is supposed to be machine-readable.
   189  `,
   190  		},
   191  		{
   192  			Action:    upload,
   193  			Name:      "up",
   194  			Usage:     "upload a file or directory to swarm using the HTTP API",
   195  			ArgsUsage: " <file>",
   196  			Description: `
   197  "upload a file or directory to swarm using the HTTP API and prints the root hash",
   198  `,
   199  		},
   200  		{
   201  			Action:    list,
   202  			Name:      "ls",
   203  			Usage:     "list files and directories contained in a manifest",
   204  			ArgsUsage: " <manifest> [<prefix>]",
   205  			Description: `
   206  Lists files and directories contained in a manifest.
   207  `,
   208  		},
   209  		{
   210  			Action:    hash,
   211  			Name:      "hash",
   212  			Usage:     "print the swarm hash of a file or directory",
   213  			ArgsUsage: " <file>",
   214  			Description: `
   215  Prints the swarm hash of file or directory.
   216  `,
   217  		},
   218  		{
   219  			Name:      "manifest",
   220  			Usage:     "update a MANIFEST",
   221  			ArgsUsage: "manifest COMMAND",
   222  			Description: `
   223  Updates a MANIFEST by adding/removing/updating the hash of a path.
   224  `,
   225  			Subcommands: []cli.Command{
   226  				{
   227  					Action:    add,
   228  					Name:      "add",
   229  					Usage:     "add a new path to the manifest",
   230  					ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]",
   231  					Description: `
   232  Adds a new path to the manifest
   233  `,
   234  				},
   235  				{
   236  					Action:    update,
   237  					Name:      "update",
   238  					Usage:     "update the hash for an already existing path in the manifest",
   239  					ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]",
   240  					Description: `
   241  Update the hash for an already existing path in the manifest
   242  `,
   243  				},
   244  				{
   245  					Action:    remove,
   246  					Name:      "remove",
   247  					Usage:     "removes a path from the manifest",
   248  					ArgsUsage: "<MANIFEST> <path>",
   249  					Description: `
   250  Removes a path from the manifest
   251  `,
   252  				},
   253  			},
   254  		},
   255  		{
   256  			Name:      "db",
   257  			Usage:     "manage the local chunk database",
   258  			ArgsUsage: "db COMMAND",
   259  			Description: `
   260  Manage the local chunk database.
   261  `,
   262  			Subcommands: []cli.Command{
   263  				{
   264  					Action:    dbExport,
   265  					Name:      "export",
   266  					Usage:     "export a local chunk database as a tar archive (use - to send to stdout)",
   267  					ArgsUsage: "<chunkdb> <file>",
   268  					Description: `
   269  Export a local chunk database as a tar archive (use - to send to stdout).
   270  
   271      swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
   272  
   273  The export may be quite large, consider piping the output through the Unix
   274  pv(1) tool to get a progress bar:
   275  
   276      swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
   277  `,
   278  				},
   279  				{
   280  					Action:    dbImport,
   281  					Name:      "import",
   282  					Usage:     "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
   283  					ArgsUsage: "<chunkdb> <file>",
   284  					Description: `
   285  Import chunks from a tar archive into a local chunk database (use - to read from stdin).
   286  
   287      swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
   288  
   289  The import may be quite large, consider piping the input through the Unix
   290  pv(1) tool to get a progress bar:
   291  
   292      pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -
   293  `,
   294  				},
   295  				{
   296  					Action:    dbClean,
   297  					Name:      "clean",
   298  					Usage:     "remove corrupt entries from a local chunk database",
   299  					ArgsUsage: "<chunkdb>",
   300  					Description: `
   301  Remove corrupt entries from a local chunk database.
   302  `,
   303  				},
   304  			},
   305  		},
   306  		{
   307  			Action: func(ctx *cli.Context) {
   308  				utils.Fatalf("ERROR: 'swarm cleandb' has been removed, please use 'swarm db clean'.")
   309  			},
   310  			Name:      "cleandb",
   311  			Usage:     "DEPRECATED: use 'swarm db clean'",
   312  			ArgsUsage: " ",
   313  			Description: `
   314  DEPRECATED: use 'swarm db clean'.
   315  `,
   316  		},
   317  		// See config.go
   318  		DumpConfigCommand,
   319  	}
   320  	sort.Sort(cli.CommandsByName(app.Commands))
   321  
   322  	app.Flags = []cli.Flag{
   323  		utils.IdentityFlag,
   324  		utils.DataDirFlag,
   325  		utils.BootnodesFlag,
   326  		utils.KeyStoreDirFlag,
   327  		utils.ListenPortFlag,
   328  		utils.NoDiscoverFlag,
   329  		utils.DiscoveryV5Flag,
   330  		utils.NetrestrictFlag,
   331  		utils.NodeKeyFileFlag,
   332  		utils.NodeKeyHexFlag,
   333  		utils.MaxPeersFlag,
   334  		utils.NATFlag,
   335  		utils.IPCDisabledFlag,
   336  		utils.IPCPathFlag,
   337  		utils.PasswordFileFlag,
   338  		// bzzd-specific flags
   339  		CorsStringFlag,
   340  		EnsAPIFlag,
   341  		SwarmTomlConfigPathFlag,
   342  		SwarmConfigPathFlag,
   343  		SwarmSwapEnabledFlag,
   344  		SwarmSwapAPIFlag,
   345  		SwarmSyncEnabledFlag,
   346  		SwarmListenAddrFlag,
   347  		SwarmPortFlag,
   348  		SwarmAccountFlag,
   349  		SwarmNetworkIdFlag,
   350  		ChequebookAddrFlag,
   351  		// upload flags
   352  		SwarmApiFlag,
   353  		SwarmRecursiveUploadFlag,
   354  		SwarmWantManifestFlag,
   355  		SwarmUploadDefaultPath,
   356  		SwarmUpFromStdinFlag,
   357  		SwarmUploadMimeType,
   358  		//deprecated flags
   359  		DeprecatedEthAPIFlag,
   360  		DeprecatedEnsAddrFlag,
   361  	}
   362  	app.Flags = append(app.Flags, debug.Flags...)
   363  	app.Flags = append(app.Flags, swarmmetrics.Flags...)
   364  	app.Before = func(ctx *cli.Context) error {
   365  		runtime.GOMAXPROCS(runtime.NumCPU())
   366  		if err := debug.Setup(ctx); err != nil {
   367  			return err
   368  		}
   369  		swarmmetrics.Setup(ctx)
   370  		return nil
   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  	//gilu 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 gilu 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  func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
   452  
   453  	//define the swarm service boot function
   454  	boot := func(ctx *node.ServiceContext) (node.Service, error) {
   455  		var swapClient *ethclient.Client
   456  		var err error
   457  		if bzzconfig.SwapApi != "" {
   458  			log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
   459  			swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
   460  			if err != nil {
   461  				return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
   462  			}
   463  		}
   464  
   465  		return swarm.NewSwarm(ctx, swapClient, bzzconfig)
   466  	}
   467  	//register within the ethereum node
   468  	if err := stack.Register(boot); err != nil {
   469  		utils.Fatalf("Failed to register the Swarm service: %v", err)
   470  	}
   471  }
   472  
   473  func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
   474  	//an account is mandatory
   475  	if bzzaccount == "" {
   476  		utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
   477  	}
   478  	// Try to load the arg as a hex key file.
   479  	if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
   480  		log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
   481  		return key
   482  	}
   483  	// Otherwise try getting it from the keystore.
   484  	am := stack.AccountManager()
   485  	ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
   486  
   487  	return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
   488  }
   489  
   490  func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
   491  	var a accounts.Account
   492  	var err error
   493  	if common.IsHexAddress(account) {
   494  		a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
   495  	} else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
   496  		if accounts := ks.Accounts(); len(accounts) > ix {
   497  			a = accounts[ix]
   498  		} else {
   499  			err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
   500  		}
   501  	} else {
   502  		utils.Fatalf("Can't find swarm account key %s", account)
   503  	}
   504  	if err != nil {
   505  		utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
   506  	}
   507  	keyjson, err := ioutil.ReadFile(a.URL.Path)
   508  	if err != nil {
   509  		utils.Fatalf("Can't load swarm account key: %v", err)
   510  	}
   511  	for i := 0; i < 3; i++ {
   512  		password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
   513  		key, err := keystore.DecryptKey(keyjson, password)
   514  		if err == nil {
   515  			return key.PrivateKey
   516  		}
   517  	}
   518  	utils.Fatalf("Can't decrypt swarm account key")
   519  	return nil
   520  }
   521  
   522  // getPassPhrase retrieves the password associated with bzz account, either by fetching
   523  // from a list of pre-loaded passwords, or by requesting it interactively from user.
   524  func getPassPhrase(prompt string, i int, passwords []string) string {
   525  	// non-interactive
   526  	if len(passwords) > 0 {
   527  		if i < len(passwords) {
   528  			return passwords[i]
   529  		}
   530  		return passwords[len(passwords)-1]
   531  	}
   532  
   533  	// fallback to interactive mode
   534  	if prompt != "" {
   535  		fmt.Println(prompt)
   536  	}
   537  	password, err := console.Stdin.PromptPassword("Passphrase: ")
   538  	if err != nil {
   539  		utils.Fatalf("Failed to read passphrase: %v", err)
   540  	}
   541  	return password
   542  }
   543  
   544  func injectBootnodes(srv *p2p.Server, nodes []string) {
   545  	for _, url := range nodes {
   546  		n, err := discover.ParseNode(url)
   547  		if err != nil {
   548  			log.Error("Invalid swarm bootnode", "err", err)
   549  			continue
   550  		}
   551  		srv.AddPeer(n)
   552  	}
   553  }