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 }