main.go 15.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

package main

import (
20
	"context"
21 22 23
	"crypto/ecdsa"
	"fmt"
	"io/ioutil"
24
	"math/big"
25
	"os"
26
	"os/signal"
27 28
	"runtime"
	"strconv"
29
	"strings"
30
	"syscall"
31
	"time"
32 33

	"github.com/ethereum/go-ethereum/accounts"
34
	"github.com/ethereum/go-ethereum/accounts/keystore"
35 36 37
	"github.com/ethereum/go-ethereum/cmd/utils"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/console"
38
	"github.com/ethereum/go-ethereum/contracts/ens"
39 40 41
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/internal/debug"
42
	"github.com/ethereum/go-ethereum/log"
43 44 45
	"github.com/ethereum/go-ethereum/node"
	"github.com/ethereum/go-ethereum/p2p"
	"github.com/ethereum/go-ethereum/p2p/discover"
46
	"github.com/ethereum/go-ethereum/params"
47
	"github.com/ethereum/go-ethereum/rpc"
48 49 50 51 52
	"github.com/ethereum/go-ethereum/swarm"
	bzzapi "github.com/ethereum/go-ethereum/swarm/api"
	"gopkg.in/urfave/cli.v1"
)

53
const clientIdentifier = "swarm"
54 55

var (
56 57
	gitCommit        string // Git SHA1 commit hash of the release (set via linker flags)
	testbetBootNodes = []string{
58 59 60 61 62
		"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
		"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
		"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
		"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
		"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
63
	}
64 65 66 67 68 69 70 71 72 73 74
)

