diff options
author | Felix Lange <fjl@twurst.com> | 2016-11-11 13:13:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-11 13:13:16 +0800 |
commit | 9eb6f627fa9e08573580fc5915e23d332a36071b (patch) | |
tree | 711a6f608e074df8ab514f1e269dac457ddc1fdc /cmd | |
parent | 80ea44c485c42032aa954f2a8580e3afb4aa5339 (diff) | |
parent | 8247bccf71351812cd60179c3ef0f9d596f117c1 (diff) | |
download | go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar.gz go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar.bz2 go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar.lz go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar.xz go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.tar.zst go-tangerine-9eb6f627fa9e08573580fc5915e23d332a36071b.zip |
Merge pull request #3247 from fjl/bzzd
cmd: add swarm command line tools
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/bzzd/main.go | 246 | ||||
-rw-r--r-- | cmd/bzzhash/main.go | 49 | ||||
-rw-r--r-- | cmd/bzzup/main.go | 161 | ||||
-rw-r--r-- | cmd/v5test/main.go | 101 |
4 files changed, 456 insertions, 101 deletions
diff --git a/cmd/bzzd/main.go b/cmd/bzzd/main.go new file mode 100644 index 000000000..b2f14a4a9 --- /dev/null +++ b/cmd/bzzd/main.go @@ -0,0 +1,246 @@ +// 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 ( + "crypto/ecdsa" + "fmt" + "io/ioutil" + "os" + "runtime" + "strconv" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/swarm" + bzzapi "github.com/ethereum/go-ethereum/swarm/api" + "gopkg.in/urfave/cli.v1" +) + +const clientIdentifier = "bzzd" + +var ( + gitCommit string // Git SHA1 commit hash of the release (set via linker flags) + app = utils.NewApp(gitCommit, "Ethereum Swarm server daemon") +) + +var ( + ChequebookAddrFlag = cli.StringFlag{ + Name: "chequebook", + Usage: "chequebook contract address", + } + SwarmAccountFlag = cli.StringFlag{ + Name: "bzzaccount", + Usage: "Swarm account key file", + } + SwarmPortFlag = cli.StringFlag{ + Name: "bzzport", + Usage: "Swarm local http api port", + } + SwarmConfigPathFlag = cli.StringFlag{ + Name: "bzzconfig", + Usage: "Swarm config file path (datadir/bzz)", + } + SwarmSwapDisabled = cli.BoolFlag{ + Name: "bzznoswap", + Usage: "Swarm SWAP disabled (default false)", + } + SwarmSyncDisabled = cli.BoolFlag{ + Name: "bzznosync", + Usage: "Swarm Syncing disabled (default false)", + } + EthAPI = cli.StringFlag{ + Name: "ethapi", + Usage: "URL of the Ethereum API provider", + Value: node.DefaultIPCEndpoint("geth"), + } +) + +var defaultBootnodes = []string{} + +func init() { + // Override flag defaults so bzzd can run alongside geth. + utils.ListenPortFlag.Value = 30399 + utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"} + + // Set up the cli app. + app.Commands = nil + app.Action = bzzd + app.Flags = []cli.Flag{ + utils.IdentityFlag, + utils.DataDirFlag, + utils.BootnodesFlag, + utils.KeyStoreDirFlag, + utils.ListenPortFlag, + utils.MaxPeersFlag, + utils.NATFlag, + utils.NodeKeyFileFlag, + utils.NodeKeyHexFlag, + utils.IPCDisabledFlag, + utils.IPCApiFlag, + utils.IPCPathFlag, + // bzzd-specific flags + EthAPI, + SwarmConfigPathFlag, + SwarmSwapDisabled, + SwarmSyncDisabled, + SwarmPortFlag, + SwarmAccountFlag, + ChequebookAddrFlag, + } + 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) + } +} + +func bzzd(ctx *cli.Context) error { + stack := utils.MakeNode(ctx, clientIdentifier, gitCommit) + registerBzzService(ctx, stack) + utils.StartNode(stack) + + // Add bootnodes as initial peers. + if ctx.GlobalIsSet(utils.BootnodesFlag.Name) { + injectBootnodes(stack.Server(), ctx.GlobalStringSlice(utils.BootnodesFlag.Name)) + } else { + injectBootnodes(stack.Server(), defaultBootnodes) + } + + stack.Wait() + return nil +} + +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() + } + bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey) + if err != nil { + utils.Fatalf("unable to configure swarm: %v", err) + } + bzzport := ctx.GlobalString(SwarmPortFlag.Name) + if len(bzzport) > 0 { + bzzconfig.Port = bzzport + } + swapEnabled := !ctx.GlobalBool(SwarmSwapDisabled.Name) + syncEnabled := !ctx.GlobalBool(SwarmSyncDisabled.Name) + + ethapi := ctx.GlobalString(EthAPI.Name) + if ethapi == "" { + utils.Fatalf("Option %q must not be empty", EthAPI.Name) + } + + boot := func(ctx *node.ServiceContext) (node.Service, error) { + client, err := ethclient.Dial(ethapi) + if err != nil { + utils.Fatalf("Can't connect: %v", err) + } + return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled) + } + if err := stack.Register(boot); err != nil { + utils.Fatalf("Failed to register the Swarm service: %v", err) + } +} + +func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey { + keyid := ctx.GlobalString(SwarmAccountFlag.Name) + if keyid == "" { + utils.Fatalf("Option %q is required", SwarmAccountFlag.Name) + } + // Try to load the arg as a hex key file. + if key, err := crypto.LoadECDSA(keyid); err == nil { + glog.V(logger.Info).Infof("swarm account key loaded: %#x", crypto.PubkeyToAddress(key.PublicKey)) + return key + } + // Otherwise try getting it from the keystore. + return decryptStoreAccount(stack.AccountManager(), keyid) +} + +func decryptStoreAccount(accman *accounts.Manager, account string) *ecdsa.PrivateKey { + var a accounts.Account + var err error + if common.IsHexAddress(account) { + a, err = accman.Find(accounts.Account{Address: common.HexToAddress(account)}) + } else if ix, ixerr := strconv.Atoi(account); ixerr == nil { + a, err = accman.AccountByIndex(ix) + } else { + utils.Fatalf("Can't find swarm account key %s", account) + } + if err != nil { + utils.Fatalf("Can't find swarm account key: %v", err) + } + keyjson, err := ioutil.ReadFile(a.File) + if err != nil { + utils.Fatalf("Can't load swarm account key: %v", err) + } + for i := 1; i <= 3; i++ { + passphrase := promptPassphrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i)) + key, err := accounts.DecryptKey(keyjson, passphrase) + if err == nil { + return key.PrivateKey + } + } + utils.Fatalf("Can't decrypt swarm account key") + return nil +} + +func promptPassphrase(prompt string) string { + if prompt != "" { + fmt.Println(prompt) + } + password, err := console.Stdin.PromptPassword("Passphrase: ") + if err != nil { + utils.Fatalf("Failed to read passphrase: %v", err) + } + return password +} + +func injectBootnodes(srv *p2p.Server, nodes []string) { + for _, url := range nodes { + n, err := discover.ParseNode(url) + if err != nil { + glog.Errorf("invalid bootnode %q", err) + } + srv.AddPeer(n) + } +} diff --git a/cmd/bzzhash/main.go b/cmd/bzzhash/main.go new file mode 100644 index 000000000..0ae99acc0 --- /dev/null +++ b/cmd/bzzhash/main.go @@ -0,0 +1,49 @@ +// 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/>. + +// Command bzzhash computes a swarm tree hash. +package main + +import ( + "fmt" + "os" + "runtime" + + "github.com/ethereum/go-ethereum/swarm/storage" +) + +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + if len(os.Args) < 2 { + fmt.Println("Usage: bzzhash <file name>") + os.Exit(0) + } + f, err := os.Open(os.Args[1]) + if err != nil { + fmt.Println("Error opening file " + os.Args[1]) + os.Exit(1) + } + + stat, _ := f.Stat() + chunker := storage.NewTreeChunker(storage.NewChunkerParams()) + key, err := chunker.Split(f, stat.Size(), nil, nil, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + } else { + fmt.Printf("%v\n", key) + } +} diff --git a/cmd/bzzup/main.go b/cmd/bzzup/main.go new file mode 100644 index 000000000..83d6f9b7f --- /dev/null +++ b/cmd/bzzup/main.go @@ -0,0 +1,161 @@ +// 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/>. + +// Command bzzup uploads files to the swarm HTTP API. +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "mime" + "net/http" + "os" + "path/filepath" + "strings" +) + +func main() { + var ( + bzzapiFlag = flag.String("bzzapi", "http://127.0.0.1:8500", "Swarm HTTP endpoint") + recursiveFlag = flag.Bool("recursive", false, "Upload directories recursively") + manifestFlag = flag.Bool("manifest", true, "Skip automatic manifest upload") + ) + log.SetOutput(os.Stderr) + log.SetFlags(0) + flag.Parse() + if flag.NArg() != 1 { + log.Fatal("need filename as the first and only argument") + } + + var ( + file = flag.Arg(0) + client = &client{api: *bzzapiFlag} + mroot manifest + ) + fi, err := os.Stat(file) + if err != nil { + log.Fatal(err) + } + if fi.IsDir() { + if !*recursiveFlag { + log.Fatal("argument is a directory and recursive upload is disabled") + } + mroot, err = client.uploadDirectory(file) + } else { + mroot, err = client.uploadFile(file, fi) + if *manifestFlag { + // Wrap the raw file entry in a proper manifest so both hashes get printed. + mroot = manifest{Entries: []manifest{mroot}} + } + } + if err != nil { + log.Fatalln("upload failed:", err) + } + if *manifestFlag { + hash, err := client.uploadManifest(mroot) + if err != nil { + log.Fatalln("manifest upload failed:", err) + } + mroot.Hash = hash + } + + // Print the manifest. This is the only output to stdout. + mrootJSON, _ := json.MarshalIndent(mroot, "", " ") + fmt.Println(string(mrootJSON)) +} + +// client wraps interaction with the swarm HTTP gateway. +type client struct { + api string +} + +// manifest is the JSON representation of a swarm manifest. +type manifest struct { + Hash string `json:"hash,omitempty"` + ContentType string `json:"contentType,omitempty"` + Path string `json:"path,omitempty"` + Entries []manifest `json:"entries,omitempty"` +} + +func (c *client) uploadFile(file string, fi os.FileInfo) (manifest, error) { + hash, err := c.uploadFileContent(file, fi) + m := manifest{ + Hash: hash, + ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())), + } + return m, err +} + +func (c *client) uploadDirectory(dir string) (manifest, error) { + dirm := manifest{} + prefix := filepath.ToSlash(dir) + "/" + err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || fi.IsDir() { + return err + } + if !strings.HasPrefix(path, dir) { + return fmt.Errorf("path %s outside directory %s", path, dir) + } + entry, err := c.uploadFile(path, fi) + entry.Path = strings.TrimPrefix(filepath.ToSlash(path), prefix) + dirm.Entries = append(dirm.Entries, entry) + return err + }) + return dirm, err +} + +func (c *client) uploadFileContent(file string, fi os.FileInfo) (string, error) { + fd, err := os.Open(file) + if err != nil { + return "", err + } + defer fd.Close() + log.Printf("uploading file %s (%d bytes)", file, fi.Size()) + return c.postRaw("application/octet-stream", fi.Size(), fd) +} + +func (c *client) uploadManifest(m manifest) (string, error) { + jsm, err := json.Marshal(m) + if err != nil { + panic(err) + } + log.Println("uploading manifest") + return c.postRaw("application/json", int64(len(jsm)), ioutil.NopCloser(bytes.NewReader(jsm))) +} + +func (c *client) postRaw(mimetype string, size int64, body io.ReadCloser) (string, error) { + req, err := http.NewRequest("POST", c.api+"/bzzr:/", body) + if err != nil { + return "", err + } + req.Header.Set("content-type", mimetype) + req.ContentLength = size + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + return "", fmt.Errorf("bad status: %s", resp.Status) + } + content, err := ioutil.ReadAll(resp.Body) + return string(content), err +} diff --git a/cmd/v5test/main.go b/cmd/v5test/main.go deleted file mode 100644 index 1daff56f8..000000000 --- a/cmd/v5test/main.go +++ /dev/null @@ -1,101 +0,0 @@ -// 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/>. - -// bootnode runs a bootstrap node for the Ethereum Discovery Protocol. -package main - -import ( - "flag" - "fmt" - "math/rand" - "strconv" - "time" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/p2p/discv5" - "github.com/ethereum/go-ethereum/p2p/nat" -) - -func main() { - var ( - listenPort = flag.Int("addr", 31000, "beginning of listening port range") - natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)") - count = flag.Int("count", 1, "number of v5 topic discovery test nodes (adds default bootnodes to form a test network)") - regtopic = flag.String("reg", "", "topic to register on the network") - looktopic = flag.String("search", "", "topic to search on the network") - ) - flag.Var(glog.GetVerbosity(), "verbosity", "log verbosity (0-9)") - flag.Var(glog.GetVModule(), "vmodule", "log verbosity pattern") - glog.SetToStderr(true) - flag.Parse() - - natm, err := nat.Parse(*natdesc) - if err != nil { - utils.Fatalf("-nat: %v", err) - } - - for i := 0; i < *count; i++ { - listenAddr := ":" + strconv.Itoa(*listenPort+i) - - nodeKey, err := crypto.GenerateKey() - if err != nil { - utils.Fatalf("could not generate key: %v", err) - } - - if net, err := discv5.ListenUDP(nodeKey, listenAddr, natm, ""); err != nil { - utils.Fatalf("%v", err) - } else { - if err := net.SetFallbackNodes(discv5.BootNodes); err != nil { - utils.Fatalf("%v", err) - } - go func() { - if *looktopic == "" { - for i := 0; i < 20; i++ { - time.Sleep(time.Millisecond * time.Duration(2000+rand.Intn(2001))) - net.BucketFill() - } - } - switch { - case *regtopic != "": - // register topic - fmt.Println("Starting topic register") - stop := make(chan struct{}) - net.RegisterTopic(discv5.Topic(*regtopic), stop) - case *looktopic != "": - // search topic - fmt.Println("Starting topic search") - stop := make(chan struct{}) - found := make(chan string, 100) - go net.SearchTopic(discv5.Topic(*looktopic), stop, found) - for s := range found { - fmt.Println(time.Now(), s) - } - default: - // just keep doing lookups - for { - time.Sleep(time.Millisecond * time.Duration(40000+rand.Intn(40001))) - net.BucketFill() - } - } - }() - } - fmt.Printf("Started test node #%d with public key %v\n", i, discv5.PubkeyID(&nodeKey.PublicKey)) - } - - select {} -} |