var (
	ChequebookAddrFlag = cli.StringFlag{
		Name:  "chequebook",
		Usage: "chequebook contract address",
	}
	SwarmAccountFlag = cli.StringFlag{
		Name:  "bzzaccount",
		Usage: "Swarm account key file",
	}
75 76 77 78
	SwarmListenAddrFlag = cli.StringFlag{
		Name:  "httpaddr",
		Usage: "Swarm HTTP API listening interface",
	}
79 80 81 82
	SwarmPortFlag = cli.StringFlag{
		Name:  "bzzport",
		Usage: "Swarm local http api port",
	}
83 84
	SwarmNetworkIdFlag = cli.IntFlag{
		Name:  "bzznetworkid",
85
		Usage: "Network identifier (integer, default 3=swarm testnet)",
86
	}
87 88 89 90
	SwarmConfigPathFlag = cli.StringFlag{
		Name:  "bzzconfig",
		Usage: "Swarm config file path (datadir/bzz)",
	}
91
	SwarmSwapEnabledFlag = cli.BoolFlag{
92 93
		Name:  "swap",
		Usage: "Swarm SWAP enabled (default false)",
94
	}
95 96 97 98
	SwarmSwapAPIFlag = cli.StringFlag{
		Name:  "swap-api",
		Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
	}
99
	SwarmSyncEnabledFlag = cli.BoolTFlag{
100 101
		Name:  "sync",
		Usage: "Swarm Syncing enabled (default true)",
102
	}
103 104 105
	EnsAPIFlag = cli.StringFlag{
		Name:  "ens-api",
		Usage: "URL of the Ethereum API provider to use for ENS record lookups",
106 107
		Value: node.DefaultIPCEndpoint("geth"),
	}
108 109 110 111
	EnsAddrFlag = cli.StringFlag{
		Name:  "ens-addr",
		Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
	}
112 113 114 115 116 117 118 119 120 121 122 123 124
	SwarmApiFlag = cli.StringFlag{
		Name:  "bzzapi",
		Usage: "Swarm HTTP endpoint",
		Value: "http://127.0.0.1:8500",
	}
	SwarmRecursiveUploadFlag = cli.BoolFlag{
		Name:  "recursive",
		Usage: "Upload directories recursively",
	}
	SwarmWantManifestFlag = cli.BoolTFlag{
		Name:  "manifest",
		Usage: "Automatic manifest upload",
	}
125 126 127 128
	SwarmUploadDefaultPath = cli.StringFlag{
		Name:  "defaultpath",
		Usage: "path to file served for empty url path (none)",
	}
129 130 131 132 133 134 135 136
	SwarmUpFromStdinFlag = cli.BoolFlag{
		Name:  "stdin",
		Usage: "reads data to be uploaded from stdin",
	}
	SwarmUploadMimeType = cli.StringFlag{
		Name:  "mime",
		Usage: "force mime type",
	}
137 138
	CorsStringFlag = cli.StringFlag{
		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 143 144 145 146

	// the following flags are deprecated and should be removed in the future
	DeprecatedEthAPIFlag = cli.StringFlag{
		Name:  "ethapi",
		Usage: "DEPRECATED: please use --ens-api and --swap-api",
	}
147 148
)

149 150 151
var defaultNodeConfig = node.DefaultConfig

// This init function sets defaults so cmd/swarm can run alongside geth.
152
func init() {
153 154 155 156 157
	defaultNodeConfig.Name = clientIdentifier
	defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
	defaultNodeConfig.P2P.ListenAddr = ":30399"
	defaultNodeConfig.IPCPath = "bzzd.ipc"
	// Set flag defaults for --help display.
158
	utils.ListenPortFlag.Value = 30399
159
}
160

161 162 163 164
var app = utils.NewApp(gitCommit, "Ethereum Swarm")

// This init function creates the cli.App.
func init() {
165
	app.Action = bzzd
166 167 168
	app.HideVersion = true // we have a command to print the version
	app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
	app.Commands = []cli.Command{
Felix Lange's avatar
Felix Lange committed
169
		{
170 171 172 173 174 175 176 177
			Action:    version,
			Name:      "version",
			Usage:     "Print version numbers",
			ArgsUsage: " ",
			Description: `
The output of this command is supposed to be machine-readable.
`,
		},
Felix Lange's avatar
Felix Lange committed
178
		{
179 180 181 182 183 184
			Action:    upload,
			Name:      "up",
			Usage:     "upload a file or directory to swarm using the HTTP API",
			ArgsUsage: " <file>",
			Description: `
"upload a file or directory to swarm using the HTTP API and prints the root hash",
185 186 187 188 189 190 191 192 193
`,
		},
		{
			Action:    list,
			Name:      "ls",
			Usage:     "list files and directories contained in a manifest",
			ArgsUsage: " <manifest> [<prefix>]",
			Description: `
Lists files and directories contained in a manifest.
194 195
`,
		},
Felix Lange's avatar
Felix Lange committed
196
		{
197 198 199 200 201 202 203 204
			Action:    hash,
			Name:      "hash",
			Usage:     "print the swarm hash of a file or directory",
			ArgsUsage: " <file>",
			Description: `
Prints the swarm hash of file or directory.
`,
		},
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
		{
			Name:      "manifest",
			Usage:     "update a MANIFEST",
			ArgsUsage: "manifest COMMAND",
			Description: `
Updates a MANIFEST by adding/removing/updating the hash of a path.
`,
			Subcommands: []cli.Command{
				{
					Action:    add,
					Name:      "add",
					Usage:     "add a new path to the manifest",
					ArgsUsage: "<MANIFEST> <path> <hash> [<content-type>]",
					Description: `
Adds a new path to the manifest
`,
				},
				{
					Action:    update,
					Name:      "update",
					Usage:     "update the hash for an already existing path in the manifest",
					ArgsUsage: "<MANIFEST> <path> <newhash> [<newcontent-type>]",
					Description: `
Update the hash for an already existing path in the manifest
`,
				},
				{
					Action:    remove,
					Name:      "remove",
					Usage:     "removes a path from the manifest",
					ArgsUsage: "<MANIFEST> <path>",
					Description: `
Removes a path from the manifest
`,
				},
			},
		},
242 243 244 245 246 247 248 249 250
		{
			Action:    cleandb,
			Name:      "cleandb",
			Usage:     "Cleans database of corrupted entries",
			ArgsUsage: " ",
			Description: `
Cleans database of corrupted entries.
`,
		},
251 252
	}

253 254 255 256 257 258
	app.Flags = []cli.Flag{
		utils.IdentityFlag,
		utils.DataDirFlag,
		utils.BootnodesFlag,
		utils.KeyStoreDirFlag,
		utils.ListenPortFlag,
259 260
		utils.NoDiscoverFlag,
		utils.DiscoveryV5Flag,
261
		utils.NetrestrictFlag,
262 263
		utils.NodeKeyFileFlag,
		utils.NodeKeyHexFlag,
264 265
		utils.MaxPeersFlag,
		utils.NATFlag,
266 267
		utils.IPCDisabledFlag,
		utils.IPCPathFlag,
268
		utils.PasswordFileFlag,
269
		// bzzd-specific flags
270
		CorsStringFlag,
271 272
		EnsAPIFlag,
		EnsAddrFlag,
273
		SwarmConfigPathFlag,
274
		SwarmSwapEnabledFlag,
275
		SwarmSwapAPIFlag,
276
		SwarmSyncEnabledFlag,
277
		SwarmListenAddrFlag,
278 279
		SwarmPortFlag,
		SwarmAccountFlag,
280
		SwarmNetworkIdFlag,
281
		ChequebookAddrFlag,
282 283 284 285
		// upload flags
		SwarmApiFlag,
		SwarmRecursiveUploadFlag,
		SwarmWantManifestFlag,
286
		SwarmUploadDefaultPath,
287 288
		SwarmUpFromStdinFlag,
		SwarmUploadMimeType,
289 290
		//deprecated flags
		DeprecatedEthAPIFlag,
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
	}
	app.Flags = append(app.Flags, debug.Flags...)
	app.Before = func(ctx *cli.Context) error {
		runtime.GOMAXPROCS(runtime.NumCPU())
		return debug.Setup(ctx)
	}
	app.After = func(ctx *cli.Context) error {
		debug.Exit()
		return nil
	}
}

func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

310 311
func version(ctx *cli.Context) error {
	fmt.Println(strings.Title(clientIdentifier))
312
	fmt.Println("Version:", params.Version)
313 314 315 316 317 318 319 320 321 322 323
	if gitCommit != "" {
		fmt.Println("Git Commit:", gitCommit)
	}
	fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
	fmt.Println("Go Version:", runtime.Version())
	fmt.Println("OS:", runtime.GOOS)
	fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
	fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
	return nil
}

324
func bzzd(ctx *cli.Context) error {
325 326 327 328 329
	// exit if the deprecated --ethapi flag is set
	if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
		utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
	}

330 331 332 333 334 335 336
	cfg := defaultNodeConfig
	utils.SetNodeConfig(ctx, &cfg)
	stack, err := node.New(&cfg)
	if err != nil {
		utils.Fatalf("can't create node: %v", err)
	}

337 338
	registerBzzService(ctx, stack)
	utils.StartNode(stack)
339

340 341 342 343 344
	go func() {
		sigc := make(chan os.Signal, 1)
		signal.Notify(sigc, syscall.SIGTERM)
		defer signal.Stop(sigc)
		<-sigc
345
		log.Info("Got sigterm, shutting swarm down...")
346 347
		stack.Stop()
	}()
348

349
	networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
350 351
	// Add bootnodes as initial peers.
	if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
352 353
		bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
		injectBootnodes(stack.Server(), bootnodes)
354
	} else {
355 356 357
		if networkId == 3 {
			injectBootnodes(stack.Server(), testbetBootNodes)
		}
358 359 360 361 362 363
	}

	stack.Wait()
	return nil
}

364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
// detectEnsAddr determines the ENS contract address by getting both the
// version and genesis hash using the client and matching them to either
// mainnet or testnet addresses
func detectEnsAddr(client *rpc.Client) (common.Address, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var version string
	if err := client.CallContext(ctx, &version, "net_version"); err != nil {
		return common.Address{}, err
	}

	block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
	if err != nil {
		return common.Address{}, err
	}

	switch {

383
	case version == "1" && block.Hash() == params.MainnetGenesisHash:
384 385 386
		log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
		return ens.MainNetAddress, nil

387
	case version == "3" && block.Hash() == params.TestnetGenesisHash:
388 389 390 391 392 393 394 395
		log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
		return ens.TestNetAddress, nil

	default:
		return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
	}
}

396 397 398 399 400 401 402 403
func registerBzzService(ctx *cli.Context, stack *node.Node) {
	prvkey := getAccount(ctx, stack)

	chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
	bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
	if bzzdir == "" {
		bzzdir = stack.InstanceDir()
	}
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 410 411 412
	}
	bzzport := ctx.GlobalString(SwarmPortFlag.Name)
	if len(bzzport) > 0 {
		bzzconfig.Port = bzzport
	}
413 414 415
	if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
		bzzconfig.ListenAddr = bzzaddr
	}
416 417
	swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
	syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
418

419 420 421 422 423 424 425 426
	swapapi := ctx.GlobalString(SwarmSwapAPIFlag.Name)
	if swapEnabled && swapapi == "" {
		utils.Fatalf("SWAP is enabled but --swap-api is not set")
	}

	ensapi := ctx.GlobalString(EnsAPIFlag.Name)
	ensAddr := ctx.GlobalString(EnsAddrFlag.Name)

427
	cors := ctx.GlobalString(CorsStringFlag.Name)
428 429

	boot := func(ctx *node.ServiceContext) (node.Service, error) {
430 431 432 433
		var swapClient *ethclient.Client
		if swapapi != "" {
			log.Info("connecting to SWAP API", "url", swapapi)
			swapClient, err = ethclient.Dial(swapapi)
zelig's avatar
zelig committed
434
			if err != nil {
435
				return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err)
zelig's avatar
zelig committed
436
			}
437
		}
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460

		var ensClient *ethclient.Client
		if ensapi != "" {
			log.Info("connecting to ENS API", "url", ensapi)
			client, err := rpc.Dial(ensapi)
			if err != nil {
				return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err)
			}
			ensClient = ethclient.NewClient(client)

			if ensAddr != "" {
				bzzconfig.EnsRoot = common.HexToAddress(ensAddr)
			} else {
				ensAddr, err := detectEnsAddr(client)
				if err == nil {
					bzzconfig.EnsRoot = ensAddr
				} else {
					log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", bzzconfig.EnsRoot), "err", err)
				}
			}
		}

		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 468
	}
}

func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
	keyid := ctx.GlobalString(SwarmAccountFlag.Name)
469

470
	if keyid == "" {
471
		utils.Fatalf("Option %q is required", SwarmAccountFlag.Name)
472 473 474
	}
	// Try to load the arg as a hex key file.
	if key, err := crypto.LoadECDSA(keyid); err == nil {
475
		log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
476 477 478
		return key
	}
	// 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 487 488
	var a accounts.Account
	var err error
	if common.IsHexAddress(account) {
489 490 491 492 493 494 495
		a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
	} else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
		if accounts := ks.Accounts(); len(accounts) > ix {
			a = accounts[ix]
		} else {
			err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
		}
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 507 508
	for i := 0; i < 3; i++ {
		password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
		key, err := keystore.DecryptKey(keyjson, password)
509 510 511 512
		if err == nil {
			return key.PrivateKey
		}
	}
513
	utils.Fatalf("Can't decrypt swarm account key")
514 515 516
	return nil
}

517 518 519 520 521 522 523 524 525 526 527 528
// getPassPhrase retrieves the password associated with bzz account, either by fetching
// from a list of pre-loaded passwords, or by requesting it interactively from user.
func getPassPhrase(prompt string, i int, passwords []string) string {
	// non-interactive
	if len(passwords) > 0 {
		if i < len(passwords) {
			return passwords[i]
		}
		return passwords[len(passwords)-1]
	}

	// fallback to interactive mode
529 530 531 532 533
	if prompt != "" {
		fmt.Println(prompt)
	}
	password, err := console.Stdin.PromptPassword("Passphrase: ")
	if err != nil {
534
		utils.Fatalf("Failed to read passphrase: %v", err)
535 536 537 538 539 540 541 542
	}
	return password
}

func injectBootnodes(srv *p2p.Server, nodes []string) {
	for _, url := range nodes {
		n, err := discover.ParseNode(url)
		if err != nil {
543
			log.Error("Invalid swarm bootnode", "err", err)
544
			continue
545 546 547 548
		}
		srv.AddPeer(n)
	}
}