aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorRafael Matias <rafael@skyle.net>2019-06-04 21:35:36 +0800
committerPéter Szilágyi <peterke@gmail.com>2019-06-04 21:35:36 +0800
commit42b81f94adba9aae7f7727951f35e92184b1eedb (patch)
tree73f3004ba6cd922bc54579fe886a7b3cb1df1741 /cmd
parent15f24ff1896835e6ab908b0d17c1cc36b300c408 (diff)
downloadgo-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar.gz
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar.bz2
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar.lz
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar.xz
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.tar.zst
go-tangerine-42b81f94adba9aae7f7727951f35e92184b1eedb.zip
swarm: code cleanup, move to ethersphere/swarm (#19661)
Diffstat (limited to 'cmd')
-rw-r--r--cmd/swarm/access.go297
-rw-r--r--cmd/swarm/access_test.go617
-rw-r--r--cmd/swarm/bootnodes.go24
-rw-r--r--cmd/swarm/config.go451
-rw-r--r--cmd/swarm/config_test.go575
-rw-r--r--cmd/swarm/db.go239
-rw-r--r--cmd/swarm/download.go112
-rw-r--r--cmd/swarm/explore.go60
-rw-r--r--cmd/swarm/export_test.go287
-rw-r--r--cmd/swarm/feeds.go238
-rw-r--r--cmd/swarm/feeds_test.go196
-rw-r--r--cmd/swarm/flags.go189
-rw-r--r--cmd/swarm/fs.go162
-rw-r--r--cmd/swarm/fs_test.go260
-rw-r--r--cmd/swarm/global-store/explorer.go66
-rw-r--r--cmd/swarm/global-store/explorer_test.go254
-rw-r--r--cmd/swarm/global-store/global_store.go120
-rw-r--r--cmd/swarm/global-store/global_store_test.go207
-rw-r--r--cmd/swarm/global-store/main.go124
-rw-r--r--cmd/swarm/global-store/run_test.go49
-rw-r--r--cmd/swarm/hash.go116
-rw-r--r--cmd/swarm/list.go70
-rw-r--r--cmd/swarm/main.go475
-rw-r--r--cmd/swarm/manifest.go353
-rw-r--r--cmd/swarm/manifest_test.go597
-rw-r--r--cmd/swarm/mimegen/generator.go124
-rw-r--r--cmd/swarm/mimegen/mime.types1828
-rw-r--r--cmd/swarm/run_test.go502
-rw-r--r--cmd/swarm/swarm-smoke/feed_upload_and_sync.go291
-rw-r--r--cmd/swarm/swarm-smoke/main.go195
-rw-r--r--cmd/swarm/swarm-smoke/sliding_window.go149
-rw-r--r--cmd/swarm/swarm-smoke/upload_and_sync.go376
-rw-r--r--cmd/swarm/swarm-smoke/upload_speed.go73
-rw-r--r--cmd/swarm/swarm-smoke/util.go231
-rw-r--r--cmd/swarm/swarm-snapshot/create.go160
-rw-r--r--cmd/swarm/swarm-snapshot/create_test.go140
-rw-r--r--cmd/swarm/swarm-snapshot/main.go83
-rw-r--r--cmd/swarm/swarm-snapshot/run_test.go49
-rw-r--r--cmd/swarm/testdata/datastore_fixture.go1390
-rw-r--r--cmd/swarm/upload.go188
-rw-r--r--cmd/swarm/upload_test.go359
41 files changed, 0 insertions, 12276 deletions
diff --git a/cmd/swarm/access.go b/cmd/swarm/access.go
deleted file mode 100644
index cc0cc8201..000000000
--- a/cmd/swarm/access.go
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2018 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/rand"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "strings"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/swarm/api"
- "github.com/ethereum/go-ethereum/swarm/api/client"
- "gopkg.in/urfave/cli.v1"
-)
-
-var (
- salt = make([]byte, 32)
- accessCommand = cli.Command{
- CustomHelpTemplate: helpTemplate,
- Name: "access",
- Usage: "encrypts a reference and embeds it into a root manifest",
- ArgsUsage: "<ref>",
- Description: "encrypts a reference and embeds it into a root manifest",
- Subcommands: []cli.Command{
- {
- CustomHelpTemplate: helpTemplate,
- Name: "new",
- Usage: "encrypts a reference and embeds it into a root manifest",
- ArgsUsage: "<ref>",
- Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
- Subcommands: []cli.Command{
- {
- Action: accessNewPass,
- CustomHelpTemplate: helpTemplate,
- Flags: []cli.Flag{
- utils.PasswordFileFlag,
- SwarmDryRunFlag,
- },
- Name: "pass",
- Usage: "encrypts a reference with a password and embeds it into a root manifest",
- ArgsUsage: "<ref>",
- Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
- },
- {
- Action: accessNewPK,
- CustomHelpTemplate: helpTemplate,
- Flags: []cli.Flag{
- utils.PasswordFileFlag,
- SwarmDryRunFlag,
- SwarmAccessGrantKeyFlag,
- },
- Name: "pk",
- Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
- ArgsUsage: "<ref>",
- Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
- },
- {
- Action: accessNewACT,
- CustomHelpTemplate: helpTemplate,
- Flags: []cli.Flag{
- SwarmAccessGrantKeysFlag,
- SwarmDryRunFlag,
- utils.PasswordFileFlag,
- },
- Name: "act",
- Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
- ArgsUsage: "<ref>",
- Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
- },
- },
- },
- },
- }
-)
-
-func init() {
- if _, err := io.ReadFull(rand.Reader, salt); err != nil {
- panic("reading from crypto/rand failed: " + err.Error())
- }
-}
-
-func accessNewPass(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 1 {
- utils.Fatalf("Expected 1 argument - the ref")
- }
-
- var (
- ae *api.AccessEntry
- accessKey []byte
- err error
- ref = args[0]
- password = getPassPhrase("", 0, makePasswordList(ctx))
- dryRun = ctx.Bool(SwarmDryRunFlag.Name)
- )
- accessKey, ae, err = api.DoPassword(ctx, password, salt)
- if err != nil {
- utils.Fatalf("error getting session key: %v", err)
- }
- m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
- if err != nil {
- utils.Fatalf("had an error generating the manifest: %v", err)
- }
- if dryRun {
- err = printManifests(m, nil)
- if err != nil {
- utils.Fatalf("had an error printing the manifests: %v", err)
- }
- } else {
- err = uploadManifests(ctx, m, nil)
- if err != nil {
- utils.Fatalf("had an error uploading the manifests: %v", err)
- }
- }
-}
-
-func accessNewPK(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 1 {
- utils.Fatalf("Expected 1 argument - the ref")
- }
-
- var (
- ae *api.AccessEntry
- sessionKey []byte
- err error
- ref = args[0]
- privateKey = getPrivKey(ctx)
- granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name)
- dryRun = ctx.Bool(SwarmDryRunFlag.Name)
- )
- sessionKey, ae, err = api.DoPK(ctx, privateKey, granteePublicKey, salt)
- if err != nil {
- utils.Fatalf("error getting session key: %v", err)
- }
- m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
- if err != nil {
- utils.Fatalf("had an error generating the manifest: %v", err)
- }
- if dryRun {
- err = printManifests(m, nil)
- if err != nil {
- utils.Fatalf("had an error printing the manifests: %v", err)
- }
- } else {
- err = uploadManifests(ctx, m, nil)
- if err != nil {
- utils.Fatalf("had an error uploading the manifests: %v", err)
- }
- }
-}
-
-func accessNewACT(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 1 {
- utils.Fatalf("Expected 1 argument - the ref")
- }
-
- var (
- ae *api.AccessEntry
- actManifest *api.Manifest
- accessKey []byte
- err error
- ref = args[0]
- pkGrantees []string
- passGrantees []string
- pkGranteesFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
- passGranteesFilename = ctx.String(utils.PasswordFileFlag.Name)
- privateKey = getPrivKey(ctx)
- dryRun = ctx.Bool(SwarmDryRunFlag.Name)
- )
- if pkGranteesFilename == "" && passGranteesFilename == "" {
- utils.Fatalf("you have to provide either a grantee public-keys file or an encryption passwords file (or both)")
- }
-
- if pkGranteesFilename != "" {
- bytes, err := ioutil.ReadFile(pkGranteesFilename)
- if err != nil {
- utils.Fatalf("had an error reading the grantee public key list")
- }
- pkGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
- }
-
- if passGranteesFilename != "" {
- bytes, err := ioutil.ReadFile(passGranteesFilename)
- if err != nil {
- utils.Fatalf("could not read password filename: %v", err)
- }
- passGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
- }
- accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees)
- if err != nil {
- utils.Fatalf("error generating ACT manifest: %v", err)
- }
-
- if err != nil {
- utils.Fatalf("error getting session key: %v", err)
- }
- m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
- if err != nil {
- utils.Fatalf("error generating root access manifest: %v", err)
- }
-
- if dryRun {
- err = printManifests(m, actManifest)
- if err != nil {
- utils.Fatalf("had an error printing the manifests: %v", err)
- }
- } else {
- err = uploadManifests(ctx, m, actManifest)
- if err != nil {
- utils.Fatalf("had an error uploading the manifests: %v", err)
- }
- }
-}
-
-func printManifests(rootAccessManifest, actManifest *api.Manifest) error {
- js, err := json.Marshal(rootAccessManifest)
- if err != nil {
- return err
- }
- fmt.Println(string(js))
-
- if actManifest != nil {
- js, err := json.Marshal(actManifest)
- if err != nil {
- return err
- }
- fmt.Println(string(js))
- }
- return nil
-}
-
-func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error {
- bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client := client.NewClient(bzzapi)
-
- var (
- key string
- err error
- )
- if actManifest != nil {
- key, err = client.UploadManifest(actManifest, false)
- if err != nil {
- return err
- }
-
- rootAccessManifest.Entries[0].Access.Act = key
- }
- key, err = client.UploadManifest(rootAccessManifest, false)
- if err != nil {
- return err
- }
- fmt.Println(key)
- return nil
-}
-
-// makePasswordList reads password lines from the file specified by the global --password flag
-// and also by the same subcommand --password flag.
-// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand.
-// Function ctx.SetGlobal is not setting the global flag value that can be accessed
-// by ctx.GlobalString using the current version of cli package.
-func makePasswordList(ctx *cli.Context) []string {
- path := ctx.GlobalString(utils.PasswordFileFlag.Name)
- if path == "" {
- path = ctx.String(utils.PasswordFileFlag.Name)
- if path == "" {
- return nil
- }
- }
- text, err := ioutil.ReadFile(path)
- if err != nil {
- utils.Fatalf("Failed to read password file: %v", err)
- }
- lines := strings.Split(string(text), "\n")
- // Sanitise DOS line endings.
- for i := range lines {
- lines[i] = strings.TrimRight(lines[i], "\r")
- }
- return lines
-}
diff --git a/cmd/swarm/access_test.go b/cmd/swarm/access_test.go
deleted file mode 100644
index 0aaaad030..000000000
--- a/cmd/swarm/access_test.go
+++ /dev/null
@@ -1,617 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "crypto/rand"
- "encoding/hex"
- "encoding/json"
- "io"
- "io/ioutil"
- gorand "math/rand"
- "net/http"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/ecies"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/swarm/api"
- swarmapi "github.com/ethereum/go-ethereum/swarm/api/client"
- "github.com/ethereum/go-ethereum/swarm/testutil"
- "golang.org/x/crypto/sha3"
-)
-
-const (
- hashRegexp = `[a-f\d]{128}`
- data = "notsorandomdata"
-)
-
-var DefaultCurve = crypto.S256()
-
-func TestACT(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- cluster := newTestCluster(t, clusterSize)
- defer cluster.Shutdown()
-
- cases := []struct {
- name string
- f func(t *testing.T, cluster *testCluster)
- }{
- {"Password", testPassword},
- {"PK", testPK},
- {"ACTWithoutBogus", testACTWithoutBogus},
- {"ACTWithBogus", testACTWithBogus},
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- tc.f(t, cluster)
- })
- }
-}
-
-// testPassword tests for the correct creation of an ACT manifest protected by a password.
-// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
-// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded
-// is then fetched through 2nd node. since the tested code is not key-aware - we can just
-// fetch from the 2nd node using HTTP BasicAuth
-func testPassword(t *testing.T, cluster *testCluster) {
- dataFilename := testutil.TempFileWithContent(t, data)
- defer os.RemoveAll(dataFilename)
-
- // upload the file with 'swarm up' and expect a hash
- up := runSwarm(t,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "up",
- "--encrypt",
- dataFilename)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
-
- if len(matches) < 1 {
- t.Fatal("no matches found")
- }
-
- ref := matches[0]
- tmp, err := ioutil.TempDir("", "swarm-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
- password := "smth"
- passwordFilename := testutil.TempFileWithContent(t, "smth")
- defer os.RemoveAll(passwordFilename)
-
- up = runSwarm(t,
- "access",
- "new",
- "pass",
- "--dry-run",
- "--password",
- passwordFilename,
- ref,
- )
-
- _, matches = up.ExpectRegexp(".+")
- up.ExpectExit()
-
- if len(matches) == 0 {
- t.Fatalf("stdout not matched")
- }
-
- var m api.Manifest
-
- err = json.Unmarshal([]byte(matches[0]), &m)
- if err != nil {
- t.Fatalf("unmarshal manifest: %v", err)
- }
-
- if len(m.Entries) != 1 {
- t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
- }
-
- e := m.Entries[0]
-
- ct := "application/bzz-manifest+json"
- if e.ContentType != ct {
- t.Errorf("expected %q content type, got %q", ct, e.ContentType)
- }
-
- if e.Access == nil {
- t.Fatal("manifest access is nil")
- }
-
- a := e.Access
-
- if a.Type != "pass" {
- t.Errorf(`got access type %q, expected "pass"`, a.Type)
- }
- if len(a.Salt) < 32 {
- t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
- }
- if a.KdfParams == nil {
- t.Fatal("manifest access kdf params is nil")
- }
- if a.Publisher != "" {
- t.Fatal("should be empty")
- }
-
- client := swarmapi.NewClient(cluster.Nodes[0].URL)
-
- hash, err := client.UploadManifest(&m, false)
- if err != nil {
- t.Fatal(err)
- }
-
- url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
-
- httpClient := &http.Client{}
- response, err := httpClient.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- if response.StatusCode != http.StatusUnauthorized {
- t.Fatal("should be a 401")
- }
- authHeader := response.Header.Get("WWW-Authenticate")
- if authHeader == "" {
- t.Fatal("should be something here")
- }
-
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.SetBasicAuth("", password)
-
- response, err = http.DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- defer response.Body.Close()
-
- if response.StatusCode != http.StatusOK {
- t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode)
- }
- d, err := ioutil.ReadAll(response.Body)
- if err != nil {
- t.Fatal(err)
- }
- if string(d) != data {
- t.Errorf("expected decrypted data %q, got %q", data, string(d))
- }
-
- wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng")
- defer os.RemoveAll(wrongPasswordFilename)
-
- //download file with 'swarm down' with wrong password
- up = runSwarm(t,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "down",
- "bzz:/"+hash,
- tmp,
- "--password",
- wrongPasswordFilename)
-
- _, matches = up.ExpectRegexp("unauthorized")
- if len(matches) != 1 && matches[0] != "unauthorized" {
- t.Fatal(`"unauthorized" not found in output"`)
- }
- up.ExpectExit()
-}
-
-// testPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee).
-// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
-// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears.
-// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware,
-// the test will fail if the proxy's given private key is not granted on the ACT.
-func testPK(t *testing.T, cluster *testCluster) {
- dataFilename := testutil.TempFileWithContent(t, data)
- defer os.RemoveAll(dataFilename)
-
- // upload the file with 'swarm up' and expect a hash
- up := runSwarm(t,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "up",
- "--encrypt",
- dataFilename)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
-
- if len(matches) < 1 {
- t.Fatal("no matches found")
- }
-
- ref := matches[0]
- pk := cluster.Nodes[0].PrivateKey
- granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
-
- publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
- if err != nil {
- t.Fatal(err)
- }
-
- passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
- defer os.RemoveAll(passwordFilename)
-
- _, publisherAccount := getTestAccount(t, publisherDir)
- up = runSwarm(t,
- "--bzzaccount",
- publisherAccount.Address.String(),
- "--password",
- passwordFilename,
- "--datadir",
- publisherDir,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "access",
- "new",
- "pk",
- "--dry-run",
- "--grant-key",
- hex.EncodeToString(granteePubKey),
- ref,
- )
-
- _, matches = up.ExpectRegexp(".+")
- up.ExpectExit()
-
- if len(matches) == 0 {
- t.Fatalf("stdout not matched")
- }
-
- //get the public key from the publisher directory
- publicKeyFromDataDir := runSwarm(t,
- "--bzzaccount",
- publisherAccount.Address.String(),
- "--password",
- passwordFilename,
- "--datadir",
- publisherDir,
- "print-keys",
- "--compressed",
- )
- _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
- publicKeyFromDataDir.ExpectExit()
- pkComp := strings.Split(publicKeyString[0], "=")[1]
- var m api.Manifest
-
- err = json.Unmarshal([]byte(matches[0]), &m)
- if err != nil {
- t.Fatalf("unmarshal manifest: %v", err)
- }
-
- if len(m.Entries) != 1 {
- t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
- }
-
- e := m.Entries[0]
-
- ct := "application/bzz-manifest+json"
- if e.ContentType != ct {
- t.Errorf("expected %q content type, got %q", ct, e.ContentType)
- }
-
- if e.Access == nil {
- t.Fatal("manifest access is nil")
- }
-
- a := e.Access
-
- if a.Type != "pk" {
- t.Errorf(`got access type %q, expected "pk"`, a.Type)
- }
- if len(a.Salt) < 32 {
- t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
- }
- if a.KdfParams != nil {
- t.Fatal("manifest access kdf params should be nil")
- }
- if a.Publisher != pkComp {
- t.Fatal("publisher key did not match")
- }
- client := swarmapi.NewClient(cluster.Nodes[0].URL)
-
- hash, err := client.UploadManifest(&m, false)
- if err != nil {
- t.Fatal(err)
- }
-
- httpClient := &http.Client{}
-
- url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
- response, err := httpClient.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- if response.StatusCode != http.StatusOK {
- t.Fatal("should be a 200")
- }
- d, err := ioutil.ReadAll(response.Body)
- if err != nil {
- t.Fatal(err)
- }
- if string(d) != data {
- t.Errorf("expected decrypted data %q, got %q", data, string(d))
- }
-}
-
-// testACTWithoutBogus tests the creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized)
-func testACTWithoutBogus(t *testing.T, cluster *testCluster) {
- testACT(t, cluster, 0)
-}
-
-// testACTWithBogus tests the creation of the ACT manifest end-to-end, with 100 bogus entries (i.e. 100 EC keys + default scenario = 3 nodes 1 unauthorized = 103 keys in the ACT manifest)
-func testACTWithBogus(t *testing.T, cluster *testCluster) {
- testACT(t, cluster, 100)
-}
-
-// testACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection
-// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data
-// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access.
-// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails.
-// the publisher uploads through one of the nodes then disappears.
-func testACT(t *testing.T, cluster *testCluster, bogusEntries int) {
- var uploadThroughNode = cluster.Nodes[0]
- client := swarmapi.NewClient(uploadThroughNode.URL)
-
- r1 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
- nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`)
- dataFilename := testutil.TempFileWithContent(t, data)
- defer os.RemoveAll(dataFilename)
-
- // upload the file with 'swarm up' and expect a hash
- up := runSwarm(t,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "up",
- "--encrypt",
- dataFilename)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
-
- if len(matches) < 1 {
- t.Fatal("no matches found")
- }
-
- ref := matches[0]
- var grantees []string
- for i, v := range cluster.Nodes {
- if i == nodeToSkip {
- continue
- }
- pk := v.PrivateKey
- granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
- grantees = append(grantees, hex.EncodeToString(granteePubKey))
- }
-
- if bogusEntries > 0 {
- var bogusGrantees []string
-
- for i := 0; i < bogusEntries; i++ {
- prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil)
- if err != nil {
- t.Fatal(err)
- }
- bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey)))
- }
- r2 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
- for i := 0; i < len(grantees); i++ {
- insertAtIdx := r2.Intn(len(bogusGrantees))
- bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...)
- }
- grantees = bogusGrantees
- }
- granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n"))
- defer os.RemoveAll(granteesPubkeyListFile)
-
- publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(publisherDir)
-
- passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
- defer os.RemoveAll(passwordFilename)
- actPasswordFilename := testutil.TempFileWithContent(t, "smth")
- defer os.RemoveAll(actPasswordFilename)
- _, publisherAccount := getTestAccount(t, publisherDir)
- up = runSwarm(t,
- "--bzzaccount",
- publisherAccount.Address.String(),
- "--password",
- passwordFilename,
- "--datadir",
- publisherDir,
- "--bzzapi",
- cluster.Nodes[0].URL,
- "access",
- "new",
- "act",
- "--grant-keys",
- granteesPubkeyListFile,
- "--password",
- actPasswordFilename,
- ref,
- )
-
- _, matches = up.ExpectRegexp(`[a-f\d]{64}`)
- up.ExpectExit()
-
- if len(matches) == 0 {
- t.Fatalf("stdout not matched")
- }
-
- //get the public key from the publisher directory
- publicKeyFromDataDir := runSwarm(t,
- "--bzzaccount",
- publisherAccount.Address.String(),
- "--password",
- passwordFilename,
- "--datadir",
- publisherDir,
- "print-keys",
- "--compressed",
- )
- _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
- publicKeyFromDataDir.ExpectExit()
- pkComp := strings.Split(publicKeyString[0], "=")[1]
-
- hash := matches[0]
- m, _, err := client.DownloadManifest(hash)
- if err != nil {
- t.Fatalf("unmarshal manifest: %v", err)
- }
-
- if len(m.Entries) != 1 {
- t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
- }
-
- e := m.Entries[0]
-
- ct := "application/bzz-manifest+json"
- if e.ContentType != ct {
- t.Errorf("expected %q content type, got %q", ct, e.ContentType)
- }
-
- if e.Access == nil {
- t.Fatal("manifest access is nil")
- }
-
- a := e.Access
-
- if a.Type != "act" {
- t.Fatalf(`got access type %q, expected "act"`, a.Type)
- }
- if len(a.Salt) < 32 {
- t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
- }
-
- if a.Publisher != pkComp {
- t.Fatal("publisher key did not match")
- }
- httpClient := &http.Client{}
-
- // all nodes except the skipped node should be able to decrypt the content
- for i, node := range cluster.Nodes {
- log.Debug("trying to fetch from node", "node index", i)
-
- url := node.URL + "/" + "bzz:/" + hash
- response, err := httpClient.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- log.Debug("got response from node", "response code", response.StatusCode)
-
- if i == nodeToSkip {
- log.Debug("reached node to skip", "status code", response.StatusCode)
-
- if response.StatusCode != http.StatusUnauthorized {
- t.Fatalf("should be a 401")
- }
-
- // try downloading using a password instead, using the unauthorized node
- passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1)
- response, err = httpClient.Get(passwordUrl)
- if err != nil {
- t.Fatal(err)
- }
- if response.StatusCode != http.StatusOK {
- t.Fatal("should be a 200")
- }
-
- // now try with the wrong password, expect 401
- passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1)
- response, err = httpClient.Get(passwordUrl)
- if err != nil {
- t.Fatal(err)
- }
- if response.StatusCode != http.StatusUnauthorized {
- t.Fatal("should be a 401")
- }
- continue
- }
-
- if response.StatusCode != http.StatusOK {
- t.Fatal("should be a 200")
- }
- d, err := ioutil.ReadAll(response.Body)
- if err != nil {
- t.Fatal(err)
- }
- if string(d) != data {
- t.Errorf("expected decrypted data %q, got %q", data, string(d))
- }
- }
-}
-
-// TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to
-// the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md
-func TestKeypairSanity(t *testing.T) {
- salt := make([]byte, 32)
- if _, err := io.ReadFull(rand.Reader, salt); err != nil {
- t.Fatalf("reading from crypto/rand failed: %v", err.Error())
- }
- sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d"
-
- for i, v := range []struct {
- publisherPriv string
- granteePub string
- }{
- {
- publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459",
- granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a",
- },
- {
- publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d",
- granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db",
- },
- } {
- b, _ := hex.DecodeString(v.granteePub)
- granteePub, _ := crypto.DecompressPubkey(b)
- publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv)
-
- ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt)
- if err != nil {
- t.Fatal(err)
- }
-
- hasher := sha3.NewLegacyKeccak256()
- hasher.Write(salt)
- shared, err := hex.DecodeString(sharedSecret)
- if err != nil {
- t.Fatal(err)
- }
- hasher.Write(shared)
- sum := hasher.Sum(nil)
-
- if !bytes.Equal(ssKey, sum) {
- t.Fatalf("%d: got a session key mismatch", i)
- }
- }
-}
diff --git a/cmd/swarm/bootnodes.go b/cmd/swarm/bootnodes.go
deleted file mode 100644
index ce3cd5288..000000000
--- a/cmd/swarm/bootnodes.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2018 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
-
-var SwarmBootnodes = []string{
- // EF Swarm Bootnode - AWS - eu-central-1
- "enode://4c113504601930bf2000c29bcd98d1716b6167749f58bad703bae338332fe93cc9d9204f08afb44100dc7bea479205f5d162df579f9a8f76f8b402d339709023@3.122.203.99:30301",
- // EF Swarm Bootnode - AWS - us-west-2
- "enode://89f2ede3371bff1ad9f2088f2012984e280287a4e2b68007c2a6ad994909c51886b4a8e9e2ecc97f9910aca538398e0a5804b0ee80a187fde1ba4f32626322ba@52.35.212.179:30301",
-}
diff --git a/cmd/swarm/config.go b/cmd/swarm/config.go
deleted file mode 100644
index e4b333549..000000000
--- a/cmd/swarm/config.go
+++ /dev/null
@@ -1,451 +0,0 @@
-// Copyright 2017 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 (
- "errors"
- "fmt"
- "io"
- "os"
- "reflect"
- "strconv"
- "strings"
- "time"
- "unicode"
-
- cli "gopkg.in/urfave/cli.v1"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/naoina/toml"
-
- bzzapi "github.com/ethereum/go-ethereum/swarm/api"
-)
-
-var (
- //flag definition for the dumpconfig command
- DumpConfigCommand = cli.Command{
- Action: utils.MigrateFlags(dumpConfig),
- Name: "dumpconfig",
- Usage: "Show configuration values",
- ArgsUsage: "",
- Flags: app.Flags,
- Category: "MISCELLANEOUS COMMANDS",
- Description: `The dumpconfig command shows configuration values.`,
- }
-
- //flag definition for the config file command
- SwarmTomlConfigPathFlag = cli.StringFlag{
- Name: "config",
- Usage: "TOML configuration file",
- }
-)
-
-//constants for environment variables
-const (
- SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
- SwarmEnvAccount = "SWARM_ACCOUNT"
- SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
- SwarmEnvPort = "SWARM_PORT"
- SwarmEnvNetworkID = "SWARM_NETWORK_ID"
- SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE"
- SwarmEnvSwapAPI = "SWARM_SWAP_API"
- SwarmEnvSyncDisable = "SWARM_SYNC_DISABLE"
- SwarmEnvSyncUpdateDelay = "SWARM_ENV_SYNC_UPDATE_DELAY"
- SwarmEnvMaxStreamPeerServers = "SWARM_ENV_MAX_STREAM_PEER_SERVERS"
- SwarmEnvLightNodeEnable = "SWARM_LIGHT_NODE_ENABLE"
- SwarmEnvDeliverySkipCheck = "SWARM_DELIVERY_SKIP_CHECK"
- SwarmEnvENSAPI = "SWARM_ENS_API"
- SwarmEnvENSAddr = "SWARM_ENS_ADDR"
- SwarmEnvCORS = "SWARM_CORS"
- SwarmEnvBootnodes = "SWARM_BOOTNODES"
- SwarmEnvPSSEnable = "SWARM_PSS_ENABLE"
- SwarmEnvStorePath = "SWARM_STORE_PATH"
- SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY"
- SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY"
- SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE"
- SwarmAccessPassword = "SWARM_ACCESS_PASSWORD"
- SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH"
- SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API"
- GethEnvDataDir = "GETH_DATADIR"
-)
-
-// These settings ensure that TOML keys use the same names as Go struct fields.
-var tomlSettings = toml.Config{
- NormFieldName: func(rt reflect.Type, key string) string {
- return key
- },
- FieldToKey: func(rt reflect.Type, field string) string {
- return field
- },
- MissingField: func(rt reflect.Type, field string) error {
- link := ""
- if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" {
- link = fmt.Sprintf(", check github.com/ethereum/go-ethereum/swarm/api/config.go for available fields")
- }
- return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
- },
-}
-
-//before booting the swarm node, build the configuration
-func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
- //start by creating a default config
- config = bzzapi.NewConfig()
- //first load settings from config file (if provided)
- config, err = configFileOverride(config, ctx)
- if err != nil {
- return nil, err
- }
- //override settings provided by environment variables
- config = envVarsOverride(config)
- //override settings provided by command line
- config = cmdLineOverride(config, ctx)
- //validate configuration parameters
- err = validateConfig(config)
-
- return
-}
-
-//finally, after the configuration build phase is finished, initialize
-func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
- //at this point, all vars should be set in the Config
- //get the account for the provided swarm account
- prvkey := getAccount(config.BzzAccount, ctx, stack)
- //set the resolved config path (geth --datadir)
- config.Path = expandPath(stack.InstanceDir())
- //finally, initialize the configuration
- err := config.Init(prvkey, nodeconfig.NodeKey())
- if err != nil {
- return err
- }
- //configuration phase completed here
- log.Debug("Starting Swarm with the following parameters:")
- //after having created the config, print it to screen
- log.Debug(printConfig(config))
- return nil
-}
-
-//configFileOverride overrides the current config with the config file, if a config file has been provided
-func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) {
- var err error
-
- //only do something if the -config flag has been set
- if ctx.GlobalIsSet(SwarmTomlConfigPathFlag.Name) {
- var filepath string
- if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" {
- utils.Fatalf("Config file flag provided with invalid file path")
- }
- var f *os.File
- f, err = os.Open(filepath)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- //decode the TOML file into a Config struct
- //note that we are decoding into the existing defaultConfig;
- //if an entry is not present in the file, the default entry is kept
- err = tomlSettings.NewDecoder(f).Decode(&config)
- // Add file name to errors that have a line number.
- if _, ok := err.(*toml.LineError); ok {
- err = errors.New(filepath + ", " + err.Error())
- }
- }
- return config, err
-}
-
-// cmdLineOverride overrides the current config with whatever is provided through the command line
-// most values are not allowed a zero value (empty string), if not otherwise noted
-func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
- if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
- currentConfig.BzzAccount = keyid
- }
-
- if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" {
- currentConfig.Contract = common.HexToAddress(chbookaddr)
- }
-
- if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
- id, err := strconv.ParseUint(networkid, 10, 64)
- if err != nil {
- utils.Fatalf("invalid cli flag %s: %v", SwarmNetworkIdFlag.Name, err)
- }
- if id != 0 {
- currentConfig.NetworkID = id
- }
- }
-
- if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
- if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
- currentConfig.Path = expandPath(datadir)
- }
- }
-
- bzzport := ctx.GlobalString(SwarmPortFlag.Name)
- if len(bzzport) > 0 {
- currentConfig.Port = bzzport
- }
-
- if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
- currentConfig.ListenAddr = bzzaddr
- }
-
- if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) {
- currentConfig.SwapEnabled = true
- }
-
- if ctx.GlobalIsSet(SwarmSyncDisabledFlag.Name) {
- currentConfig.SyncEnabled = false
- }
-
- if d := ctx.GlobalDuration(SwarmSyncUpdateDelay.Name); d > 0 {
- currentConfig.SyncUpdateDelay = d
- }
-
- // any value including 0 is acceptable
- currentConfig.MaxStreamPeerServers = ctx.GlobalInt(SwarmMaxStreamPeerServersFlag.Name)
-
- if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) {
- currentConfig.LightNodeEnabled = true
- }
-
- if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) {
- currentConfig.DeliverySkipCheck = true
- }
-
- currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name)
- if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
- utils.Fatalf(SwarmErrSwapSetNoAPI)
- }
-
- if ctx.GlobalIsSet(EnsAPIFlag.Name) {
- ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name)
- // preserve backward compatibility to disable ENS with --ens-api=""
- if len(ensAPIs) == 1 && ensAPIs[0] == "" {
- ensAPIs = nil
- }
- for i := range ensAPIs {
- ensAPIs[i] = expandPath(ensAPIs[i])
- }
-
- currentConfig.EnsAPIs = ensAPIs
- }
-
- if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" {
- currentConfig.Cors = cors
- }
-
- if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
- currentConfig.ChunkDbPath = storePath
- }
-
- if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 {
- currentConfig.DbCapacity = storeCapacity
- }
-
- if ctx.GlobalIsSet(SwarmStoreCacheCapacity.Name) {
- currentConfig.CacheCapacity = ctx.GlobalUint(SwarmStoreCacheCapacity.Name)
- }
-
- if ctx.GlobalIsSet(SwarmBootnodeModeFlag.Name) {
- currentConfig.BootnodeMode = ctx.GlobalBool(SwarmBootnodeModeFlag.Name)
- }
-
- if ctx.GlobalIsSet(SwarmGlobalStoreAPIFlag.Name) {
- currentConfig.GlobalStoreAPI = ctx.GlobalString(SwarmGlobalStoreAPIFlag.Name)
- }
-
- return currentConfig
-
-}
-
-// envVarsOverride overrides the current config with whatver is provided in environment variables
-// most values are not allowed a zero value (empty string), if not otherwise noted
-func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
- if keyid := os.Getenv(SwarmEnvAccount); keyid != "" {
- currentConfig.BzzAccount = keyid
- }
-
- if chbookaddr := os.Getenv(SwarmEnvChequebookAddr); chbookaddr != "" {
- currentConfig.Contract = common.HexToAddress(chbookaddr)
- }
-
- if networkid := os.Getenv(SwarmEnvNetworkID); networkid != "" {
- id, err := strconv.ParseUint(networkid, 10, 64)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvNetworkID, err)
- }
- if id != 0 {
- currentConfig.NetworkID = id
- }
- }
-
- if datadir := os.Getenv(GethEnvDataDir); datadir != "" {
- currentConfig.Path = expandPath(datadir)
- }
-
- bzzport := os.Getenv(SwarmEnvPort)
- if len(bzzport) > 0 {
- currentConfig.Port = bzzport
- }
-
- if bzzaddr := os.Getenv(SwarmEnvListenAddr); bzzaddr != "" {
- currentConfig.ListenAddr = bzzaddr
- }
-
- if swapenable := os.Getenv(SwarmEnvSwapEnable); swapenable != "" {
- swap, err := strconv.ParseBool(swapenable)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSwapEnable, err)
- }
- currentConfig.SwapEnabled = swap
- }
-
- if syncdisable := os.Getenv(SwarmEnvSyncDisable); syncdisable != "" {
- sync, err := strconv.ParseBool(syncdisable)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncDisable, err)
- }
- currentConfig.SyncEnabled = !sync
- }
-
- if v := os.Getenv(SwarmEnvDeliverySkipCheck); v != "" {
- skipCheck, err := strconv.ParseBool(v)
- if err != nil {
- currentConfig.DeliverySkipCheck = skipCheck
- }
- }
-
- if v := os.Getenv(SwarmEnvSyncUpdateDelay); v != "" {
- d, err := time.ParseDuration(v)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncUpdateDelay, err)
- }
- currentConfig.SyncUpdateDelay = d
- }
-
- if max := os.Getenv(SwarmEnvMaxStreamPeerServers); max != "" {
- m, err := strconv.Atoi(max)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvMaxStreamPeerServers, err)
- }
- currentConfig.MaxStreamPeerServers = m
- }
-
- if lne := os.Getenv(SwarmEnvLightNodeEnable); lne != "" {
- lightnode, err := strconv.ParseBool(lne)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvLightNodeEnable, err)
- }
- currentConfig.LightNodeEnabled = lightnode
- }
-
- if swapapi := os.Getenv(SwarmEnvSwapAPI); swapapi != "" {
- currentConfig.SwapAPI = swapapi
- }
-
- if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
- utils.Fatalf(SwarmErrSwapSetNoAPI)
- }
-
- if ensapi := os.Getenv(SwarmEnvENSAPI); ensapi != "" {
- currentConfig.EnsAPIs = strings.Split(ensapi, ",")
- }
-
- if ensaddr := os.Getenv(SwarmEnvENSAddr); ensaddr != "" {
- currentConfig.EnsRoot = common.HexToAddress(ensaddr)
- }
-
- if cors := os.Getenv(SwarmEnvCORS); cors != "" {
- currentConfig.Cors = cors
- }
-
- if bm := os.Getenv(SwarmEnvBootnodeMode); bm != "" {
- bootnodeMode, err := strconv.ParseBool(bm)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmEnvBootnodeMode, err)
- }
- currentConfig.BootnodeMode = bootnodeMode
- }
-
- if api := os.Getenv(SwarmGlobalstoreAPI); api != "" {
- currentConfig.GlobalStoreAPI = api
- }
-
- return currentConfig
-}
-
-// dumpConfig is the dumpconfig command.
-// writes a default config to STDOUT
-func dumpConfig(ctx *cli.Context) error {
- cfg, err := buildConfig(ctx)
- if err != nil {
- utils.Fatalf(fmt.Sprintf("Uh oh - dumpconfig triggered an error %v", err))
- }
- comment := ""
- out, err := tomlSettings.Marshal(&cfg)
- if err != nil {
- return err
- }
- io.WriteString(os.Stdout, comment)
- os.Stdout.Write(out)
- return nil
-}
-
-//validate configuration parameters
-func validateConfig(cfg *bzzapi.Config) (err error) {
- for _, ensAPI := range cfg.EnsAPIs {
- if ensAPI != "" {
- if err := validateEnsAPIs(ensAPI); err != nil {
- return fmt.Errorf("invalid format [tld:][contract-addr@]url for ENS API endpoint configuration %q: %v", ensAPI, err)
- }
- }
- }
- return nil
-}
-
-//validate EnsAPIs configuration parameter
-func validateEnsAPIs(s string) (err error) {
- // missing contract address
- if strings.HasPrefix(s, "@") {
- return errors.New("missing contract address")
- }
- // missing url
- if strings.HasSuffix(s, "@") {
- return errors.New("missing url")
- }
- // missing tld
- if strings.HasPrefix(s, ":") {
- return errors.New("missing tld")
- }
- // missing url
- if strings.HasSuffix(s, ":") {
- return errors.New("missing url")
- }
- return nil
-}
-
-//print a Config as string
-func printConfig(config *bzzapi.Config) string {
- out, err := tomlSettings.Marshal(&config)
- if err != nil {
- return fmt.Sprintf("Something is not right with the configuration: %v", err)
- }
- return string(out)
-}
diff --git a/cmd/swarm/config_test.go b/cmd/swarm/config_test.go
deleted file mode 100644
index 484f6dec3..000000000
--- a/cmd/swarm/config_test.go
+++ /dev/null
@@ -1,575 +0,0 @@
-// Copyright 2017 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 (
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "os"
- "os/exec"
- "testing"
- "time"
-
- "github.com/docker/docker/pkg/reexec"
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm"
- "github.com/ethereum/go-ethereum/swarm/api"
-)
-
-func TestConfigDump(t *testing.T) {
- swarm := runSwarm(t, "dumpconfig")
- defaultConf := api.NewConfig()
- out, err := tomlSettings.Marshal(&defaultConf)
- if err != nil {
- t.Fatal(err)
- }
- swarm.Expect(string(out))
- swarm.ExpectExit()
-}
-
-func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
- flags := []string{
- fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
- fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
- fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
- }
-
- swarm := runSwarm(t, flags...)
- swarm.Expect("Fatal: " + SwarmErrSwapSetNoAPI + "\n")
- swarm.ExpectExit()
-}
-
-func TestConfigFailsNoBzzAccount(t *testing.T) {
- flags := []string{
- fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
- fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
- }
-
- swarm := runSwarm(t, flags...)
- swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
- swarm.ExpectExit()
-}
-
-func TestConfigCmdLineOverrides(t *testing.T) {
- dir, err := ioutil.TempDir("", "bzztest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- conf, account := getTestAccount(t, dir)
- node := &testNode{Dir: dir}
-
- // assign ports
- httpPort, err := assignTCPPort()
- if err != nil {
- t.Fatal(err)
- }
-
- flags := []string{
- fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
- fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
- fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
- fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
- fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
- fmt.Sprintf("--%s", SwarmDeliverySkipCheckFlag.Name),
- fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
- fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
- }
- node.Cmd = runSwarm(t, flags...)
- node.Cmd.InputLine(testPassphrase)
- defer func() {
- if t.Failed() {
- node.Shutdown()
- }
- }()
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- if info.Port != httpPort {
- t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
- }
-
- if info.NetworkID != 42 {
- t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkID)
- }
-
- if info.SyncEnabled {
- t.Fatal("Expected Sync to be disabled, but is true")
- }
-
- if !info.DeliverySkipCheck {
- t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
- }
-
- if info.Cors != "*" {
- t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
- }
-
- node.Shutdown()
-}
-
-func TestConfigFileOverrides(t *testing.T) {
-
- // assign ports
- httpPort, err := assignTCPPort()
- if err != nil {
- t.Fatal(err)
- }
-
- //create a config file
- //first, create a default conf
- defaultConf := api.NewConfig()
- //change some values in order to test if they have been loaded
- defaultConf.SyncEnabled = false
- defaultConf.DeliverySkipCheck = true
- defaultConf.NetworkID = 54
- defaultConf.Port = httpPort
- defaultConf.DbCapacity = 9000000
- defaultConf.HiveParams.KeepAliveInterval = 6000000000
- defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
- //defaultConf.SyncParams.KeyBufferSize = 512
- //create a TOML string
- out, err := tomlSettings.Marshal(&defaultConf)
- if err != nil {
- t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
- }
- //create file
- f, err := ioutil.TempFile("", "testconfig.toml")
- if err != nil {
- t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
- }
- //write file
- _, err = f.WriteString(string(out))
- if err != nil {
- t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
- }
- f.Sync()
-
- dir, err := ioutil.TempDir("", "bzztest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- conf, account := getTestAccount(t, dir)
- node := &testNode{Dir: dir}
-
- flags := []string{
- fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
- fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
- fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
- fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
- }
- node.Cmd = runSwarm(t, flags...)
- node.Cmd.InputLine(testPassphrase)
- defer func() {
- if t.Failed() {
- node.Shutdown()
- }
- }()
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- if info.Port != httpPort {
- t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
- }
-
- if info.NetworkID != 54 {
- t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
- }
-
- if info.SyncEnabled {
- t.Fatal("Expected Sync to be disabled, but is true")
- }
-
- if !info.DeliverySkipCheck {
- t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
- }
-
- if info.DbCapacity != 9000000 {
- t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
- }
-
- if info.HiveParams.KeepAliveInterval != 6000000000 {
- t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
- }
-
- if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
- t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
- }
-
- // if info.SyncParams.KeyBufferSize != 512 {
- // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
- // }
-
- node.Shutdown()
-}
-
-func TestConfigEnvVars(t *testing.T) {
- // assign ports
- httpPort, err := assignTCPPort()
- if err != nil {
- t.Fatal(err)
- }
-
- envVars := os.Environ()
- envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort))
- envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999"))
- envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*"))
- envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncDisabledFlag.EnvVar, "true"))
- envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmDeliverySkipCheckFlag.EnvVar, "true"))
-
- dir, err := ioutil.TempDir("", "bzztest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- conf, account := getTestAccount(t, dir)
- node := &testNode{Dir: dir}
- flags := []string{
- fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
- "--ens-api", "",
- "--datadir", dir,
- "--ipcpath", conf.IPCPath,
- }
-
- //node.Cmd = runSwarm(t,flags...)
- //node.Cmd.cmd.Env = envVars
- //the above assignment does not work, so we need a custom Cmd here in order to pass envVars:
- cmd := &exec.Cmd{
- Path: reexec.Self(),
- Args: append([]string{"swarm-test"}, flags...),
- Stderr: os.Stderr,
- Stdout: os.Stdout,
- }
- cmd.Env = envVars
- //stdout, err := cmd.StdoutPipe()
- //if err != nil {
- // t.Fatal(err)
- //}
- //stdout = bufio.NewReader(stdout)
- var stdin io.WriteCloser
- if stdin, err = cmd.StdinPipe(); err != nil {
- t.Fatal(err)
- }
- if err := cmd.Start(); err != nil {
- t.Fatal(err)
- }
-
- //cmd.InputLine(testPassphrase)
- io.WriteString(stdin, testPassphrase+"\n")
- defer func() {
- if t.Failed() {
- node.Shutdown()
- cmd.Process.Kill()
- }
- }()
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
-
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- if info.Port != httpPort {
- t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
- }
-
- if info.NetworkID != 999 {
- t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkID)
- }
-
- if info.Cors != "*" {
- t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
- }
-
- if info.SyncEnabled {
- t.Fatal("Expected Sync to be disabled, but is true")
- }
-
- if !info.DeliverySkipCheck {
- t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
- }
-
- node.Shutdown()
- cmd.Process.Kill()
-}
-
-func TestConfigCmdLineOverridesFile(t *testing.T) {
-
- // assign ports
- httpPort, err := assignTCPPort()
- if err != nil {
- t.Fatal(err)
- }
-
- //create a config file
- //first, create a default conf
- defaultConf := api.NewConfig()
- //change some values in order to test if they have been loaded
- defaultConf.SyncEnabled = true
- defaultConf.NetworkID = 54
- defaultConf.Port = "8588"
- defaultConf.DbCapacity = 9000000
- defaultConf.HiveParams.KeepAliveInterval = 6000000000
- defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
- //defaultConf.SyncParams.KeyBufferSize = 512
- //create a TOML file
- out, err := tomlSettings.Marshal(&defaultConf)
- if err != nil {
- t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
- }
- //write file
- fname := "testconfig.toml"
- f, err := ioutil.TempFile("", fname)
- if err != nil {
- t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
- }
- defer os.Remove(fname)
- //write file
- _, err = f.WriteString(string(out))
- if err != nil {
- t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
- }
- f.Sync()
-
- dir, err := ioutil.TempDir("", "bzztest")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- conf, account := getTestAccount(t, dir)
- node := &testNode{Dir: dir}
-
- expectNetworkId := uint64(77)
-
- flags := []string{
- fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77",
- fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
- fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
- fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
- fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
- fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
- fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
- }
- node.Cmd = runSwarm(t, flags...)
- node.Cmd.InputLine(testPassphrase)
- defer func() {
- if t.Failed() {
- node.Shutdown()
- }
- }()
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- if info.Port != httpPort {
- t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
- }
-
- if info.NetworkID != expectNetworkId {
- t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkID)
- }
-
- if info.SyncEnabled {
- t.Fatal("Expected Sync to be disabled, but is true")
- }
-
- if info.DbCapacity != 9000000 {
- t.Fatalf("Expected Capacity to be %d, got %d", 9000000, info.DbCapacity)
- }
-
- if info.HiveParams.KeepAliveInterval != 6000000000 {
- t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
- }
-
- if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
- t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
- }
-
- // if info.SyncParams.KeyBufferSize != 512 {
- // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
- // }
-
- node.Shutdown()
-}
-
-func TestConfigValidate(t *testing.T) {
- for _, c := range []struct {
- cfg *api.Config
- err string
- }{
- {
- cfg: &api.Config{EnsAPIs: []string{
- "/data/testnet/geth.ipc",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "http://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "ws://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "test:/data/testnet/geth.ipc",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "test:ws://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:12344",
- }},
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "eth:",
- }},
- err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"eth:\": missing url",
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "314159265dD8dbb310642f98f50C066173C1259b@",
- }},
- err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"314159265dD8dbb310642f98f50C066173C1259b@\": missing url",
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- ":314159265dD8dbb310642f98f50C066173C1259",
- }},
- err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \":314159265dD8dbb310642f98f50C066173C1259\": missing tld",
- },
- {
- cfg: &api.Config{EnsAPIs: []string{
- "@/data/testnet/geth.ipc",
- }},
- err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"@/data/testnet/geth.ipc\": missing contract address",
- },
- } {
- err := validateConfig(c.cfg)
- if c.err != "" && err.Error() != c.err {
- t.Errorf("expected error %q, got %q", c.err, err)
- }
- if c.err == "" && err != nil {
- t.Errorf("unexpected error %q", err)
- }
- }
-}
-
-func assignTCPPort() (string, error) {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- return "", err
- }
- l.Close()
- _, port, err := net.SplitHostPort(l.Addr().String())
- if err != nil {
- return "", err
- }
- return port, nil
-}
diff --git a/cmd/swarm/db.go b/cmd/swarm/db.go
deleted file mode 100644
index b0e9f367f..000000000
--- a/cmd/swarm/db.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2017 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 (
- "archive/tar"
- "bytes"
- "encoding/binary"
- "encoding/hex"
- "fmt"
- "io"
- "os"
- "path/filepath"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/swarm/chunk"
- "github.com/ethereum/go-ethereum/swarm/storage/localstore"
- "github.com/syndtr/goleveldb/leveldb"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "gopkg.in/urfave/cli.v1"
-)
-
-var legacyKeyIndex = byte(0)
-var keyData = byte(6)
-
-type dpaDBIndex struct {
- Idx uint64
- Access uint64
-}
-
-var dbCommand = cli.Command{
- Name: "db",
- CustomHelpTemplate: helpTemplate,
- Usage: "manage the local chunk database",
- ArgsUsage: "db COMMAND",
- Description: "Manage the local chunk database",
- Subcommands: []cli.Command{
- {
- Action: dbExport,
- CustomHelpTemplate: helpTemplate,
- Name: "export",
- Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
- ArgsUsage: "<chunkdb> <file>",
- Description: `
-Export a local chunk database as a tar archive (use - to send to stdout).
-
- swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
-
-The export may be quite large, consider piping the output through the Unix
-pv(1) tool to get a progress bar:
-
- swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
-`,
- },
- {
- Action: dbImport,
- CustomHelpTemplate: helpTemplate,
- Name: "import",
- Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
- ArgsUsage: "<chunkdb> <file>",
- Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
-
- swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
-
-The import may be quite large, consider piping the input through the Unix
-pv(1) tool to get a progress bar:
-
- pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
- Flags: []cli.Flag{
- SwarmLegacyFlag,
- },
- },
- },
-}
-
-func dbExport(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 3 {
- utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to write the tar archive to, - for stdout) and the base key")
- }
-
- var out io.Writer
- if args[1] == "-" {
- out = os.Stdout
- } else {
- f, err := os.Create(args[1])
- if err != nil {
- utils.Fatalf("error opening output file: %s", err)
- }
- defer f.Close()
- out = f
- }
-
- isLegacy := localstore.IsLegacyDatabase(args[0])
- if isLegacy {
- count, err := exportLegacy(args[0], common.Hex2Bytes(args[2]), out)
- if err != nil {
- utils.Fatalf("error exporting legacy local chunk database: %s", err)
- }
-
- log.Info(fmt.Sprintf("successfully exported %d chunks from legacy db", count))
- return
- }
-
- store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
- if err != nil {
- utils.Fatalf("error opening local chunk database: %s", err)
- }
- defer store.Close()
-
- count, err := store.Export(out)
- if err != nil {
- utils.Fatalf("error exporting local chunk database: %s", err)
- }
-
- log.Info(fmt.Sprintf("successfully exported %d chunks", count))
-}
-
-func dbImport(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 3 {
- utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to read the tar archive from, - for stdin) and the base key")
- }
-
- legacy := ctx.IsSet(SwarmLegacyFlag.Name)
-
- store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
- if err != nil {
- utils.Fatalf("error opening local chunk database: %s", err)
- }
- defer store.Close()
-
- var in io.Reader
- if args[1] == "-" {
- in = os.Stdin
- } else {
- f, err := os.Open(args[1])
- if err != nil {
- utils.Fatalf("error opening input file: %s", err)
- }
- defer f.Close()
- in = f
- }
-
- count, err := store.Import(in, legacy)
- if err != nil {
- utils.Fatalf("error importing local chunk database: %s", err)
- }
-
- log.Info(fmt.Sprintf("successfully imported %d chunks", count))
-}
-
-func openLDBStore(path string, basekey []byte) (*localstore.DB, error) {
- if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
- return nil, fmt.Errorf("invalid chunkdb path: %s", err)
- }
-
- return localstore.New(path, basekey, nil)
-}
-
-func decodeIndex(data []byte, index *dpaDBIndex) error {
- dec := rlp.NewStream(bytes.NewReader(data), 0)
- return dec.Decode(index)
-}
-
-func getDataKey(idx uint64, po uint8) []byte {
- key := make([]byte, 10)
- key[0] = keyData
- key[1] = po
- binary.BigEndian.PutUint64(key[2:], idx)
-
- return key
-}
-
-func exportLegacy(path string, basekey []byte, out io.Writer) (int64, error) {
- tw := tar.NewWriter(out)
- defer tw.Close()
- db, err := leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 128})
- if err != nil {
- return 0, err
- }
- defer db.Close()
-
- it := db.NewIterator(nil, nil)
- defer it.Release()
- var count int64
- for ok := it.Seek([]byte{legacyKeyIndex}); ok; ok = it.Next() {
- key := it.Key()
- if (key == nil) || (key[0] != legacyKeyIndex) {
- break
- }
-
- var index dpaDBIndex
-
- hash := key[1:]
- decodeIndex(it.Value(), &index)
-
- po := uint8(chunk.Proximity(basekey, hash))
-
- datakey := getDataKey(index.Idx, po)
- data, err := db.Get(datakey, nil)
- if err != nil {
- log.Crit(fmt.Sprintf("Chunk %x found but could not be accessed: %v, %x", key, err, datakey))
- continue
- }
-
- hdr := &tar.Header{
- Name: hex.EncodeToString(hash),
- Mode: 0644,
- Size: int64(len(data)),
- }
- if err := tw.WriteHeader(hdr); err != nil {
- return count, err
- }
- if _, err := tw.Write(data); err != nil {
- return count, err
- }
- count++
- }
-
- return count, nil
-}
diff --git a/cmd/swarm/download.go b/cmd/swarm/download.go
deleted file mode 100644
index fcbefa020..000000000
--- a/cmd/swarm/download.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2018 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 (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/swarm/api"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- "gopkg.in/urfave/cli.v1"
-)
-
-var downloadCommand = cli.Command{
- Action: download,
- Name: "down",
- Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
- Usage: "downloads a swarm manifest or a file inside a manifest",
- ArgsUsage: " <uri> [<dir>]",
- Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
-}
-
-func download(ctx *cli.Context) {
- log.Debug("downloading content using swarm down")
- args := ctx.Args()
- dest := "."
-
- switch len(args) {
- case 0:
- utils.Fatalf("Usage: swarm down [options] <bzz locator> [<destination path>]")
- case 1:
- log.Trace(fmt.Sprintf("swarm down: no destination path - assuming working dir"))
- default:
- log.Trace(fmt.Sprintf("destination path arg: %s", args[1]))
- if absDest, err := filepath.Abs(args[1]); err == nil {
- dest = absDest
- } else {
- utils.Fatalf("could not get download path: %v", err)
- }
- }
-
- var (
- bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- isRecursive = ctx.Bool(SwarmRecursiveFlag.Name)
- client = swarm.NewClient(bzzapi)
- )
-
- if fi, err := os.Stat(dest); err == nil {
- if isRecursive && !fi.Mode().IsDir() {
- utils.Fatalf("destination path is not a directory!")
- }
- } else {
- if !os.IsNotExist(err) {
- utils.Fatalf("could not stat path: %v", err)
- }
- }
-
- uri, err := api.Parse(args[0])
- if err != nil {
- utils.Fatalf("could not parse uri argument: %v", err)
- }
-
- dl := func(credentials string) error {
- // assume behaviour according to --recursive switch
- if isRecursive {
- if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
- if err == swarm.ErrUnauthorized {
- return err
- }
- return fmt.Errorf("directory %s: %v", uri.Path, err)
- }
- } else {
- // we are downloading a file
- log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
-
- err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
- if err != nil {
- if err == swarm.ErrUnauthorized {
- return err
- }
- return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err)
- }
- }
- return nil
- }
- if passwords := makePasswordList(ctx); passwords != nil {
- password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
- err = dl(password)
- } else {
- err = dl("")
- }
- if err != nil {
- utils.Fatalf("download: %v", err)
- }
-}
diff --git a/cmd/swarm/explore.go b/cmd/swarm/explore.go
deleted file mode 100644
index 9566213e4..000000000
--- a/cmd/swarm/explore.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 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 (
- "context"
- "fmt"
- "os"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/swarm/chunk"
- "github.com/ethereum/go-ethereum/swarm/storage"
- "gopkg.in/urfave/cli.v1"
-)
-
-var hashesCommand = cli.Command{
- Action: hashes,
- CustomHelpTemplate: helpTemplate,
- Name: "hashes",
- Usage: "print all hashes of a file to STDOUT",
- ArgsUsage: "<file>",
- Description: "Prints all hashes of a file to STDOUT",
-}
-
-func hashes(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) < 1 {
- utils.Fatalf("Usage: swarm hashes <file name>")
- }
- f, err := os.Open(args[0])
- if err != nil {
- utils.Fatalf("Error opening file " + args[1])
- }
- defer f.Close()
-
- fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
- refs, err := fileStore.GetAllReferences(context.TODO(), f, false)
- if err != nil {
- utils.Fatalf("%v\n", err)
- } else {
- for _, r := range refs {
- fmt.Println(r.String())
- }
- }
-}
diff --git a/cmd/swarm/export_test.go b/cmd/swarm/export_test.go
deleted file mode 100644
index ca82cfd4c..000000000
--- a/cmd/swarm/export_test.go
+++ /dev/null
@@ -1,287 +0,0 @@
-// Copyright 2018 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 (
- "archive/tar"
- "bytes"
- "compress/gzip"
- "crypto/md5"
- "encoding/base64"
- "encoding/hex"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path"
- "runtime"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/cmd/swarm/testdata"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/swarm"
- "github.com/ethereum/go-ethereum/swarm/testutil"
-)
-
-const (
- DATABASE_FIXTURE_BZZ_ACCOUNT = "0aa159029fa13ffa8fa1c6fff6ebceface99d6a4"
- DATABASE_FIXTURE_PASSWORD = "pass"
- FIXTURE_DATADIR_PREFIX = "swarm/bzz-0aa159029fa13ffa8fa1c6fff6ebceface99d6a4"
- FixtureBaseKey = "a9f22b3d77b4bdf5f3eefce995d6c8e7cecf2636f20956f08a0d1ed95adb52ad"
-)
-
-// TestCLISwarmExportImport perform the following test:
-// 1. runs swarm node
-// 2. uploads a random file
-// 3. runs an export of the local datastore
-// 4. runs a second swarm node
-// 5. imports the exported datastore
-// 6. fetches the uploaded random file from the second node
-func TestCLISwarmExportImport(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
- cluster := newTestCluster(t, 1)
-
- // generate random 1mb file
- content := testutil.RandomBytes(1, 1000000)
- fileName := testutil.TempFileWithContent(t, string(content))
- defer os.Remove(fileName)
-
- // upload the file with 'swarm up' and expect a hash
- up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", fileName)
- _, matches := up.ExpectRegexp(`[a-f\d]{64}`)
- up.ExpectExit()
- hash := matches[0]
-
- var info swarm.Info
- if err := cluster.Nodes[0].Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- cluster.Stop()
- defer cluster.Cleanup()
-
- // generate an export.tar
- exportCmd := runSwarm(t, "db", "export", info.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info.BzzKey, "0x"))
- exportCmd.ExpectExit()
-
- // start second cluster
- cluster2 := newTestCluster(t, 1)
-
- var info2 swarm.Info
- if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- // stop second cluster, so that we close LevelDB
- cluster2.Stop()
- defer cluster2.Cleanup()
-
- // import the export.tar
- importCmd := runSwarm(t, "db", "import", info2.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x"))
- importCmd.ExpectExit()
-
- // spin second cluster back up
- cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x"))
-
- // try to fetch imported file
- res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + hash)
- if err != nil {
- t.Fatal(err)
- }
-
- if res.StatusCode != 200 {
- t.Fatalf("expected HTTP status %d, got %s", 200, res.Status)
- }
-
- // compare downloaded file with the generated random file
- mustEqualFiles(t, bytes.NewReader(content), res.Body)
-}
-
-// TestExportLegacyToNew checks that an old database gets imported correctly into the new localstore structure
-// The test sequence is as follows:
-// 1. unpack database fixture to tmp dir
-// 2. try to open with new swarm binary that should complain about old database
-// 3. export from old database
-// 4. remove the chunks folder
-// 5. import the dump
-// 6. file should be accessible
-func TestExportLegacyToNew(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip() // this should be reenabled once the appveyor tests underlying issue is fixed
- }
- /*
- fixture bzz account 0aa159029fa13ffa8fa1c6fff6ebceface99d6a4
- */
- const UPLOADED_FILE_MD5_HASH = "a001fdae53ba50cae584b8b02b06f821"
- const UPLOADED_HASH = "67a86082ee0ea1bc7dd8d955bb1e14d04f61d55ae6a4b37b3d0296a3a95e454a"
- tmpdir, err := ioutil.TempDir("", "swarm-test")
- log.Trace("running legacy datastore migration test", "temp dir", tmpdir)
- defer os.RemoveAll(tmpdir)
- if err != nil {
- t.Fatal(err)
- }
- inflateBase64Gzip(t, testdata.DATADIR_MIGRATION_FIXTURE, tmpdir)
-
- tmpPassword := testutil.TempFileWithContent(t, DATABASE_FIXTURE_PASSWORD)
- defer os.Remove(tmpPassword)
-
- flags := []string{
- "--datadir", tmpdir,
- "--bzzaccount", DATABASE_FIXTURE_BZZ_ACCOUNT,
- "--password", tmpPassword,
- }
-
- newSwarmOldDb := runSwarm(t, flags...)
- _, matches := newSwarmOldDb.ExpectRegexp(".+")
- newSwarmOldDb.ExpectExit()
-
- if len(matches) == 0 {
- t.Fatalf("stdout not matched")
- }
-
- if newSwarmOldDb.ExitStatus() == 0 {
- t.Fatal("should error")
- }
- t.Log("exporting legacy database")
- actualDataDir := path.Join(tmpdir, FIXTURE_DATADIR_PREFIX)
- exportCmd := runSwarm(t, "--verbosity", "5", "db", "export", actualDataDir+"/chunks", tmpdir+"/export.tar", FixtureBaseKey)
- exportCmd.ExpectExit()
-
- stat, err := os.Stat(tmpdir + "/export.tar")
- if err != nil {
- t.Fatal(err)
- }
-
- // make some silly size assumption
- if stat.Size() < 90000 {
- t.Fatal("export size too small")
- }
- log.Info("removing chunk datadir")
- err = os.RemoveAll(path.Join(actualDataDir, "chunks"))
- if err != nil {
- t.Fatal(err)
- }
-
- // start second cluster
- cluster2 := newTestCluster(t, 1)
- var info2 swarm.Info
- if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil {
- t.Fatal(err)
- }
-
- // stop second cluster, so that we close LevelDB
- cluster2.Stop()
- defer cluster2.Cleanup()
-
- // import the export.tar
- importCmd := runSwarm(t, "db", "import", "--legacy", info2.Path+"/chunks", tmpdir+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x"))
- importCmd.ExpectExit()
-
- // spin second cluster back up
- cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x"))
- t.Log("trying to http get the file")
- // try to fetch imported file
- res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + UPLOADED_HASH)
- if err != nil {
- t.Fatal(err)
- }
-
- if res.StatusCode != 200 {
- t.Fatalf("expected HTTP status %d, got %s", 200, res.Status)
- }
- h := md5.New()
- if _, err := io.Copy(h, res.Body); err != nil {
- t.Fatal(err)
- }
-
- sum := h.Sum(nil)
-
- b, err := hex.DecodeString(UPLOADED_FILE_MD5_HASH)
- if err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(sum, b) {
- t.Fatal("should be equal")
- }
-}
-
-func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
- h := md5.New()
- upLen, err := io.Copy(h, up)
- if err != nil {
- t.Fatal(err)
- }
- upHash := h.Sum(nil)
- h.Reset()
- downLen, err := io.Copy(h, down)
- if err != nil {
- t.Fatal(err)
- }
- downHash := h.Sum(nil)
-
- if !bytes.Equal(upHash, downHash) || upLen != downLen {
- t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen)
- }
-}
-
-func inflateBase64Gzip(t *testing.T, base64File, directory string) {
- t.Helper()
-
- f := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64File))
- gzf, err := gzip.NewReader(f)
- if err != nil {
- t.Fatal(err)
- }
-
- tarReader := tar.NewReader(gzf)
-
- for {
- header, err := tarReader.Next()
- if err == io.EOF {
- break
- }
-
- if err != nil {
- t.Fatal(err)
- }
-
- name := header.Name
-
- switch header.Typeflag {
- case tar.TypeDir:
- err := os.Mkdir(path.Join(directory, name), os.ModePerm)
- if err != nil {
- t.Fatal(err)
- }
- case tar.TypeReg:
- file, err := os.Create(path.Join(directory, name))
- if err != nil {
- t.Fatal(err)
- }
- if _, err := io.Copy(file, tarReader); err != nil {
- t.Fatal(err)
- }
- file.Close()
- default:
- t.Fatal("shouldn't happen")
- }
- }
-}
diff --git a/cmd/swarm/feeds.go b/cmd/swarm/feeds.go
deleted file mode 100644
index 6cd971a92..000000000
--- a/cmd/swarm/feeds.go
+++ /dev/null
@@ -1,238 +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/>.
-
-// Command feed allows the user to create and update signed Swarm feeds
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- "github.com/ethereum/go-ethereum/swarm/storage/feed"
- "gopkg.in/urfave/cli.v1"
-)
-
-var feedCommand = cli.Command{
- CustomHelpTemplate: helpTemplate,
- Name: "feed",
- Usage: "(Advanced) Create and update Swarm Feeds",
- ArgsUsage: "<create|update|info>",
- Description: "Works with Swarm Feeds",
- Subcommands: []cli.Command{
- {
- Action: feedCreateManifest,
- CustomHelpTemplate: helpTemplate,
- Name: "create",
- Usage: "creates and publishes a new feed manifest",
- Description: `creates and publishes a new feed manifest pointing to a specified user's updates about a particular topic.
- The feed topic can be built in the following ways:
- * use --topic to set the topic to an arbitrary binary hex string.
- * use --name to set the topic to a human-readable name.
- For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
- * use both --topic and --name to create named subtopics.
- For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
- this feed tracks a discussion about that contract.
- The --user flag allows to have this manifest refer to a user other than yourself. If not specified,
- it will then default to your local account (--bzzaccount)`,
- Flags: []cli.Flag{SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
- },
- {
- Action: feedUpdate,
- CustomHelpTemplate: helpTemplate,
- Name: "update",
- Usage: "updates the content of an existing Swarm Feed",
- ArgsUsage: "<0x Hex data>",
- Description: `publishes a new update on the specified topic
- The feed topic can be built in the following ways:
- * use --topic to set the topic to an arbitrary binary hex string.
- * use --name to set the topic to a human-readable name.
- For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
- * use both --topic and --name to create named subtopics.
- For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
- this feed tracks a discussion about that contract.
-
- If you have a manifest, you can specify it with --manifest to refer to the feed,
- instead of using --topic / --name
- `,
- Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag},
- },
- {
- Action: feedInfo,
- CustomHelpTemplate: helpTemplate,
- Name: "info",
- Usage: "obtains information about an existing Swarm feed",
- Description: `obtains information about an existing Swarm feed
- The topic can be specified directly with the --topic flag as an hex string
- If no topic is specified, the default topic (zero) will be used
- The --name flag can be used to specify subtopics with a specific name.
- The --user flag allows to refer to a user other than yourself. If not specified,
- it will then default to your local account (--bzzaccount)
- If you have a manifest, you can specify it with --manifest instead of --topic / --name / ---user
- to refer to the feed`,
- Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
- },
- },
-}
-
-func NewGenericSigner(ctx *cli.Context) feed.Signer {
- return feed.NewGenericSigner(getPrivKey(ctx))
-}
-
-func getTopic(ctx *cli.Context) (topic feed.Topic) {
- var name = ctx.String(SwarmFeedNameFlag.Name)
- var relatedTopic = ctx.String(SwarmFeedTopicFlag.Name)
- var relatedTopicBytes []byte
- var err error
-
- if relatedTopic != "" {
- relatedTopicBytes, err = hexutil.Decode(relatedTopic)
- if err != nil {
- utils.Fatalf("Error parsing topic: %s", err)
- }
- }
-
- topic, err = feed.NewTopic(name, relatedTopicBytes)
- if err != nil {
- utils.Fatalf("Error parsing topic: %s", err)
- }
- return topic
-}
-
-// swarm feed create <frequency> [--name <name>] [--data <0x Hexdata> [--multihash=false]]
-// swarm feed update <Manifest Address or ENS domain> <0x Hexdata> [--multihash=false]
-// swarm feed info <Manifest Address or ENS domain>
-
-func feedCreateManifest(ctx *cli.Context) {
- var (
- bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client = swarm.NewClient(bzzapi)
- )
-
- newFeedUpdateRequest := feed.NewFirstRequest(getTopic(ctx))
- newFeedUpdateRequest.Feed.User = feedGetUser(ctx)
-
- manifestAddress, err := client.CreateFeedWithManifest(newFeedUpdateRequest)
- if err != nil {
- utils.Fatalf("Error creating feed manifest: %s", err.Error())
- return
- }
- fmt.Println(manifestAddress) // output manifest address to the user in a single line (useful for other commands to pick up)
-
-}
-
-func feedUpdate(ctx *cli.Context) {
- args := ctx.Args()
-
- var (
- bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client = swarm.NewClient(bzzapi)
- manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
- )
-
- if len(args) < 1 {
- fmt.Println("Incorrect number of arguments")
- cli.ShowCommandHelpAndExit(ctx, "update", 1)
- return
- }
-
- signer := NewGenericSigner(ctx)
-
- data, err := hexutil.Decode(args[0])
- if err != nil {
- utils.Fatalf("Error parsing data: %s", err.Error())
- return
- }
-
- var updateRequest *feed.Request
- var query *feed.Query
-
- if manifestAddressOrDomain == "" {
- query = new(feed.Query)
- query.User = signer.Address()
- query.Topic = getTopic(ctx)
- }
-
- // Retrieve a feed update request
- updateRequest, err = client.GetFeedRequest(query, manifestAddressOrDomain)
- if err != nil {
- utils.Fatalf("Error retrieving feed status: %s", err.Error())
- }
-
- // Check that the provided signer matches the request to sign
- if updateRequest.User != signer.Address() {
- utils.Fatalf("Signer address does not match the update request")
- }
-
- // set the new data
- updateRequest.SetData(data)
-
- // sign update
- if err = updateRequest.Sign(signer); err != nil {
- utils.Fatalf("Error signing feed update: %s", err.Error())
- }
-
- // post update
- err = client.UpdateFeed(updateRequest)
- if err != nil {
- utils.Fatalf("Error updating feed: %s", err.Error())
- return
- }
-}
-
-func feedInfo(ctx *cli.Context) {
- var (
- bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client = swarm.NewClient(bzzapi)
- manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
- )
-
- var query *feed.Query
- if manifestAddressOrDomain == "" {
- query = new(feed.Query)
- query.Topic = getTopic(ctx)
- query.User = feedGetUser(ctx)
- }
-
- metadata, err := client.GetFeedRequest(query, manifestAddressOrDomain)
- if err != nil {
- utils.Fatalf("Error retrieving feed metadata: %s", err.Error())
- return
- }
- encodedMetadata, err := metadata.MarshalJSON()
- if err != nil {
- utils.Fatalf("Error encoding metadata to JSON for display:%s", err)
- }
- fmt.Println(string(encodedMetadata))
-}
-
-func feedGetUser(ctx *cli.Context) common.Address {
- var user = ctx.String(SwarmFeedUserFlag.Name)
- if user != "" {
- return common.HexToAddress(user)
- }
- pk := getPrivKey(ctx)
- if pk == nil {
- utils.Fatalf("Cannot read private key. Must specify --user or --bzzaccount")
- }
- return crypto.PubkeyToAddress(pk.PublicKey)
-
-}
diff --git a/cmd/swarm/feeds_test.go b/cmd/swarm/feeds_test.go
deleted file mode 100644
index 4c40f62a8..000000000
--- a/cmd/swarm/feeds_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2017 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 (
- "bytes"
- "encoding/json"
- "io/ioutil"
- "os"
- "testing"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/swarm/api"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
- "github.com/ethereum/go-ethereum/swarm/storage/feed"
- "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
- "github.com/ethereum/go-ethereum/swarm/testutil"
-)
-
-func TestCLIFeedUpdate(t *testing.T) {
- srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer {
- return swarmhttp.NewServer(api, "")
- }, nil)
- log.Info("starting a test swarm server")
- defer srv.Close()
-
- // create a private key file for signing
- privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979"
- privKey, _ := crypto.HexToECDSA(privkeyHex)
- address := crypto.PubkeyToAddress(privKey.PublicKey)
-
- pkFileName := testutil.TempFileWithContent(t, privkeyHex)
- defer os.Remove(pkFileName)
-
- // compose a topic. We'll be doing quotes about Miguel de Cervantes
- var topic feed.Topic
- subject := []byte("Miguel de Cervantes")
- copy(topic[:], subject[:])
- name := "quotes"
-
- // prepare some data for the update
- data := []byte("En boca cerrada no entran moscas")
- hexData := hexutil.Encode(data)
-
- flags := []string{
- "--bzzapi", srv.URL,
- "--bzzaccount", pkFileName,
- "feed", "update",
- "--topic", topic.Hex(),
- "--name", name,
- hexData}
-
- // create an update and expect an exit without errors
- log.Info("updating a feed with 'swarm feed update'")
- cmd := runSwarm(t, flags...)
- cmd.ExpectExit()
-
- // now try to get the update using the client
- client := swarm.NewClient(srv.URL)
-
- // build the same topic as before, this time
- // we use NewTopic to create a topic automatically.
- topic, err := feed.NewTopic(name, subject)
- if err != nil {
- t.Fatal(err)
- }
-
- // Feed configures whose updates we will be looking up.
- fd := feed.Feed{
- Topic: topic,
- User: address,
- }
-
- // Build a query to get the latest update
- query := feed.NewQueryLatest(&fd, lookup.NoClue)
-
- // retrieve content!
- reader, err := client.QueryFeed(query, "")
- if err != nil {
- t.Fatal(err)
- }
-
- retrieved, err := ioutil.ReadAll(reader)
- if err != nil {
- t.Fatal(err)
- }
-
- // check we retrieved the sent information
- if !bytes.Equal(data, retrieved) {
- t.Fatalf("Received %s, expected %s", retrieved, data)
- }
-
- // Now retrieve info for the next update
- flags = []string{
- "--bzzapi", srv.URL,
- "feed", "info",
- "--topic", topic.Hex(),
- "--user", address.Hex(),
- }
-
- log.Info("getting feed info with 'swarm feed info'")
- cmd = runSwarm(t, flags...)
- _, matches := cmd.ExpectRegexp(`.*`) // regex hack to extract stdout
- cmd.ExpectExit()
-
- // verify we can deserialize the result as a valid JSON
- var request feed.Request
- err = json.Unmarshal([]byte(matches[0]), &request)
- if err != nil {
- t.Fatal(err)
- }
-
- // make sure the retrieved feed is the same
- if request.Feed != fd {
- t.Fatalf("Expected feed to be: %s, got %s", fd, request.Feed)
- }
-
- // test publishing a manifest
- flags = []string{
- "--bzzapi", srv.URL,
- "--bzzaccount", pkFileName,
- "feed", "create",
- "--topic", topic.Hex(),
- }
-
- log.Info("Publishing manifest with 'swarm feed create'")
- cmd = runSwarm(t, flags...)
- _, matches = cmd.ExpectRegexp(`[a-f\d]{64}`)
- cmd.ExpectExit()
-
- manifestAddress := matches[0] // read the received feed manifest
-
- // now attempt to lookup the latest update using a manifest instead
- reader, err = client.QueryFeed(nil, manifestAddress)
- if err != nil {
- t.Fatal(err)
- }
-
- retrieved, err = ioutil.ReadAll(reader)
- if err != nil {
- t.Fatal(err)
- }
-
- if !bytes.Equal(data, retrieved) {
- t.Fatalf("Received %s, expected %s", retrieved, data)
- }
-
- // test publishing a manifest for a different user
- flags = []string{
- "--bzzapi", srv.URL,
- "feed", "create",
- "--topic", topic.Hex(),
- "--user", "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // different user
- }
-
- log.Info("Publishing manifest with 'swarm feed create' for a different user")
- cmd = runSwarm(t, flags...)
- _, matches = cmd.ExpectRegexp(`[a-f\d]{64}`)
- cmd.ExpectExit()
-
- manifestAddress = matches[0] // read the received feed manifest
-
- // now let's try to update that user's manifest which we don't have the private key for
- flags = []string{
- "--bzzapi", srv.URL,
- "--bzzaccount", pkFileName,
- "feed", "update",
- "--manifest", manifestAddress,
- hexData}
-
- // create an update and expect an error given there is a user mismatch
- log.Info("updating a feed with 'swarm feed update'")
- cmd = runSwarm(t, flags...)
- cmd.ExpectRegexp("Fatal:.*") // best way so far to detect a failure.
- cmd.ExpectExit()
- if cmd.ExitStatus() == 0 {
- t.Fatal("Expected nonzero exit code when updating a manifest with the wrong user. Got 0.")
- }
-}
diff --git a/cmd/swarm/flags.go b/cmd/swarm/flags.go
deleted file mode 100644
index 6093149e3..000000000
--- a/cmd/swarm/flags.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2018 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 feed allows the user to create and update signed Swarm feeds
-package main
-
-import cli "gopkg.in/urfave/cli.v1"
-
-var (
- ChequebookAddrFlag = cli.StringFlag{
- Name: "chequebook",
- Usage: "chequebook contract address",
- EnvVar: SwarmEnvChequebookAddr,
- }
- SwarmAccountFlag = cli.StringFlag{
- Name: "bzzaccount",
- Usage: "Swarm account key file",
- EnvVar: SwarmEnvAccount,
- }
- SwarmListenAddrFlag = cli.StringFlag{
- Name: "httpaddr",
- Usage: "Swarm HTTP API listening interface",
- EnvVar: SwarmEnvListenAddr,
- }
- SwarmPortFlag = cli.StringFlag{
- Name: "bzzport",
- Usage: "Swarm local http api port",
- EnvVar: SwarmEnvPort,
- }
- SwarmNetworkIdFlag = cli.IntFlag{
- Name: "bzznetworkid",
- Usage: "Network identifier (integer, default 3=swarm testnet)",
- EnvVar: SwarmEnvNetworkID,
- }
- SwarmSwapEnabledFlag = cli.BoolFlag{
- Name: "swap",
- Usage: "Swarm SWAP enabled (default false)",
- EnvVar: SwarmEnvSwapEnable,
- }
- SwarmSwapAPIFlag = cli.StringFlag{
- Name: "swap-api",
- Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
- EnvVar: SwarmEnvSwapAPI,
- }
- SwarmSyncDisabledFlag = cli.BoolTFlag{
- Name: "nosync",
- Usage: "Disable swarm syncing",
- EnvVar: SwarmEnvSyncDisable,
- }
- SwarmSyncUpdateDelay = cli.DurationFlag{
- Name: "sync-update-delay",
- Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
- EnvVar: SwarmEnvSyncUpdateDelay,
- }
- SwarmMaxStreamPeerServersFlag = cli.IntFlag{
- Name: "max-stream-peer-servers",
- Usage: "Limit of Stream peer servers, 0 denotes unlimited",
- EnvVar: SwarmEnvMaxStreamPeerServers,
- Value: 10000, // A very large default value is possible as stream servers have very small memory footprint
- }
- SwarmLightNodeEnabled = cli.BoolFlag{
- Name: "lightnode",
- Usage: "Enable Swarm LightNode (default false)",
- EnvVar: SwarmEnvLightNodeEnable,
- }
- SwarmDeliverySkipCheckFlag = cli.BoolFlag{
- Name: "delivery-skip-check",
- Usage: "Skip chunk delivery check (default false)",
- EnvVar: SwarmEnvDeliverySkipCheck,
- }
- EnsAPIFlag = cli.StringSliceFlag{
- Name: "ens-api",
- Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
- EnvVar: SwarmEnvENSAPI,
- }
- SwarmApiFlag = cli.StringFlag{
- Name: "bzzapi",
- Usage: "Specifies the Swarm HTTP endpoint to connect to",
- Value: "http://127.0.0.1:8500",
- }
- SwarmRecursiveFlag = cli.BoolFlag{
- Name: "recursive",
- Usage: "Upload directories recursively",
- }
- SwarmWantManifestFlag = cli.BoolTFlag{
- Name: "manifest",
- Usage: "Automatic manifest upload (default true)",
- }
- SwarmUploadDefaultPath = cli.StringFlag{
- Name: "defaultpath",
- Usage: "path to file served for empty url path (none)",
- }
- SwarmAccessGrantKeyFlag = cli.StringFlag{
- Name: "grant-key",
- Usage: "grants a given public key access to an ACT",
- }
- SwarmAccessGrantKeysFlag = cli.StringFlag{
- Name: "grant-keys",
- Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
- }
- SwarmUpFromStdinFlag = cli.BoolFlag{
- Name: "stdin",
- Usage: "reads data to be uploaded from stdin",
- }
- SwarmUploadMimeType = cli.StringFlag{
- Name: "mime",
- Usage: "Manually specify MIME type",
- }
- SwarmEncryptedFlag = cli.BoolFlag{
- Name: "encrypt",
- Usage: "use encrypted upload",
- }
- SwarmAccessPasswordFlag = cli.StringFlag{
- Name: "password",
- Usage: "Password",
- EnvVar: SwarmAccessPassword,
- }
- SwarmDryRunFlag = cli.BoolFlag{
- Name: "dry-run",
- Usage: "dry-run",
- }
- CorsStringFlag = cli.StringFlag{
- Name: "corsdomain",
- Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
- EnvVar: SwarmEnvCORS,
- }
- SwarmStorePath = cli.StringFlag{
- Name: "store.path",
- Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
- EnvVar: SwarmEnvStorePath,
- }
- SwarmStoreCapacity = cli.Uint64Flag{
- Name: "store.size",
- Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
- EnvVar: SwarmEnvStoreCapacity,
- }
- SwarmStoreCacheCapacity = cli.UintFlag{
- Name: "store.cache.size",
- Usage: "Number of recent chunks cached in memory",
- EnvVar: SwarmEnvStoreCacheCapacity,
- Value: 10000,
- }
- SwarmCompressedFlag = cli.BoolFlag{
- Name: "compressed",
- Usage: "Prints encryption keys in compressed form",
- }
- SwarmBootnodeModeFlag = cli.BoolFlag{
- Name: "bootnode-mode",
- Usage: "Run Swarm in Bootnode mode",
- }
- SwarmFeedNameFlag = cli.StringFlag{
- Name: "name",
- Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name",
- }
- SwarmFeedTopicFlag = cli.StringFlag{
- Name: "topic",
- Usage: "User-defined topic this feed is tracking, hex encoded. Limited to 64 hexadecimal characters",
- }
- SwarmFeedManifestFlag = cli.StringFlag{
- Name: "manifest",
- Usage: "Refers to the feed through a manifest",
- }
- SwarmFeedUserFlag = cli.StringFlag{
- Name: "user",
- Usage: "Indicates the user who updates the feed",
- }
- SwarmGlobalStoreAPIFlag = cli.StringFlag{
- Name: "globalstore-api",
- Usage: "URL of the Global Store API provider (only for testing)",
- EnvVar: SwarmGlobalstoreAPI,
- }
- SwarmLegacyFlag = cli.BoolFlag{
- Name: "legacy",
- Usage: "Use this flag when importing a db export from a legacy local store database dump (for schemas older than 'sanctuary')",
- }
-)
diff --git a/cmd/swarm/fs.go b/cmd/swarm/fs.go
deleted file mode 100644
index 7f156523b..000000000
--- a/cmd/swarm/fs.go
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2018 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 (
- "context"
- "fmt"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm/fuse"
- "gopkg.in/urfave/cli.v1"
-)
-
-var fsCommand = cli.Command{
- Name: "fs",
- CustomHelpTemplate: helpTemplate,
- Usage: "perform FUSE operations",
- ArgsUsage: "fs COMMAND",
- Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node",
- Subcommands: []cli.Command{
- {
- Action: mount,
- CustomHelpTemplate: helpTemplate,
- Name: "mount",
- Usage: "mount a swarm hash to a mount point",
- ArgsUsage: "swarm fs mount <manifest hash> <mount point>",
- Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
- },
- {
- Action: unmount,
- CustomHelpTemplate: helpTemplate,
- Name: "unmount",
- Usage: "unmount a swarmfs mount",
- ArgsUsage: "swarm fs unmount <mount point>",
- Description: "Unmounts a swarmfs mount residing at <mount point>. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
- },
- {
- Action: listMounts,
- CustomHelpTemplate: helpTemplate,
- Name: "list",
- Usage: "list swarmfs mounts",
- ArgsUsage: "swarm fs list",
- Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
- },
- },
-}
-
-func mount(cliContext *cli.Context) {
- args := cliContext.Args()
- if len(args) < 2 {
- utils.Fatalf("Usage: swarm fs mount <manifestHash> <file name>")
- }
-
- client, err := dialRPC(cliContext)
- if err != nil {
- utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
- }
- defer client.Close()
-
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- mf := &fuse.MountInfo{}
- mountPoint, err := filepath.Abs(filepath.Clean(args[1]))
- if err != nil {
- utils.Fatalf("error expanding path for mount point: %v", err)
- }
- err = client.CallContext(ctx, mf, "swarmfs_mount", args[0], mountPoint)
- if err != nil {
- utils.Fatalf("had an error calling the RPC endpoint while mounting: %v", err)
- }
-}
-
-func unmount(cliContext *cli.Context) {
- args := cliContext.Args()
-
- if len(args) < 1 {
- utils.Fatalf("Usage: swarm fs unmount <mount path>")
- }
- client, err := dialRPC(cliContext)
- if err != nil {
- utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
- }
- defer client.Close()
-
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- mf := fuse.MountInfo{}
- err = client.CallContext(ctx, &mf, "swarmfs_unmount", args[0])
- if err != nil {
- utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err)
- }
- fmt.Printf("%s\n", mf.LatestManifest) //print the latest manifest hash for user reference
-}
-
-func listMounts(cliContext *cli.Context) {
- client, err := dialRPC(cliContext)
- if err != nil {
- utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
- }
- defer client.Close()
-
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- var mf []fuse.MountInfo
- err = client.CallContext(ctx, &mf, "swarmfs_listmounts")
- if err != nil {
- utils.Fatalf("encountered an error calling the RPC endpoint while listing mounts: %v", err)
- }
- if len(mf) == 0 {
- fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n")
- } else {
- fmt.Printf("Found %d swarmfs mount(s):\n", len(mf))
- for i, mountInfo := range mf {
- fmt.Printf("%d:\n", i)
- fmt.Printf("\tMount point: %s\n", mountInfo.MountPoint)
- fmt.Printf("\tLatest Manifest: %s\n", mountInfo.LatestManifest)
- fmt.Printf("\tStart Manifest: %s\n", mountInfo.StartManifest)
- }
- }
-}
-
-func dialRPC(ctx *cli.Context) (*rpc.Client, error) {
- endpoint := getIPCEndpoint(ctx)
- log.Info("IPC endpoint", "path", endpoint)
- return rpc.Dial(endpoint)
-}
-
-func getIPCEndpoint(ctx *cli.Context) string {
- cfg := defaultNodeConfig
- utils.SetNodeConfig(ctx, &cfg)
-
- endpoint := cfg.IPCEndpoint()
-
- if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
- // Backwards compatibility with geth < 1.5 which required
- // these prefixes.
- endpoint = endpoint[4:]
- }
- return endpoint
-}
diff --git a/cmd/swarm/fs_test.go b/cmd/swarm/fs_test.go
deleted file mode 100644
index 5f58d6c0d..000000000
--- a/cmd/swarm/fs_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2018 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/>.
-
-// +build linux freebsd
-
-package main
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/log"
-)
-
-type testFile struct {
- filePath string
- content string
-}
-
-// TestCLISwarmFsDefaultIPCPath tests if the most basic fs command, i.e., list
-// can find and correctly connect to a running Swarm node on the default
-// IPCPath.
-func TestCLISwarmFsDefaultIPCPath(t *testing.T) {
- cluster := newTestCluster(t, 1)
- defer cluster.Shutdown()
-
- handlingNode := cluster.Nodes[0]
- list := runSwarm(t, []string{
- "--datadir", handlingNode.Dir,
- "fs",
- "list",
- }...)
-
- list.WaitExit()
- if list.Err != nil {
- t.Fatal(list.Err)
- }
-}
-
-// TestCLISwarmFs is a high-level test of swarmfs
-//
-// This test fails on travis for macOS as this executable exits with code 1
-// and without any log messages in the log:
-// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse.
-// This is the reason for this file not being built on darwin architecture.
-func TestCLISwarmFs(t *testing.T) {
- cluster := newTestCluster(t, 3)
- defer cluster.Shutdown()
-
- // create a tmp dir
- mountPoint, err := ioutil.TempDir("", "swarm-test")
- log.Debug("swarmfs cli test", "1st mount", mountPoint)
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(mountPoint)
-
- handlingNode := cluster.Nodes[0]
- mhash := doUploadEmptyDir(t, handlingNode)
- log.Debug("swarmfs cli test: mounting first run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
-
- mount := runSwarm(t, []string{
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
- "fs",
- "mount",
- mhash,
- mountPoint,
- }...)
- mount.ExpectExit()
-
- filesToAssert := []*testFile{}
-
- dirPath, err := createDirInDir(mountPoint, "testSubDir")
- if err != nil {
- t.Fatal(err)
- }
- dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir")
- if err != nil {
- t.Fatal(err)
- }
-
- dummyContent := "somerandomtestcontentthatshouldbeasserted"
- dirs := []string{
- mountPoint,
- dirPath,
- dirPath2,
- }
- files := []string{"f1.tmp", "f2.tmp"}
- for _, d := range dirs {
- for _, entry := range files {
- tFile, err := createTestFileInPath(d, entry, dummyContent)
- if err != nil {
- t.Fatal(err)
- }
- filesToAssert = append(filesToAssert, tFile)
- }
- }
- if len(filesToAssert) != len(dirs)*len(files) {
- t.Fatalf("should have %d files to assert now, got %d", len(dirs)*len(files), len(filesToAssert))
- }
- hashRegexp := `[a-f\d]{64}`
- log.Debug("swarmfs cli test: unmounting first run...", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
-
- unmount := runSwarm(t, []string{
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
- "fs",
- "unmount",
- mountPoint,
- }...)
- _, matches := unmount.ExpectRegexp(hashRegexp)
- unmount.ExpectExit()
-
- hash := matches[0]
- if hash == mhash {
- t.Fatal("this should not be equal")
- }
- log.Debug("swarmfs cli test: asserting no files in mount point")
-
- //check that there's nothing in the mount folder
- filesInDir, err := ioutil.ReadDir(mountPoint)
- if err != nil {
- t.Fatalf("had an error reading the directory: %v", err)
- }
-
- if len(filesInDir) != 0 {
- t.Fatal("there shouldn't be anything here")
- }
-
- secondMountPoint, err := ioutil.TempDir("", "swarm-test")
- log.Debug("swarmfs cli test", "2nd mount point at", secondMountPoint)
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(secondMountPoint)
-
- log.Debug("swarmfs cli test: remounting at second mount point", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
-
- //remount, check files
- newMount := runSwarm(t, []string{
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
- "fs",
- "mount",
- hash, // the latest hash
- secondMountPoint,
- }...)
-
- newMount.ExpectExit()
- time.Sleep(1 * time.Second)
-
- filesInDir, err = ioutil.ReadDir(secondMountPoint)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(filesInDir) == 0 {
- t.Fatal("there should be something here")
- }
-
- log.Debug("swarmfs cli test: traversing file tree to see it matches previous mount")
-
- for _, file := range filesToAssert {
- file.filePath = strings.Replace(file.filePath, mountPoint, secondMountPoint, -1)
- fileBytes, err := ioutil.ReadFile(file.filePath)
-
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(fileBytes, bytes.NewBufferString(file.content).Bytes()) {
- t.Fatal("this should be equal")
- }
- }
-
- log.Debug("swarmfs cli test: unmounting second run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
-
- unmountSec := runSwarm(t, []string{
- fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
- "fs",
- "unmount",
- secondMountPoint,
- }...)
-
- _, matches = unmountSec.ExpectRegexp(hashRegexp)
- unmountSec.ExpectExit()
-
- if matches[0] != hash {
- t.Fatal("these should be equal - no changes made")
- }
-}
-
-func doUploadEmptyDir(t *testing.T, node *testNode) string {
- // create a tmp dir
- tmpDir, err := ioutil.TempDir("", "swarm-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDir)
-
- hashRegexp := `[a-f\d]{64}`
-
- flags := []string{
- "--bzzapi", node.URL,
- "--recursive",
- "up",
- tmpDir}
-
- log.Info("swarmfs cli test: uploading dir with 'swarm up'")
- up := runSwarm(t, flags...)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
- hash := matches[0]
- log.Info("swarmfs cli test: dir uploaded", "hash", hash)
- return hash
-}
-
-func createDirInDir(createInDir string, dirToCreate string) (string, error) {
- fullpath := filepath.Join(createInDir, dirToCreate)
- err := os.MkdirAll(fullpath, 0777)
- if err != nil {
- return "", err
- }
- return fullpath, nil
-}
-
-func createTestFileInPath(dir, filename, content string) (*testFile, error) {
- tFile := &testFile{}
- filePath := filepath.Join(dir, filename)
- if file, err := os.Create(filePath); err == nil {
- tFile.content = content
- tFile.filePath = filePath
-
- _, err = io.WriteString(file, content)
- if err != nil {
- return nil, err
- }
- file.Close()
- }
-
- return tFile, nil
-}
diff --git a/cmd/swarm/global-store/explorer.go b/cmd/swarm/global-store/explorer.go
deleted file mode 100644
index 634ff1ebb..000000000
--- a/cmd/swarm/global-store/explorer.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 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 (
- "context"
- "fmt"
- "net"
- "net/http"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/swarm/storage/mock"
- "github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-// serveChunkExplorer starts an http server in background with chunk explorer handler
-// using the provided global store. Server is started if the returned shutdown function
-// is not nil.
-func serveChunkExplorer(ctx *cli.Context, globalStore mock.GlobalStorer) (shutdown func(), err error) {
- if !ctx.IsSet("explorer-address") {
- return nil, nil
- }
-
- corsOrigins := ctx.StringSlice("explorer-cors-origin")
- server := &http.Server{
- Handler: explorer.NewHandler(globalStore, corsOrigins),
- IdleTimeout: 30 * time.Minute,
- ReadTimeout: 2 * time.Minute,
- WriteTimeout: 2 * time.Minute,
- }
- listener, err := net.Listen("tcp", ctx.String("explorer-address"))
- if err != nil {
- return nil, fmt.Errorf("explorer: %v", err)
- }
- log.Info("chunk explorer http", "address", listener.Addr().String(), "origins", corsOrigins)
-
- go func() {
- if err := server.Serve(listener); err != nil {
- log.Error("chunk explorer", "err", err)
- }
- }()
-
- return func() {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if err := server.Shutdown(ctx); err != nil {
- log.Error("chunk explorer: shutdown", "err", err)
- }
- }, nil
-}
diff --git a/cmd/swarm/global-store/explorer_test.go b/cmd/swarm/global-store/explorer_test.go
deleted file mode 100644
index 2e4928c8f..000000000
--- a/cmd/swarm/global-store/explorer_test.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2019 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 (
- "encoding/json"
- "fmt"
- "net/http"
- "sort"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
- mockRPC "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
-)
-
-// TestExplorer validates basic chunk explorer functionality by storing
-// a small set of chunk and making http requests on exposed endpoint.
-// Full chunk explorer validation is done in mock/explorer package.
-func TestExplorer(t *testing.T) {
- addr := findFreeTCPAddress(t)
- explorerAddr := findFreeTCPAddress(t)
- testCmd := runGlobalStore(t, "ws", "--addr", addr, "--explorer-address", explorerAddr)
- defer testCmd.Kill()
-
- client := websocketClient(t, addr)
-
- store := mockRPC.NewGlobalStore(client)
- defer store.Close()
-
- nodeKeys := map[string][]string{
- "a1": {"b1", "b2", "b3"},
- "a2": {"b3", "b4", "b5"},
- }
-
- keyNodes := make(map[string][]string)
-
- for addr, keys := range nodeKeys {
- for _, key := range keys {
- keyNodes[key] = append(keyNodes[key], addr)
- }
- }
-
- invalidAddr := "c1"
- invalidKey := "d1"
-
- for addr, keys := range nodeKeys {
- for _, key := range keys {
- err := store.Put(common.HexToAddress(addr), common.Hex2Bytes(key), []byte("data"))
- if err != nil {
- t.Fatal(err)
- }
- }
- }
-
- endpoint := "http://" + explorerAddr
-
- t.Run("has key", func(t *testing.T) {
- for addr, keys := range nodeKeys {
- for _, key := range keys {
- testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+key, http.StatusOK)
- testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+key, http.StatusNotFound)
- }
- testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+invalidKey, http.StatusNotFound)
- }
- testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+invalidKey, http.StatusNotFound)
- })
-
- t.Run("keys", func(t *testing.T) {
- var keys []string
- for key := range keyNodes {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- testKeysResponse(t, endpoint+"/api/keys", explorer.KeysResponse{
- Keys: keys,
- })
- })
-
- t.Run("nodes", func(t *testing.T) {
- var nodes []string
- for addr := range nodeKeys {
- nodes = append(nodes, common.HexToAddress(addr).Hex())
- }
- sort.Strings(nodes)
- testNodesResponse(t, endpoint+"/api/nodes", explorer.NodesResponse{
- Nodes: nodes,
- })
- })
-
- t.Run("node keys", func(t *testing.T) {
- for addr, keys := range nodeKeys {
- testKeysResponse(t, endpoint+"/api/keys?node="+addr, explorer.KeysResponse{
- Keys: keys,
- })
- }
- testKeysResponse(t, endpoint+"/api/keys?node="+invalidAddr, explorer.KeysResponse{})
- })
-
- t.Run("key nodes", func(t *testing.T) {
- for key, addrs := range keyNodes {
- var nodes []string
- for _, addr := range addrs {
- nodes = append(nodes, common.HexToAddress(addr).Hex())
- }
- sort.Strings(nodes)
- testNodesResponse(t, endpoint+"/api/nodes?key="+key, explorer.NodesResponse{
- Nodes: nodes,
- })
- }
- testNodesResponse(t, endpoint+"/api/nodes?key="+invalidKey, explorer.NodesResponse{})
- })
-}
-
-// TestExplorer_CORSOrigin validates if chunk explorer returns
-// correct CORS origin header in GET and OPTIONS requests.
-func TestExplorer_CORSOrigin(t *testing.T) {
- origin := "http://localhost/"
- addr := findFreeTCPAddress(t)
- explorerAddr := findFreeTCPAddress(t)
- testCmd := runGlobalStore(t, "ws",
- "--addr", addr,
- "--explorer-address", explorerAddr,
- "--explorer-cors-origin", origin,
- )
- defer testCmd.Kill()
-
- // wait until the server is started
- waitHTTPEndpoint(t, explorerAddr)
-
- url := "http://" + explorerAddr + "/api/keys"
-
- t.Run("get", func(t *testing.T) {
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Set("Origin", origin)
-
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- header := resp.Header.Get("Access-Control-Allow-Origin")
- if header != origin {
- t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
- }
- })
-
- t.Run("preflight", func(t *testing.T) {
- req, err := http.NewRequest(http.MethodOptions, url, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Set("Origin", origin)
- req.Header.Set("Access-Control-Request-Method", "GET")
-
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- header := resp.Header.Get("Access-Control-Allow-Origin")
- if header != origin {
- t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
- }
- })
-}
-
-// testStatusResponse makes an http request to provided url
-// and validates if response is explorer.StatusResponse for
-// the expected status code.
-func testStatusResponse(t *testing.T, url string, code int) {
- t.Helper()
-
- resp, err := http.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != code {
- t.Errorf("got status code %v, want %v", resp.StatusCode, code)
- }
- var r explorer.StatusResponse
- if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
- t.Fatal(err)
- }
- if r.Code != code {
- t.Errorf("got response code %v, want %v", r.Code, code)
- }
- if r.Message != http.StatusText(code) {
- t.Errorf("got response message %q, want %q", r.Message, http.StatusText(code))
- }
-}
-
-// testKeysResponse makes an http request to provided url
-// and validates if response machhes expected explorer.KeysResponse.
-func testKeysResponse(t *testing.T, url string, want explorer.KeysResponse) {
- t.Helper()
-
- resp, err := http.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != http.StatusOK {
- t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
- }
- var r explorer.KeysResponse
- if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
- t.Fatal(err)
- }
- if fmt.Sprint(r.Keys) != fmt.Sprint(want.Keys) {
- t.Errorf("got keys %v, want %v", r.Keys, want.Keys)
- }
- if r.Next != want.Next {
- t.Errorf("got next %s, want %s", r.Next, want.Next)
- }
-}
-
-// testNodeResponse makes an http request to provided url
-// and validates if response machhes expected explorer.NodeResponse.
-func testNodesResponse(t *testing.T, url string, want explorer.NodesResponse) {
- t.Helper()
-
- resp, err := http.Get(url)
- if err != nil {
- t.Fatal(err)
- }
- if resp.StatusCode != http.StatusOK {
- t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
- }
- var r explorer.NodesResponse
- if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
- t.Fatal(err)
- }
- if fmt.Sprint(r.Nodes) != fmt.Sprint(want.Nodes) {
- t.Errorf("got nodes %v, want %v", r.Nodes, want.Nodes)
- }
- if r.Next != want.Next {
- t.Errorf("got next %s, want %s", r.Next, want.Next)
- }
-}
diff --git a/cmd/swarm/global-store/global_store.go b/cmd/swarm/global-store/global_store.go
deleted file mode 100644
index f93b464db..000000000
--- a/cmd/swarm/global-store/global_store.go
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2019 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 (
- "io"
- "net"
- "net/http"
- "os"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm/storage/mock"
- "github.com/ethereum/go-ethereum/swarm/storage/mock/db"
- "github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-// startHTTP starts a global store with HTTP RPC server.
-// It is used for "http" cli command.
-func startHTTP(ctx *cli.Context) (err error) {
- server, cleanup, err := newServer(ctx)
- if err != nil {
- return err
- }
- defer cleanup()
-
- listener, err := net.Listen("tcp", ctx.String("addr"))
- if err != nil {
- return err
- }
- log.Info("http", "address", listener.Addr().String())
-
- return http.Serve(listener, server)
-}
-
-// startWS starts a global store with WebSocket RPC server.
-// It is used for "websocket" cli command.
-func startWS(ctx *cli.Context) (err error) {
- server, cleanup, err := newServer(ctx)
- if err != nil {
- return err
- }
- defer cleanup()
-
- listener, err := net.Listen("tcp", ctx.String("addr"))
- if err != nil {
- return err
- }
- origins := ctx.StringSlice("origins")
- log.Info("websocket", "address", listener.Addr().String(), "origins", origins)
-
- return http.Serve(listener, server.WebsocketHandler(origins))
-}
-
-// newServer creates a global store and starts a chunk explorer server if configured.
-// Returned cleanup function should be called only if err is nil.
-func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error) {
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
-
- cleanup = func() {}
- var globalStore mock.GlobalStorer
- dir := ctx.String("dir")
- if dir != "" {
- dbStore, err := db.NewGlobalStore(dir)
- if err != nil {
- return nil, nil, err
- }
- cleanup = func() {
- if err := dbStore.Close(); err != nil {
- log.Error("global store: close", "err", err)
- }
- }
- globalStore = dbStore
- log.Info("database global store", "dir", dir)
- } else {
- globalStore = mem.NewGlobalStore()
- log.Info("in-memory global store")
- }
-
- server = rpc.NewServer()
- if err := server.RegisterName("mockStore", globalStore); err != nil {
- cleanup()
- return nil, nil, err
- }
-
- shutdown, err := serveChunkExplorer(ctx, globalStore)
- if err != nil {
- cleanup()
- return nil, nil, err
- }
- if shutdown != nil {
- cleanup = func() {
- shutdown()
-
- if c, ok := globalStore.(io.Closer); ok {
- if err := c.Close(); err != nil {
- log.Error("global store: close", "err", err)
- }
- }
- }
- }
-
- return server, cleanup, nil
-}
diff --git a/cmd/swarm/global-store/global_store_test.go b/cmd/swarm/global-store/global_store_test.go
deleted file mode 100644
index c437c9d45..000000000
--- a/cmd/swarm/global-store/global_store_test.go
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2019 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 (
- "context"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/rpc"
- mockRPC "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
-)
-
-// TestHTTP_InMemory tests in-memory global store that exposes
-// HTTP server.
-func TestHTTP_InMemory(t *testing.T) {
- testHTTP(t, true)
-}
-
-// TestHTTP_Database tests global store with persisted database
-// that exposes HTTP server.
-func TestHTTP_Database(t *testing.T) {
- dir, err := ioutil.TempDir("", "swarm-global-store-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- // create a fresh global store
- testHTTP(t, true, "--dir", dir)
-
- // check if data saved by the previous global store instance
- testHTTP(t, false, "--dir", dir)
-}
-
-// testWebsocket starts global store binary with HTTP server
-// and validates that it can store and retrieve data.
-// If put is false, no data will be stored, only retrieved,
-// giving the possibility to check if data is present in the
-// storage directory.
-func testHTTP(t *testing.T, put bool, args ...string) {
- addr := findFreeTCPAddress(t)
- testCmd := runGlobalStore(t, append([]string{"http", "--addr", addr}, args...)...)
- defer testCmd.Kill()
-
- client, err := rpc.DialHTTP("http://" + addr)
- if err != nil {
- t.Fatal(err)
- }
-
- // wait until global store process is started as
- // rpc.DialHTTP is actually not connecting
- waitHTTPEndpoint(t, addr)
-
- store := mockRPC.NewGlobalStore(client)
- defer store.Close()
-
- node := store.NewNodeStore(common.HexToAddress("123abc"))
-
- wantKey := "key"
- wantValue := "value"
-
- if put {
- err = node.Put([]byte(wantKey), []byte(wantValue))
- if err != nil {
- t.Fatal(err)
- }
- }
-
- gotValue, err := node.Get([]byte(wantKey))
- if err != nil {
- t.Fatal(err)
- }
-
- if string(gotValue) != wantValue {
- t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue)
- }
-}
-
-// TestWebsocket_InMemory tests in-memory global store that exposes
-// WebSocket server.
-func TestWebsocket_InMemory(t *testing.T) {
- testWebsocket(t, true)
-}
-
-// TestWebsocket_Database tests global store with persisted database
-// that exposes HTTP server.
-func TestWebsocket_Database(t *testing.T) {
- dir, err := ioutil.TempDir("", "swarm-global-store-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
- // create a fresh global store
- testWebsocket(t, true, "--dir", dir)
-
- // check if data saved by the previous global store instance
- testWebsocket(t, false, "--dir", dir)
-}
-
-// testWebsocket starts global store binary with WebSocket server
-// and validates that it can store and retrieve data.
-// If put is false, no data will be stored, only retrieved,
-// giving the possibility to check if data is present in the
-// storage directory.
-func testWebsocket(t *testing.T, put bool, args ...string) {
- addr := findFreeTCPAddress(t)
- testCmd := runGlobalStore(t, append([]string{"ws", "--addr", addr}, args...)...)
- defer testCmd.Kill()
-
- client := websocketClient(t, addr)
-
- store := mockRPC.NewGlobalStore(client)
- defer store.Close()
-
- node := store.NewNodeStore(common.HexToAddress("123abc"))
-
- wantKey := "key"
- wantValue := "value"
-
- if put {
- err := node.Put([]byte(wantKey), []byte(wantValue))
- if err != nil {
- t.Fatal(err)
- }
- }
-
- gotValue, err := node.Get([]byte(wantKey))
- if err != nil {
- t.Fatal(err)
- }
-
- if string(gotValue) != wantValue {
- t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue)
- }
-}
-
-// findFreeTCPAddress returns a local address (IP:Port) to which
-// global store can listen on.
-func findFreeTCPAddress(t *testing.T) (addr string) {
- t.Helper()
-
- listener, err := net.Listen("tcp", "")
- if err != nil {
- t.Fatal(err)
- }
- defer listener.Close()
-
- return listener.Addr().String()
-}
-
-// websocketClient waits until global store process is started
-// and returns rpc client.
-func websocketClient(t *testing.T, addr string) (client *rpc.Client) {
- t.Helper()
-
- var err error
- for i := 0; i < 1000; i++ {
- client, err = rpc.DialWebsocket(context.Background(), "ws://"+addr, "")
- if err == nil {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- if err != nil {
- t.Fatal(err)
- }
- return client
-}
-
-// waitHTTPEndpoint retries http requests to a provided
-// address until the connection is established.
-func waitHTTPEndpoint(t *testing.T, addr string) {
- t.Helper()
-
- var err error
- for i := 0; i < 1000; i++ {
- _, err = http.Get("http://" + addr)
- if err == nil {
- break
- }
- time.Sleep(10 * time.Millisecond)
- }
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/cmd/swarm/global-store/main.go b/cmd/swarm/global-store/main.go
deleted file mode 100644
index 58505f988..000000000
--- a/cmd/swarm/global-store/main.go
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2019 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 (
- "os"
-
- "github.com/ethereum/go-ethereum/log"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-var (
- version = "0.1"
- gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
- gitDate string
-)
-
-func main() {
- err := newApp().Run(os.Args)
- if err != nil {
- log.Error(err.Error())
- os.Exit(1)
- }
-}
-
-// newApp construct a new instance of Swarm Global Store.
-// Method Run is called on it in the main function and in tests.
-func newApp() (app *cli.App) {
- app = cli.NewApp()
- app.Name = "global-store"
- app.Version = version
- if len(gitCommit) >= 8 {
- app.Version += "-" + gitCommit[:8]
- }
- if gitDate != "" {
- app.Version += "-" + gitDate
- }
- app.Usage = "Swarm Global Store"
-
- // app flags (for all commands)
- app.Flags = []cli.Flag{
- cli.IntFlag{
- Name: "verbosity",
- Value: 3,
- Usage: "Verbosity level.",
- },
- cli.StringFlag{
- Name: "explorer-address",
- Value: "",
- Usage: "Chunk explorer HTTP listener address.",
- },
- cli.StringSliceFlag{
- Name: "explorer-cors-origin",
- Value: nil,
- Usage: "Chunk explorer CORS origin (can be specified multiple times).",
- },
- }
-
- app.Commands = []cli.Command{
- {
- Name: "http",
- Aliases: []string{"h"},
- Usage: "Start swarm global store with HTTP server.",
- Action: startHTTP,
- // Flags only for "start" command.
- // Allow app flags to be specified after the
- // command argument.
- Flags: append(app.Flags,
- cli.StringFlag{
- Name: "dir",
- Value: "",
- Usage: "Data directory.",
- },
- cli.StringFlag{
- Name: "addr",
- Value: "0.0.0.0:3033",
- Usage: "Address to listen for HTTP connections.",
- },
- ),
- },
- {
- Name: "websocket",
- Aliases: []string{"ws"},
- Usage: "Start swarm global store with WebSocket server.",
- Action: startWS,
- // Flags only for "start" command.
- // Allow app flags to be specified after the
- // command argument.
- Flags: append(app.Flags,
- cli.StringFlag{
- Name: "dir",
- Value: "",
- Usage: "Data directory.",
- },
- cli.StringFlag{
- Name: "addr",
- Value: "0.0.0.0:3033",
- Usage: "Address to listen for WebSocket connections.",
- },
- cli.StringSliceFlag{
- Name: "origin",
- Value: nil,
- Usage: "WebSocket CORS origin (can be specified multiple times).",
- },
- ),
- },
- }
-
- return app
-}
diff --git a/cmd/swarm/global-store/run_test.go b/cmd/swarm/global-store/run_test.go
deleted file mode 100644
index d7ef626e5..000000000
--- a/cmd/swarm/global-store/run_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2019 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 (
- "fmt"
- "os"
- "testing"
-
- "github.com/docker/docker/pkg/reexec"
- "github.com/ethereum/go-ethereum/internal/cmdtest"
-)
-
-func init() {
- reexec.Register("swarm-global-store", func() {
- if err := newApp().Run(os.Args); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- os.Exit(0)
- })
-}
-
-func runGlobalStore(t *testing.T, args ...string) *cmdtest.TestCmd {
- tt := cmdtest.NewTestCmd(t, nil)
- tt.Run("swarm-global-store", args...)
- return tt
-}
-
-func TestMain(m *testing.M) {
- if reexec.Init() {
- return
- }
- os.Exit(m.Run())
-}
diff --git a/cmd/swarm/hash.go b/cmd/swarm/hash.go
deleted file mode 100644
index ff786fa10..000000000
--- a/cmd/swarm/hash.go
+++ /dev/null
@@ -1,116 +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/>.
-
-// Command bzzhash computes a swarm tree hash.
-package main
-
-import (
- "context"
- "encoding/hex"
- "fmt"
- "os"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/contracts/ens"
- "github.com/ethereum/go-ethereum/swarm/chunk"
- "github.com/ethereum/go-ethereum/swarm/storage"
- "gopkg.in/urfave/cli.v1"
-)
-
-var hashCommand = cli.Command{
- Action: hash,
- CustomHelpTemplate: helpTemplate,
- Name: "hash",
- Usage: "print the swarm hash of a file or directory",
- ArgsUsage: "<file>",
- Description: "Prints the swarm hash of file or directory",
- Subcommands: []cli.Command{
- {
- CustomHelpTemplate: helpTemplate,
- Name: "ens",
- Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
- ArgsUsage: "<ref>",
- Description: "",
- Subcommands: []cli.Command{
- {
- Action: encodeEipHash,
- CustomHelpTemplate: helpTemplate,
- Name: "contenthash",
- Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
- ArgsUsage: "<ref>",
- Description: "",
- },
- {
- Action: ensNodeHash,
- CustomHelpTemplate: helpTemplate,
- Name: "node",
- Usage: "converts an ens name to an ENS node hash",
- ArgsUsage: "<ref>",
- Description: "",
- },
- },
- },
- }}
-
-func hash(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) < 1 {
- utils.Fatalf("Usage: swarm hash <file name>")
- }
- f, err := os.Open(args[0])
- if err != nil {
- utils.Fatalf("Error opening file " + args[1])
- }
- defer f.Close()
-
- stat, _ := f.Stat()
- fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
- addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
- if err != nil {
- utils.Fatalf("%v\n", err)
- } else {
- fmt.Printf("%v\n", addr)
- }
-}
-func ensNodeHash(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) < 1 {
- utils.Fatalf("Usage: swarm hash ens node <ens name>")
- }
- ensName := args[0]
-
- hash := ens.EnsNode(ensName)
-
- stringHex := hex.EncodeToString(hash[:])
- fmt.Println(stringHex)
-}
-func encodeEipHash(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) < 1 {
- utils.Fatalf("Usage: swarm hash ens <swarm hash>")
- }
- swarmHash := args[0]
-
- hash := common.HexToHash(swarmHash)
- ensHash, err := ens.EncodeSwarmHash(hash)
- if err != nil {
- utils.Fatalf("error converting swarm hash", err)
- }
-
- stringHex := hex.EncodeToString(ensHash)
- fmt.Println(stringHex)
-}
diff --git a/cmd/swarm/list.go b/cmd/swarm/list.go
deleted file mode 100644
index 5d35154a5..000000000
--- a/cmd/swarm/list.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2017 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 (
- "fmt"
- "os"
- "strings"
- "text/tabwriter"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- "gopkg.in/urfave/cli.v1"
-)
-
-var listCommand = cli.Command{
- Action: list,
- CustomHelpTemplate: helpTemplate,
- Name: "ls",
- Usage: "list files and directories contained in a manifest",
- ArgsUsage: "<manifest> [<prefix>]",
- Description: "Lists files and directories contained in a manifest",
-}
-
-func list(ctx *cli.Context) {
- args := ctx.Args()
-
- if len(args) < 1 {
- utils.Fatalf("Please supply a manifest reference as the first argument")
- } else if len(args) > 2 {
- utils.Fatalf("Too many arguments - usage 'swarm ls manifest [prefix]'")
- }
- manifest := args[0]
-
- var prefix string
- if len(args) == 2 {
- prefix = args[1]
- }
-
- bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client := swarm.NewClient(bzzapi)
- list, err := client.List(manifest, prefix, "")
- if err != nil {
- utils.Fatalf("Failed to generate file and directory list: %s", err)
- }
-
- w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
- defer w.Flush()
- fmt.Fprintln(w, "HASH\tCONTENT TYPE\tPATH")
- for _, prefix := range list.CommonPrefixes {
- fmt.Fprintf(w, "%s\t%s\t%s\n", "", "DIR", prefix)
- }
- for _, entry := range list.Entries {
- fmt.Fprintf(w, "%s\t%s\t%s\n", entry.Hash, entry.ContentType, entry.Path)
- }
-}
diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go
deleted file mode 100644
index f4c1dd688..000000000
--- a/cmd/swarm/main.go
+++ /dev/null
@@ -1,475 +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/>.
-
-package main
-
-import (
- "crypto/ecdsa"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "os"
- "os/signal"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "syscall"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/accounts/keystore"
- "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/internal/debug"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/enode"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm"
- bzzapi "github.com/ethereum/go-ethereum/swarm/api"
- swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
- "github.com/ethereum/go-ethereum/swarm/storage/mock"
- mockrpc "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
- "github.com/ethereum/go-ethereum/swarm/tracing"
- sv "github.com/ethereum/go-ethereum/swarm/version"
-
- cli "gopkg.in/urfave/cli.v1"
-)
-
-const clientIdentifier = "swarm"
-const helpTemplate = `NAME:
-{{.HelpName}} - {{.Usage}}
-
-USAGE:
-{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
-
-CATEGORY:
-{{.Category}}{{end}}{{if .Description}}
-
-DESCRIPTION:
-{{.Description}}{{end}}{{if .VisibleFlags}}
-
-OPTIONS:
-{{range .VisibleFlags}}{{.}}
-{{end}}{{end}}
-`
-
-// Git SHA1 commit hash of the release (set via linker flags)
-// this variable will be assigned if corresponding parameter is passed with install, but not with test
-// e.g.: go install -ldflags "-X main.gitCommit=ed1312d01b19e04ef578946226e5d8069d5dfd5a" ./cmd/swarm
-var gitCommit string
-
-//declare a few constant error messages, useful for later error check comparisons in test
-var (
- SwarmErrNoBZZAccount = "bzzaccount option is required but not set; check your config file, command line or environment variables"
- SwarmErrSwapSetNoAPI = "SWAP is enabled but --swap-api is not set"
-)
-
-// this help command gets added to any subcommand that does not define it explicitly
-var defaultSubcommandHelp = cli.Command{
- Action: func(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "", 1) },
- CustomHelpTemplate: helpTemplate,
- Name: "help",
- Usage: "shows this help",
- Hidden: true,
-}
-
-var defaultNodeConfig = node.DefaultConfig
-
-// This init function sets defaults so cmd/swarm can run alongside geth.
-func init() {
- sv.GitCommit = gitCommit
- defaultNodeConfig.Name = clientIdentifier
- defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit)
- defaultNodeConfig.P2P.ListenAddr = ":30399"
- defaultNodeConfig.IPCPath = "bzzd.ipc"
- // Set flag defaults for --help display.
- utils.ListenPortFlag.Value = 30399
-}
-
-var app = utils.NewApp("", "", "Ethereum Swarm")
-
-// This init function creates the cli.App.
-func init() {
- app.Action = bzzd
- app.Version = sv.ArchiveVersion(gitCommit)
- app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
- app.Commands = []cli.Command{
- {
- Action: version,
- CustomHelpTemplate: helpTemplate,
- Name: "version",
- Usage: "Print version numbers",
- Description: "The output of this command is supposed to be machine-readable",
- },
- {
- Action: keys,
- CustomHelpTemplate: helpTemplate,
- Name: "print-keys",
- Flags: []cli.Flag{SwarmCompressedFlag},
- Usage: "Print public key information",
- Description: "The output of this command is supposed to be machine-readable",
- },
- // See upload.go
- upCommand,
- // See access.go
- accessCommand,
- // See feeds.go
- feedCommand,
- // See list.go
- listCommand,
- // See hash.go
- hashCommand,
- // See download.go
- downloadCommand,
- // See manifest.go
- manifestCommand,
- // See fs.go
- fsCommand,
- // See db.go
- dbCommand,
- // See config.go
- DumpConfigCommand,
- // hashesCommand
- hashesCommand,
- }
-
- // append a hidden help subcommand to all commands that have subcommands
- // if a help command was already defined above, that one will take precedence.
- addDefaultHelpSubcommands(app.Commands)
-
- sort.Sort(cli.CommandsByName(app.Commands))
-
- app.Flags = []cli.Flag{
- utils.IdentityFlag,
- utils.DataDirFlag,
- utils.BootnodesFlag,
- utils.KeyStoreDirFlag,
- utils.ListenPortFlag,
- utils.DiscoveryV5Flag,
- utils.NetrestrictFlag,
- utils.NodeKeyFileFlag,
- utils.NodeKeyHexFlag,
- utils.MaxPeersFlag,
- utils.NATFlag,
- utils.IPCDisabledFlag,
- utils.IPCPathFlag,
- utils.PasswordFileFlag,
- // bzzd-specific flags
- CorsStringFlag,
- EnsAPIFlag,
- SwarmTomlConfigPathFlag,
- SwarmSwapEnabledFlag,
- SwarmSwapAPIFlag,
- SwarmSyncDisabledFlag,
- SwarmSyncUpdateDelay,
- SwarmMaxStreamPeerServersFlag,
- SwarmLightNodeEnabled,
- SwarmDeliverySkipCheckFlag,
- SwarmListenAddrFlag,
- SwarmPortFlag,
- SwarmAccountFlag,
- SwarmNetworkIdFlag,
- ChequebookAddrFlag,
- // upload flags
- SwarmApiFlag,
- SwarmRecursiveFlag,
- SwarmWantManifestFlag,
- SwarmUploadDefaultPath,
- SwarmUpFromStdinFlag,
- SwarmUploadMimeType,
- // bootnode mode
- SwarmBootnodeModeFlag,
- // storage flags
- SwarmStorePath,
- SwarmStoreCapacity,
- SwarmStoreCacheCapacity,
- SwarmGlobalStoreAPIFlag,
- }
- rpcFlags := []cli.Flag{
- utils.WSEnabledFlag,
- utils.WSListenAddrFlag,
- utils.WSPortFlag,
- utils.WSApiFlag,
- utils.WSAllowedOriginsFlag,
- }
- app.Flags = append(app.Flags, rpcFlags...)
- app.Flags = append(app.Flags, debug.Flags...)
- app.Flags = append(app.Flags, swarmmetrics.Flags...)
- app.Flags = append(app.Flags, tracing.Flags...)
- app.Before = func(ctx *cli.Context) error {
- runtime.GOMAXPROCS(runtime.NumCPU())
- if err := debug.Setup(ctx, ""); err != nil {
- return err
- }
- swarmmetrics.Setup(ctx)
- tracing.Setup(ctx)
- return nil
- }
- 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 keys(ctx *cli.Context) error {
- privateKey := getPrivKey(ctx)
- pubkey := crypto.FromECDSAPub(&privateKey.PublicKey)
- pubkeyhex := hex.EncodeToString(pubkey)
- pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
- bzzkey := crypto.Keccak256Hash(pubkey).Hex()
-
- if !ctx.Bool(SwarmCompressedFlag.Name) {
- fmt.Println(fmt.Sprintf("bzzkey=%s", bzzkey[2:]))
- fmt.Println(fmt.Sprintf("publicKey=%s", pubkeyhex))
- }
- fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed))
-
- return nil
-}
-
-func version(ctx *cli.Context) error {
- fmt.Println(strings.Title(clientIdentifier))
- fmt.Println("Version:", sv.VersionWithMeta)
- if gitCommit != "" {
- fmt.Println("Git Commit:", gitCommit)
- }
- fmt.Println("Go Version:", runtime.Version())
- fmt.Println("OS:", runtime.GOOS)
- return nil
-}
-
-func bzzd(ctx *cli.Context) error {
- //build a valid bzzapi.Config from all available sources:
- //default config, file config, command line and env vars
-
- bzzconfig, err := buildConfig(ctx)
- if err != nil {
- utils.Fatalf("unable to configure swarm: %v", err)
- }
-
- cfg := defaultNodeConfig
-
- //pss operates on ws
- cfg.WSModules = append(cfg.WSModules, "pss")
-
- //geth only supports --datadir via command line
- //in order to be consistent within swarm, if we pass --datadir via environment variable
- //or via config file, we get the same directory for geth and swarm
- if _, err := os.Stat(bzzconfig.Path); err == nil {
- cfg.DataDir = bzzconfig.Path
- }
-
- //optionally set the bootnodes before configuring the node
- setSwarmBootstrapNodes(ctx, &cfg)
- //setup the ethereum node
- utils.SetNodeConfig(ctx, &cfg)
-
- //disable dynamic dialing from p2p/discovery
- cfg.P2P.NoDial = true
-
- stack, err := node.New(&cfg)
- if err != nil {
- utils.Fatalf("can't create node: %v", err)
- }
- defer stack.Close()
-
- //a few steps need to be done after the config phase is completed,
- //due to overriding behavior
- err = initSwarmNode(bzzconfig, stack, ctx, &cfg)
- if err != nil {
- return err
- }
- //register BZZ as node.Service in the ethereum node
- registerBzzService(bzzconfig, stack)
- //start the node
- utils.StartNode(stack)
-
- go func() {
- sigc := make(chan os.Signal, 1)
- signal.Notify(sigc, syscall.SIGTERM)
- defer signal.Stop(sigc)
- <-sigc
- log.Info("Got sigterm, shutting swarm down...")
- stack.Stop()
- }()
-
- // add swarm bootnodes, because swarm doesn't use p2p package's discovery discv5
- go func() {
- s := stack.Server()
-
- for _, n := range cfg.P2P.BootstrapNodes {
- s.AddPeer(n)
- }
- }()
-
- stack.Wait()
- return nil
-}
-
-func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
- //define the swarm service boot function
- boot := func(_ *node.ServiceContext) (node.Service, error) {
- var nodeStore *mock.NodeStore
- if bzzconfig.GlobalStoreAPI != "" {
- // connect to global store
- client, err := rpc.Dial(bzzconfig.GlobalStoreAPI)
- if err != nil {
- return nil, fmt.Errorf("global store: %v", err)
- }
- globalStore := mockrpc.NewGlobalStore(client)
- // create a node store for this swarm key on global store
- nodeStore = globalStore.NewNodeStore(common.HexToAddress(bzzconfig.BzzKey))
- }
- return swarm.NewSwarm(bzzconfig, nodeStore)
- }
- //register within the ethereum node
- if err := stack.Register(boot); err != nil {
- utils.Fatalf("Failed to register the Swarm service: %v", err)
- }
-}
-
-func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
- //an account is mandatory
- if bzzaccount == "" {
- utils.Fatalf(SwarmErrNoBZZAccount)
- }
- // Try to load the arg as a hex key file.
- if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
- log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
- return key
- }
- // Otherwise try getting it from the keystore.
- am := stack.AccountManager()
- ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
-
- return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
-}
-
-// getPrivKey returns the private key of the specified bzzaccount
-// Used only by client commands, such as `feed`
-func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
- // booting up the swarm node just as we do in bzzd action
- bzzconfig, err := buildConfig(ctx)
- if err != nil {
- utils.Fatalf("unable to configure swarm: %v", err)
- }
- cfg := defaultNodeConfig
- if _, err := os.Stat(bzzconfig.Path); err == nil {
- cfg.DataDir = bzzconfig.Path
- }
- utils.SetNodeConfig(ctx, &cfg)
- stack, err := node.New(&cfg)
- if err != nil {
- utils.Fatalf("can't create node: %v", err)
- }
- defer stack.Close()
-
- return getAccount(bzzconfig.BzzAccount, ctx, stack)
-}
-
-func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
- var a accounts.Account
- var err error
- if common.IsHexAddress(account) {
- 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))
- }
- } else {
- utils.Fatalf("Can't find swarm account key %s", account)
- }
- if err != nil {
- utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
- }
- keyjson, err := ioutil.ReadFile(a.URL.Path)
- if err != nil {
- utils.Fatalf("Can't load swarm account key: %v", err)
- }
- 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)
- if err == nil {
- return key.PrivateKey
- }
- }
- utils.Fatalf("Can't decrypt swarm account key")
- return nil
-}
-
-// 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
- if prompt != "" {
- fmt.Println(prompt)
- }
- password, err := console.Stdin.PromptPassword("Passphrase: ")
- if err != nil {
- utils.Fatalf("Failed to read passphrase: %v", err)
- }
- return password
-}
-
-// addDefaultHelpSubcommand scans through defined CLI commands and adds
-// a basic help subcommand to each
-// if a help command is already defined, it will take precedence over the default.
-func addDefaultHelpSubcommands(commands []cli.Command) {
- for i := range commands {
- cmd := &commands[i]
- if cmd.Subcommands != nil {
- cmd.Subcommands = append(cmd.Subcommands, defaultSubcommandHelp)
- addDefaultHelpSubcommands(cmd.Subcommands)
- }
- }
-}
-
-func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
- if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) {
- return
- }
-
- cfg.P2P.BootstrapNodes = []*enode.Node{}
-
- for _, url := range SwarmBootnodes {
- node, err := enode.ParseV4(url)
- if err != nil {
- log.Error("Bootstrap URL invalid", "enode", url, "err", err)
- }
- cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node)
- }
-
-}
diff --git a/cmd/swarm/manifest.go b/cmd/swarm/manifest.go
deleted file mode 100644
index 312c72fa2..000000000
--- a/cmd/swarm/manifest.go
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright 2017 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 MANIFEST update
-package main
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/swarm/api"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- "gopkg.in/urfave/cli.v1"
-)
-
-var manifestCommand = cli.Command{
- Name: "manifest",
- CustomHelpTemplate: helpTemplate,
- Usage: "perform operations on swarm manifests",
- ArgsUsage: "COMMAND",
- Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
- Subcommands: []cli.Command{
- {
- Action: manifestAdd,
- CustomHelpTemplate: helpTemplate,
- Name: "add",
- Usage: "add a new path to the manifest",
- ArgsUsage: "<MANIFEST> <path> <hash>",
- Description: "Adds a new path to the manifest",
- },
- {
- Action: manifestUpdate,
- CustomHelpTemplate: helpTemplate,
- Name: "update",
- Usage: "update the hash for an already existing path in the manifest",
- ArgsUsage: "<MANIFEST> <path> <newhash>",
- Description: "Update the hash for an already existing path in the manifest",
- },
- {
- Action: manifestRemove,
- CustomHelpTemplate: helpTemplate,
- Name: "remove",
- Usage: "removes a path from the manifest",
- ArgsUsage: "<MANIFEST> <path>",
- Description: "Removes a path from the manifest",
- },
- },
-}
-
-// manifestAdd adds a new entry to the manifest at the given path.
-// New entry hash, the last argument, must be the hash of a manifest
-// with only one entry, which meta-data will be added to the original manifest.
-// On success, this function will print new (updated) manifest's hash.
-func manifestAdd(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 3 {
- utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
- }
-
- var (
- mhash = args[0]
- path = args[1]
- hash = args[2]
- )
-
- bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client := swarm.NewClient(bzzapi)
-
- m, _, err := client.DownloadManifest(hash)
- if err != nil {
- utils.Fatalf("Error downloading manifest to add: %v", err)
- }
- l := len(m.Entries)
- if l == 0 {
- utils.Fatalf("No entries in manifest %s", hash)
- } else if l > 1 {
- utils.Fatalf("Too many entries in manifest %s", hash)
- }
-
- newManifest := addEntryToManifest(client, mhash, path, m.Entries[0])
- fmt.Println(newManifest)
-}
-
-// manifestUpdate replaces an existing entry of the manifest at the given path.
-// New entry hash, the last argument, must be the hash of a manifest
-// with only one entry, which meta-data will be added to the original manifest.
-// On success, this function will print hash of the updated manifest.
-func manifestUpdate(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 3 {
- utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
- }
-
- var (
- mhash = args[0]
- path = args[1]
- hash = args[2]
- )
-
- bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client := swarm.NewClient(bzzapi)
-
- m, _, err := client.DownloadManifest(hash)
- if err != nil {
- utils.Fatalf("Error downloading manifest to update: %v", err)
- }
- l := len(m.Entries)
- if l == 0 {
- utils.Fatalf("No entries in manifest %s", hash)
- } else if l > 1 {
- utils.Fatalf("Too many entries in manifest %s", hash)
- }
-
- newManifest, _, defaultEntryUpdated := updateEntryInManifest(client, mhash, path, m.Entries[0], true)
- if defaultEntryUpdated {
- // Print informational message to stderr
- // allowing the user to get the new manifest hash from stdout
- // without the need to parse the complete output.
- fmt.Fprintln(os.Stderr, "Manifest default entry is updated, too")
- }
- fmt.Println(newManifest)
-}
-
-// manifestRemove removes an existing entry of the manifest at the given path.
-// On success, this function will print hash of the manifest which does not
-// contain the path.
-func manifestRemove(ctx *cli.Context) {
- args := ctx.Args()
- if len(args) != 2 {
- utils.Fatalf("Need exactly two arguments <MHASH> <path>")
- }
-
- var (
- mhash = args[0]
- path = args[1]
- )
-
- bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- client := swarm.NewClient(bzzapi)
-
- newManifest := removeEntryFromManifest(client, mhash, path)
- fmt.Println(newManifest)
-}
-
-func addEntryToManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry) string {
- var longestPathEntry = api.ManifestEntry{}
-
- mroot, isEncrypted, err := client.DownloadManifest(mhash)
- if err != nil {
- utils.Fatalf("Manifest download failed: %v", err)
- }
-
- // See if we path is in this Manifest or do we have to dig deeper
- for _, e := range mroot.Entries {
- if path == e.Path {
- utils.Fatalf("Path %s already present, not adding anything", path)
- } else {
- if e.ContentType == api.ManifestType {
- prfxlen := strings.HasPrefix(path, e.Path)
- if prfxlen && len(path) > len(longestPathEntry.Path) {
- longestPathEntry = e
- }
- }
- }
- }
-
- if longestPathEntry.Path != "" {
- // Load the child Manifest add the entry there
- newPath := path[len(longestPathEntry.Path):]
- newHash := addEntryToManifest(client, longestPathEntry.Hash, newPath, entry)
-
- // Replace the hash for parent Manifests
- newMRoot := &api.Manifest{}
- for _, e := range mroot.Entries {
- if longestPathEntry.Path == e.Path {
- e.Hash = newHash
- }
- newMRoot.Entries = append(newMRoot.Entries, e)
- }
- mroot = newMRoot
- } else {
- // Add the entry in the leaf Manifest
- entry.Path = path
- mroot.Entries = append(mroot.Entries, entry)
- }
-
- newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
- if err != nil {
- utils.Fatalf("Manifest upload failed: %v", err)
- }
- return newManifestHash
-}
-
-// updateEntryInManifest updates an existing entry o path with a new one in the manifest with provided mhash
-// finding the path recursively through all nested manifests. Argument isRoot is used for default
-// entry update detection. If the updated entry has the same hash as the default entry, then the
-// default entry in root manifest will be updated too.
-// Returned values are the new manifest hash, hash of the entry that was replaced by the new entry and
-// a a bool that is true if default entry is updated.
-func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry, isRoot bool) (newManifestHash, oldHash string, defaultEntryUpdated bool) {
- var (
- newEntry = api.ManifestEntry{}
- longestPathEntry = api.ManifestEntry{}
- )
-
- mroot, isEncrypted, err := client.DownloadManifest(mhash)
- if err != nil {
- utils.Fatalf("Manifest download failed: %v", err)
- }
-
- // See if we path is in this Manifest or do we have to dig deeper
- for _, e := range mroot.Entries {
- if path == e.Path {
- newEntry = e
- // keep the reference of the hash of the entry that should be replaced
- // for default entry detection
- oldHash = e.Hash
- } else {
- if e.ContentType == api.ManifestType {
- prfxlen := strings.HasPrefix(path, e.Path)
- if prfxlen && len(path) > len(longestPathEntry.Path) {
- longestPathEntry = e
- }
- }
- }
- }
-
- if longestPathEntry.Path == "" && newEntry.Path == "" {
- utils.Fatalf("Path %s not present in the Manifest, not setting anything", path)
- }
-
- if longestPathEntry.Path != "" {
- // Load the child Manifest add the entry there
- newPath := path[len(longestPathEntry.Path):]
- var newHash string
- newHash, oldHash, _ = updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry, false)
-
- // Replace the hash for parent Manifests
- newMRoot := &api.Manifest{}
- for _, e := range mroot.Entries {
- if longestPathEntry.Path == e.Path {
- e.Hash = newHash
- }
- newMRoot.Entries = append(newMRoot.Entries, e)
-
- }
- mroot = newMRoot
- }
-
- // update the manifest if the new entry is found and
- // check if default entry should be updated
- if newEntry.Path != "" || isRoot {
- // Replace the hash for leaf Manifest
- newMRoot := &api.Manifest{}
- for _, e := range mroot.Entries {
- if newEntry.Path == e.Path {
- entry.Path = e.Path
- newMRoot.Entries = append(newMRoot.Entries, entry)
- } else if isRoot && e.Path == "" && e.Hash == oldHash {
- entry.Path = e.Path
- newMRoot.Entries = append(newMRoot.Entries, entry)
- defaultEntryUpdated = true
- } else {
- newMRoot.Entries = append(newMRoot.Entries, e)
- }
- }
- mroot = newMRoot
- }
-
- newManifestHash, err = client.UploadManifest(mroot, isEncrypted)
- if err != nil {
- utils.Fatalf("Manifest upload failed: %v", err)
- }
- return newManifestHash, oldHash, defaultEntryUpdated
-}
-
-func removeEntryFromManifest(client *swarm.Client, mhash, path string) string {
- var (
- entryToRemove = api.ManifestEntry{}
- longestPathEntry = api.ManifestEntry{}
- )
-
- mroot, isEncrypted, err := client.DownloadManifest(mhash)
- if err != nil {
- utils.Fatalf("Manifest download failed: %v", err)
- }
-
- // See if we path is in this Manifest or do we have to dig deeper
- for _, entry := range mroot.Entries {
- if path == entry.Path {
- entryToRemove = entry
- } else {
- if entry.ContentType == api.ManifestType {
- prfxlen := strings.HasPrefix(path, entry.Path)
- if prfxlen && len(path) > len(longestPathEntry.Path) {
- longestPathEntry = entry
- }
- }
- }
- }
-
- if longestPathEntry.Path == "" && entryToRemove.Path == "" {
- utils.Fatalf("Path %s not present in the Manifest, not removing anything", path)
- }
-
- if longestPathEntry.Path != "" {
- // Load the child Manifest remove the entry there
- newPath := path[len(longestPathEntry.Path):]
- newHash := removeEntryFromManifest(client, longestPathEntry.Hash, newPath)
-
- // Replace the hash for parent Manifests
- newMRoot := &api.Manifest{}
- for _, entry := range mroot.Entries {
- if longestPathEntry.Path == entry.Path {
- entry.Hash = newHash
- }
- newMRoot.Entries = append(newMRoot.Entries, entry)
- }
- mroot = newMRoot
- }
-
- if entryToRemove.Path != "" {
- // remove the entry in this Manifest
- newMRoot := &api.Manifest{}
- for _, entry := range mroot.Entries {
- if entryToRemove.Path != entry.Path {
- newMRoot.Entries = append(newMRoot.Entries, entry)
- }
- }
- mroot = newMRoot
- }
-
- newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
- if err != nil {
- utils.Fatalf("Manifest upload failed: %v", err)
- }
- return newManifestHash
-}
diff --git a/cmd/swarm/manifest_test.go b/cmd/swarm/manifest_test.go
deleted file mode 100644
index 01d982aa7..000000000
--- a/cmd/swarm/manifest_test.go
+++ /dev/null
@@ -1,597 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "testing"
-
- "github.com/ethereum/go-ethereum/swarm/api"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
- swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
-)
-
-// TestManifestChange tests manifest add, update and remove
-// cli commands without encryption.
-func TestManifestChange(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- testManifestChange(t, false)
-}
-
-// TestManifestChange tests manifest add, update and remove
-// cli commands with encryption enabled.
-func TestManifestChangeEncrypted(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- testManifestChange(t, true)
-}
-
-// testManifestChange performs cli commands:
-// - manifest add
-// - manifest update
-// - manifest remove
-// on a manifest, testing the functionality of this
-// comands on paths that are in root manifest or a nested one.
-// Argument encrypt controls whether to use encryption or not.
-func testManifestChange(t *testing.T, encrypt bool) {
- t.Parallel()
- srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
- defer srv.Close()
-
- tmp, err := ioutil.TempDir("", "swarm-manifest-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
-
- origDir := filepath.Join(tmp, "orig")
- if err := os.Mkdir(origDir, 0777); err != nil {
- t.Fatal(err)
- }
-
- indexDataFilename := filepath.Join(origDir, "index.html")
- err = ioutil.WriteFile(indexDataFilename, []byte("<h1>Test</h1>"), 0666)
- if err != nil {
- t.Fatal(err)
- }
- // Files paths robots.txt and robots.html share the same prefix "robots."
- // which will result a manifest with a nested manifest under path "robots.".
- // This will allow testing manifest changes on both root and nested manifest.
- err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("<strong>No Robots Allowed</strong>"), 0666)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- args := []string{
- "--bzzapi",
- srv.URL,
- "--recursive",
- "--defaultpath",
- indexDataFilename,
- "up",
- origDir,
- }
- if encrypt {
- args = append(args, "--encrypt")
- }
-
- origManifestHash := runSwarmExpectHash(t, args...)
-
- checkHashLength(t, origManifestHash, encrypt)
-
- client := swarm.NewClient(srv.URL)
-
- // upload a new file and use its manifest to add it the original manifest.
- t.Run("add", func(t *testing.T) {
- humansData := []byte("Ann\nBob")
- humansDataFilename := filepath.Join(tmp, "humans.txt")
- err = ioutil.WriteFile(humansDataFilename, humansData, 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- humansManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "up",
- humansDataFilename,
- )
-
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "add",
- origManifestHash,
- "humans.txt",
- humansManifestHash,
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- for _, e := range newManifest.Entries {
- if e.Path == "humans.txt" {
- found = true
- if e.Size != int64(len(humansData)) {
- t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size)
- }
- if e.ModTime.IsZero() {
- t.Errorf("got zero mod time for humans.txt")
- }
- ct := "text/plain; charset=utf-8"
- if e.ContentType != ct {
- t.Errorf("expected content type %q, got %q", ct, e.ContentType)
- }
- break
- }
- }
- if !found {
- t.Fatal("no humans.txt in new manifest")
- }
-
- checkFile(t, client, newManifestHash, "humans.txt", humansData)
- })
-
- // upload a new file and use its manifest to add it the original manifest,
- // but ensure that the file will be in the nested manifest of the original one.
- t.Run("add nested", func(t *testing.T) {
- robotsData := []byte(`{"disallow": "/"}`)
- robotsDataFilename := filepath.Join(tmp, "robots.json")
- err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- robotsManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "up",
- robotsDataFilename,
- )
-
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "add",
- origManifestHash,
- "robots.json",
- robotsManifestHash,
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- loop:
- for _, e := range newManifest.Entries {
- if e.Path == "robots." {
- nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
- for _, e := range nestedManifest.Entries {
- if e.Path == "json" {
- found = true
- if e.Size != int64(len(robotsData)) {
- t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size)
- }
- if e.ModTime.IsZero() {
- t.Errorf("got zero mod time for robots.json")
- }
- ct := "application/json"
- if e.ContentType != ct {
- t.Errorf("expected content type %q, got %q", ct, e.ContentType)
- }
- break loop
- }
- }
- }
- }
- if !found {
- t.Fatal("no robots.json in new manifest")
- }
-
- checkFile(t, client, newManifestHash, "robots.json", robotsData)
- })
-
- // upload a new file and use its manifest to change the file it the original manifest.
- t.Run("update", func(t *testing.T) {
- indexData := []byte("<h1>Ethereum Swarm</h1>")
- indexDataFilename := filepath.Join(tmp, "index.html")
- err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- indexManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "up",
- indexDataFilename,
- )
-
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "update",
- origManifestHash,
- "index.html",
- indexManifestHash,
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- for _, e := range newManifest.Entries {
- if e.Path == "index.html" {
- found = true
- if e.Size != int64(len(indexData)) {
- t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size)
- }
- if e.ModTime.IsZero() {
- t.Errorf("got zero mod time for index.html")
- }
- ct := "text/html; charset=utf-8"
- if e.ContentType != ct {
- t.Errorf("expected content type %q, got %q", ct, e.ContentType)
- }
- break
- }
- }
- if !found {
- t.Fatal("no index.html in new manifest")
- }
-
- checkFile(t, client, newManifestHash, "index.html", indexData)
-
- // check default entry change
- checkFile(t, client, newManifestHash, "", indexData)
- })
-
- // upload a new file and use its manifest to change the file it the original manifest,
- // but ensure that the file is in the nested manifest of the original one.
- t.Run("update nested", func(t *testing.T) {
- robotsData := []byte(`<string>Only humans allowed!!!</strong>`)
- robotsDataFilename := filepath.Join(tmp, "robots.html")
- err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- humansManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "up",
- robotsDataFilename,
- )
-
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "update",
- origManifestHash,
- "robots.html",
- humansManifestHash,
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- loop:
- for _, e := range newManifest.Entries {
- if e.Path == "robots." {
- nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
- for _, e := range nestedManifest.Entries {
- if e.Path == "html" {
- found = true
- if e.Size != int64(len(robotsData)) {
- t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size)
- }
- if e.ModTime.IsZero() {
- t.Errorf("got zero mod time for robots.html")
- }
- ct := "text/html; charset=utf-8"
- if e.ContentType != ct {
- t.Errorf("expected content type %q, got %q", ct, e.ContentType)
- }
- break loop
- }
- }
- }
- }
- if !found {
- t.Fatal("no robots.html in new manifest")
- }
-
- checkFile(t, client, newManifestHash, "robots.html", robotsData)
- })
-
- // remove a file from the manifest.
- t.Run("remove", func(t *testing.T) {
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "remove",
- origManifestHash,
- "mutants.txt",
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- for _, e := range newManifest.Entries {
- if e.Path == "mutants.txt" {
- found = true
- break
- }
- }
- if found {
- t.Fatal("mutants.txt is not removed")
- }
- })
-
- // remove a file from the manifest, but ensure that the file is in
- // the nested manifest of the original one.
- t.Run("remove nested", func(t *testing.T) {
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "remove",
- origManifestHash,
- "robots.html",
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- loop:
- for _, e := range newManifest.Entries {
- if e.Path == "robots." {
- nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
- for _, e := range nestedManifest.Entries {
- if e.Path == "html" {
- found = true
- break loop
- }
- }
- }
- }
- if found {
- t.Fatal("robots.html in not removed")
- }
- })
-}
-
-// TestNestedDefaultEntryUpdate tests if the default entry is updated
-// if the file in nested manifest used for it is also updated.
-func TestNestedDefaultEntryUpdate(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- testNestedDefaultEntryUpdate(t, false)
-}
-
-// TestNestedDefaultEntryUpdateEncrypted tests if the default entry
-// of encrypted upload is updated if the file in nested manifest
-// used for it is also updated.
-func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- testNestedDefaultEntryUpdate(t, true)
-}
-
-func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
- t.Parallel()
- srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
- defer srv.Close()
-
- tmp, err := ioutil.TempDir("", "swarm-manifest-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
-
- origDir := filepath.Join(tmp, "orig")
- if err := os.Mkdir(origDir, 0777); err != nil {
- t.Fatal(err)
- }
-
- indexData := []byte("<h1>Test</h1>")
- indexDataFilename := filepath.Join(origDir, "index.html")
- err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
- if err != nil {
- t.Fatal(err)
- }
- // Add another file with common prefix as the default entry to test updates of
- // default entry with nested manifests.
- err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- args := []string{
- "--bzzapi",
- srv.URL,
- "--recursive",
- "--defaultpath",
- indexDataFilename,
- "up",
- origDir,
- }
- if encrypt {
- args = append(args, "--encrypt")
- }
-
- origManifestHash := runSwarmExpectHash(t, args...)
-
- checkHashLength(t, origManifestHash, encrypt)
-
- client := swarm.NewClient(srv.URL)
-
- newIndexData := []byte("<h1>Ethereum Swarm</h1>")
- newIndexDataFilename := filepath.Join(tmp, "index.html")
- err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- newIndexManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "up",
- newIndexDataFilename,
- )
-
- newManifestHash := runSwarmExpectHash(t,
- "--bzzapi",
- srv.URL,
- "manifest",
- "update",
- origManifestHash,
- "index.html",
- newIndexManifestHash,
- )
-
- checkHashLength(t, newManifestHash, encrypt)
-
- newManifest := downloadManifest(t, client, newManifestHash, encrypt)
-
- var found bool
- for _, e := range newManifest.Entries {
- if e.Path == "index." {
- found = true
- newManifest = downloadManifest(t, client, e.Hash, encrypt)
- break
- }
- }
- if !found {
- t.Fatal("no index. path in new manifest")
- }
-
- found = false
- for _, e := range newManifest.Entries {
- if e.Path == "html" {
- found = true
- if e.Size != int64(len(newIndexData)) {
- t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size)
- }
- if e.ModTime.IsZero() {
- t.Errorf("got zero mod time for index.html")
- }
- ct := "text/html; charset=utf-8"
- if e.ContentType != ct {
- t.Errorf("expected content type %q, got %q", ct, e.ContentType)
- }
- break
- }
- }
- if !found {
- t.Fatal("no html in new manifest")
- }
-
- checkFile(t, client, newManifestHash, "index.html", newIndexData)
-
- // check default entry change
- checkFile(t, client, newManifestHash, "", newIndexData)
-}
-
-func runSwarmExpectHash(t *testing.T, args ...string) (hash string) {
- t.Helper()
- hashRegexp := `[a-f\d]{64,128}`
- up := runSwarm(t, args...)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
-
- if len(matches) < 1 {
- t.Fatal("no matches found")
- }
- return matches[0]
-}
-
-func checkHashLength(t *testing.T, hash string, encrypted bool) {
- t.Helper()
- l := len(hash)
- if encrypted && l != 128 {
- t.Errorf("expected hash length 128, got %v", l)
- }
- if !encrypted && l != 64 {
- t.Errorf("expected hash length 64, got %v", l)
- }
-}
-
-func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) {
- t.Helper()
- m, isEncrypted, err := client.DownloadManifest(hash)
- if err != nil {
- t.Fatal(err)
- }
-
- if encrypted != isEncrypted {
- t.Error("new manifest encryption flag is not correct")
- }
- return m
-}
-
-func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) {
- t.Helper()
- f, err := client.Download(hash, path)
- if err != nil {
- t.Fatal(err)
- }
-
- got, err := ioutil.ReadAll(f)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(got, expected) {
- t.Errorf("expected file content %q, got %q", expected, got)
- }
-}
diff --git a/cmd/swarm/mimegen/generator.go b/cmd/swarm/mimegen/generator.go
deleted file mode 100644
index 68f9e306e..000000000
--- a/cmd/swarm/mimegen/generator.go
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2018 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
-
-// Standard "mime" package rely on system-settings, see mime.osInitMime
-// Swarm will run on many OS/Platform/Docker and must behave similar
-// This command generates code to add common mime types based on mime.types file
-//
-// mime.types file provided by mailcap, which follow https://www.iana.org/assignments/media-types/media-types.xhtml
-//
-// Get last version of mime.types file by:
-// docker run --rm -v $(pwd):/tmp alpine:edge /bin/sh -c "apk add -U mailcap; mv /etc/mime.types /tmp"
-
-import (
- "bufio"
- "bytes"
- "flag"
- "html/template"
- "io/ioutil"
- "strings"
-
- "log"
-)
-
-var (
- typesFlag = flag.String("types", "", "Input mime.types file")
- packageFlag = flag.String("package", "", "Golang package in output file")
- outFlag = flag.String("out", "", "Output file name for the generated mime types")
-)
-
-type mime struct {
- Name string
- Exts []string
-}
-
-type templateParams struct {
- PackageName string
- Mimes []mime
-}
-
-func main() {
- // Parse and ensure all needed inputs are specified
- flag.Parse()
- if *typesFlag == "" {
- log.Fatalf("--types is required")
- }
- if *packageFlag == "" {
- log.Fatalf("--types is required")
- }
- if *outFlag == "" {
- log.Fatalf("--out is required")
- }
-
- params := templateParams{
- PackageName: *packageFlag,
- }
-
- types, err := ioutil.ReadFile(*typesFlag)
- if err != nil {
- log.Fatal(err)
- }
-
- scanner := bufio.NewScanner(bytes.NewReader(types))
- for scanner.Scan() {
- txt := scanner.Text()
- if strings.HasPrefix(txt, "#") || len(txt) == 0 {
- continue
- }
- parts := strings.Fields(txt)
- if len(parts) == 1 {
- continue
- }
- params.Mimes = append(params.Mimes, mime{parts[0], parts[1:]})
- }
-
- if err = scanner.Err(); err != nil {
- log.Fatal(err)
- }
-
- result := bytes.NewBuffer([]byte{})
-
- if err := template.Must(template.New("_").Parse(tpl)).Execute(result, params); err != nil {
- log.Fatal(err)
- }
-
- if err := ioutil.WriteFile(*outFlag, result.Bytes(), 0600); err != nil {
- log.Fatal(err)
- }
-}
-
-var tpl = `// Code generated by github.com/ethereum/go-ethereum/cmd/swarm/mimegen. DO NOT EDIT.
-
-package {{ .PackageName }}
-
-import "mime"
-func init() {
- var mimeTypes = map[string]string{
-{{- range .Mimes -}}
- {{ $name := .Name -}}
- {{- range .Exts }}
- ".{{ . }}": "{{ $name | html }}",
- {{- end }}
-{{- end }}
- }
- for ext, name := range mimeTypes {
- if err := mime.AddExtensionType(ext, name); err != nil {
- panic(err)
- }
- }
-}
-`
diff --git a/cmd/swarm/mimegen/mime.types b/cmd/swarm/mimegen/mime.types
deleted file mode 100644
index 1bdf21149..000000000
--- a/cmd/swarm/mimegen/mime.types
+++ /dev/null
@@ -1,1828 +0,0 @@
-# This is a comment. I love comments. -*- indent-tabs-mode: t -*-
-
-# This file controls what Internet media types are sent to the client for
-# given file extension(s). Sending the correct media type to the client
-# is important so they know how to handle the content of the file.
-# Extra types can either be added here or by using an AddType directive
-# in your config files. For more information about Internet media types,
-# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
-# registry is at <http://www.iana.org/assignments/media-types/>.
-
-# IANA types
-
-# MIME type Extensions
-application/1d-interleaved-parityfec
-application/3gpdash-qoe-report+xml
-application/3gpp-ims+xml
-application/A2L a2l
-application/activemessage
-application/alto-costmap+json
-application/alto-costmapfilter+json
-application/alto-directory+json
-application/alto-endpointcost+json
-application/alto-endpointcostparams+json
-application/alto-endpointprop+json
-application/alto-endpointpropparams+json
-application/alto-error+json
-application/alto-networkmap+json
-application/alto-networkmapfilter+json
-application/AML aml
-application/andrew-inset ez
-application/applefile
-application/ATF atf
-application/ATFX atfx
-application/ATXML atxml
-application/atom+xml atom
-application/atomcat+xml atomcat
-application/atomdeleted+xml atomdeleted
-application/atomicmail
-application/atomsvc+xml atomsvc
-application/auth-policy+xml apxml
-application/bacnet-xdd+zip xdd
-application/batch-SMTP
-application/beep+xml
-application/calendar+json
-application/calendar+xml xcs
-application/call-completion
-application/cals-1840
-application/cbor cbor
-application/ccmp+xml ccmp
-application/ccxml+xml ccxml
-application/CDFX+XML cdfx
-application/cdmi-capability cdmia
-application/cdmi-container cdmic
-application/cdmi-domain cdmid
-application/cdmi-object cdmio
-application/cdmi-queue cdmiq
-application/cdni
-application/CEA cea
-application/cea-2018+xml
-application/cellml+xml cellml cml
-application/cfw
-application/clue_info+xml clue
-application/cms cmsc
-application/cnrp+xml
-application/coap-group+json
-application/coap-payload
-application/commonground
-application/conference-info+xml
-application/cpl+xml cpl
-application/cose
-application/cose-key
-application/cose-key-set
-application/csrattrs csrattrs
-application/csta+xml
-application/CSTAdata+xml
-application/csvm+json
-application/cybercash
-application/dash+xml mpd
-application/dashdelta mpdd
-application/davmount+xml davmount
-application/dca-rft
-application/DCD dcd
-application/dec-dx
-application/dialog-info+xml
-application/dicom dcm
-application/dicom+json
-application/dicom+xml
-application/DII dii
-application/DIT dit
-application/dns
-application/dskpp+xml xmls
-application/dssc+der dssc
-application/dssc+xml xdssc
-application/dvcs dvc
-application/ecmascript es
-application/EDI-Consent
-application/EDI-X12
-application/EDIFACT
-application/efi efi
-application/EmergencyCallData.Comment+xml
-application/EmergencyCallData.Control+xml
-application/EmergencyCallData.DeviceInfo+xml
-application/EmergencyCallData.eCall.MSD
-application/EmergencyCallData.ProviderInfo+xml
-application/EmergencyCallData.ServiceInfo+xml
-application/EmergencyCallData.SubscriberInfo+xml
-application/EmergencyCallData.VEDS+xml
-application/emma+xml emma
-application/emotionml+xml emotionml
-application/encaprtp
-application/epp+xml
-application/epub+zip epub
-application/eshop
-application/exi exi
-application/fastinfoset finf
-application/fastsoap
-application/fdt+xml fdt
-# fits, fit, fts: image/fits
-application/fits
-# application/font-sfnt deprecated in favor of font/sfnt
-application/font-tdpfr pfr
-# application/font-woff deprecated in favor of font/woff
-application/framework-attributes+xml
-application/geo+json geojson
-application/geo+json-seq
-application/gml+xml gml
-application/gzip gz tgz
-application/H224
-application/held+xml
-application/http
-application/hyperstudio stk
-application/ibe-key-request+xml
-application/ibe-pkg-reply+xml
-application/ibe-pp-data
-application/iges
-application/im-iscomposing+xml
-application/index
-application/index.cmd
-application/index.obj
-application/index.response
-application/index.vnd
-application/inkml+xml ink inkml
-application/iotp
-application/ipfix ipfix
-application/ipp
-application/isup
-application/its+xml its
-application/javascript js
-application/jose
-application/jose+json
-application/jrd+json jrd
-application/json json
-application/json-patch+json json-patch
-application/json-seq
-application/jwk+json
-application/jwk-set+json
-application/jwt
-application/kpml-request+xml
-application/kpml-response+xml
-application/ld+json jsonld
-application/lgr+xml lgr
-application/link-format wlnk
-application/load-control+xml
-application/lost+xml lostxml
-application/lostsync+xml lostsyncxml
-application/LXF lxf
-application/mac-binhex40 hqx
-application/macwriteii
-application/mads+xml mads
-application/marc mrc
-application/marcxml+xml mrcx
-application/mathematica nb ma mb
-application/mathml-content+xml
-application/mathml-presentation+xml
-application/mathml+xml mml
-application/mbms-associated-procedure-description+xml
-application/mbms-deregister+xml
-application/mbms-envelope+xml
-application/mbms-msk-response+xml
-application/mbms-msk+xml
-application/mbms-protection-description+xml
-application/mbms-reception-report+xml
-application/mbms-register-response+xml
-application/mbms-register+xml
-application/mbms-schedule+xml
-application/mbms-user-service-description+xml
-application/mbox mbox
-application/media_control+xml
-# mpf: text/vnd.ms-mediapackage
-application/media-policy-dataset+xml
-application/mediaservercontrol+xml
-application/merge-patch+json
-application/metalink4+xml meta4
-application/mets+xml mets
-application/MF4 mf4
-application/mikey
-application/mods+xml mods
-application/moss-keys
-application/moss-signature
-application/mosskey-data
-application/mosskey-request
-application/mp21 m21 mp21
-# mp4, mpg4: video/mp4, see RFC 4337
-application/mp4
-application/mpeg4-generic
-application/mpeg4-iod
-application/mpeg4-iod-xmt
-# xdf: application/xcap-diff+xml
-application/mrb-consumer+xml
-application/mrb-publish+xml
-application/msc-ivr+xml
-application/msc-mixer+xml
-application/msword doc
-application/mud+json
-application/mxf mxf
-application/n-quads nq
-application/n-triples nt
-application/nasdata
-application/news-checkgroups
-application/news-groupinfo
-application/news-transmission
-application/nlsml+xml
-application/nss
-application/ocsp-request orq
-application/ocsp-response ors
-application/octet-stream bin lha lzh exe class so dll img iso
-application/oda oda
-application/ODX odx
-application/oebps-package+xml opf
-application/ogg ogx
-application/oxps oxps
-application/p2p-overlay+xml relo
-application/parityfec
-# xer: application/xcap-error+xml
-application/patch-ops-error+xml
-application/pdf pdf
-application/PDX pdx
-application/pgp-encrypted pgp
-application/pgp-keys
-application/pgp-signature sig
-application/pidf-diff+xml
-application/pidf+xml
-application/pkcs10 p10
-application/pkcs12 p12 pfx
-application/pkcs7-mime p7m p7c
-application/pkcs7-signature p7s
-application/pkcs8 p8
-# ac: application/vnd.nokia.n-gage.ac+xml
-application/pkix-attr-cert
-application/pkix-cert cer
-application/pkix-crl crl
-application/pkix-pkipath pkipath
-application/pkixcmp pki
-application/pls+xml pls
-application/poc-settings+xml
-application/postscript ps eps ai
-application/ppsp-tracker+json
-application/problem+json
-application/problem+xml
-application/provenance+xml provx
-application/prs.alvestrand.titrax-sheet
-application/prs.cww cw cww
-application/prs.hpub+zip hpub
-application/prs.nprend rnd rct
-application/prs.plucker
-application/prs.rdf-xml-crypt rdf-crypt
-application/prs.xsf+xml xsf
-application/pskc+xml pskcxml
-application/qsig
-application/raptorfec
-application/rdap+json
-application/rdf+xml rdf
-application/reginfo+xml rif
-application/relax-ng-compact-syntax rnc
-application/remote-printing
-application/reputon+json
-application/resource-lists-diff+xml rld
-application/resource-lists+xml rl
-application/rfc+xml rfcxml
-application/riscos
-application/rlmi+xml
-application/rls-services+xml rs
-application/rpki-ghostbusters gbr
-application/rpki-manifest mft
-application/rpki-publication
-application/rpki-roa roa
-application/rpki-updown
-application/rtf rtf
-application/rtploopback
-application/rtx
-application/samlassertion+xml
-application/samlmetadata+xml
-application/sbml+xml
-application/scaip+xml
-# scm: application/vnd.lotus-screencam
-application/scim+json scim
-application/scvp-cv-request scq
-application/scvp-cv-response scs
-application/scvp-vp-request spq
-application/scvp-vp-response spp
-application/sdp sdp
-application/sep+xml
-application/sep-exi
-application/session-info
-application/set-payment
-application/set-payment-initiation
-application/set-registration
-application/set-registration-initiation
-application/sgml
-application/sgml-open-catalog soc
-application/shf+xml shf
-application/sieve siv sieve
-application/simple-filter+xml cl
-application/simple-message-summary
-application/simpleSymbolContainer
-application/slate
-# application/smil obsoleted by application/smil+xml
-application/smil+xml smil smi sml
-application/smpte336m
-application/soap+fastinfoset
-application/soap+xml
-application/sparql-query rq
-application/sparql-results+xml srx
-application/spirits-event+xml
-application/sql sql
-application/srgs gram
-application/srgs+xml grxml
-application/sru+xml sru
-application/ssml+xml ssml
-application/tamp-apex-update tau
-application/tamp-apex-update-confirm auc
-application/tamp-community-update tcu
-application/tamp-community-update-confirm cuc
-application/tamp-error ter
-application/tamp-sequence-adjust tsa
-application/tamp-sequence-adjust-confirm sac
-# tsq: application/timestamp-query
-application/tamp-status-query
-# tsr: application/timestamp-reply
-application/tamp-status-response
-application/tamp-update tur
-application/tamp-update-confirm tuc
-application/tei+xml tei teiCorpus odd
-application/thraud+xml tfi
-application/timestamp-query tsq
-application/timestamp-reply tsr
-application/timestamped-data tsd
-application/trig trig
-application/ttml+xml ttml
-application/tve-trigger
-application/ulpfec
-application/urc-grpsheet+xml gsheet
-application/urc-ressheet+xml rsheet
-application/urc-targetdesc+xml td
-application/urc-uisocketdesc+xml uis
-application/vcard+json
-application/vcard+xml
-application/vemmi
-application/vnd.3gpp.access-transfer-events+xml
-application/vnd.3gpp.bsf+xml
-application/vnd.3gpp.mid-call+xml
-application/vnd.3gpp.pic-bw-large plb
-application/vnd.3gpp.pic-bw-small psb
-application/vnd.3gpp.pic-bw-var pvb
-application/vnd.3gpp-prose+xml
-application/vnd.3gpp-prose-pc3ch+xml
-# sms: application/vnd.3gpp2.sms
-application/vnd.3gpp.sms
-application/vnd.3gpp.sms+xml
-application/vnd.3gpp.srvcc-ext+xml
-application/vnd.3gpp.SRVCC-info+xml
-application/vnd.3gpp.state-and-event-info+xml
-application/vnd.3gpp.ussd+xml
-application/vnd.3gpp2.bcmcsinfo+xml
-application/vnd.3gpp2.sms sms
-application/vnd.3gpp2.tcap tcap
-application/vnd.3lightssoftware.imagescal imgcal
-application/vnd.3M.Post-it-Notes pwn
-application/vnd.accpac.simply.aso aso
-application/vnd.accpac.simply.imp imp
-application/vnd.acucobol acu
-application/vnd.acucorp atc acutc
-application/vnd.adobe.flash.movie swf
-application/vnd.adobe.formscentral.fcdt fcdt
-application/vnd.adobe.fxp fxp fxpl
-application/vnd.adobe.partial-upload
-application/vnd.adobe.xdp+xml xdp
-application/vnd.adobe.xfdf xfdf
-application/vnd.aether.imp
-application/vnd.ah-barcode
-application/vnd.ahead.space ahead
-application/vnd.airzip.filesecure.azf azf
-application/vnd.airzip.filesecure.azs azs
-application/vnd.amazon.mobi8-ebook azw3
-application/vnd.americandynamics.acc acc
-application/vnd.amiga.ami ami
-application/vnd.amundsen.maze+xml
-application/vnd.anki apkg
-application/vnd.anser-web-certificate-issue-initiation cii
-# Not in IANA listing, but is on FTP site?
-application/vnd.anser-web-funds-transfer-initiation fti
-# atx: audio/ATRAC-X
-application/vnd.antix.game-component
-application/vnd.apache.thrift.binary
-application/vnd.apache.thrift.compact
-application/vnd.apache.thrift.json
-application/vnd.api+json
-application/vnd.apothekende.reservation+json
-application/vnd.apple.installer+xml dist distz pkg mpkg
-# m3u: audio/x-mpegurl for now
-application/vnd.apple.mpegurl m3u8
-# application/vnd.arastra.swi obsoleted by application/vnd.aristanetworks.swi
-application/vnd.aristanetworks.swi swi
-application/vnd.artsquare
-application/vnd.astraea-software.iota iota
-application/vnd.audiograph aep
-application/vnd.autopackage package
-application/vnd.avistar+xml
-application/vnd.balsamiq.bmml+xml bmml
-application/vnd.balsamiq.bmpr bmpr
-application/vnd.bekitzur-stech+json
-application/vnd.bint.med-content
-application/vnd.biopax.rdf+xml
-application/vnd.blueice.multipass mpm
-application/vnd.bluetooth.ep.oob ep
-application/vnd.bluetooth.le.oob le
-application/vnd.bmi bmi
-application/vnd.businessobjects rep
-application/vnd.cab-jscript
-application/vnd.canon-cpdl
-application/vnd.canon-lips
-application/vnd.capasystems-pg+json
-application/vnd.cendio.thinlinc.clientconf tlclient
-application/vnd.century-systems.tcp_stream
-application/vnd.chemdraw+xml cdxml
-application/vnd.chess-pgn pgn
-application/vnd.chipnuts.karaoke-mmd mmd
-application/vnd.cinderella cdy
-application/vnd.cirpack.isdn-ext
-application/vnd.citationstyles.style+xml csl
-application/vnd.claymore cla
-application/vnd.cloanto.rp9 rp9
-application/vnd.clonk.c4group c4g c4d c4f c4p c4u
-application/vnd.cluetrust.cartomobile-config c11amc
-application/vnd.cluetrust.cartomobile-config-pkg c11amz
-application/vnd.coffeescript coffee
-application/vnd.collection+json
-application/vnd.collection.doc+json
-application/vnd.collection.next+json
-application/vnd.comicbook+zip cbz
-# icc: application/vnd.iccprofile
-application/vnd.commerce-battelle ica icf icd ic0 ic1 ic2 ic3 ic4 ic5 ic6 ic7 ic8
-application/vnd.commonspace csp cst
-application/vnd.contact.cmsg cdbcmsg
-application/vnd.coreos.ignition+json ign ignition
-application/vnd.cosmocaller cmc
-application/vnd.crick.clicker clkx
-application/vnd.crick.clicker.keyboard clkk
-application/vnd.crick.clicker.palette clkp
-application/vnd.crick.clicker.template clkt
-application/vnd.crick.clicker.wordbank clkw
-application/vnd.criticaltools.wbs+xml wbs
-application/vnd.ctc-posml pml
-application/vnd.ctct.ws+xml
-application/vnd.cups-pdf
-application/vnd.cups-postscript
-application/vnd.cups-ppd ppd
-application/vnd.cups-raster
-application/vnd.cups-raw
-application/vnd.curl curl
-application/vnd.cyan.dean.root+xml
-application/vnd.cybank
-application/vnd.d2l.coursepackage1p0+zip
-application/vnd.dart dart
-application/vnd.data-vision.rdz rdz
-application/vnd.datapackage+json
-application/vnd.dataresource+json
-application/vnd.debian.binary-package deb udeb
-application/vnd.dece.data uvf uvvf uvd uvvd
-application/vnd.dece.ttml+xml uvt uvvt
-application/vnd.dece.unspecified uvx uvvx
-application/vnd.dece.zip uvz uvvz
-application/vnd.denovo.fcselayout-link fe_launch
-application/vnd.desmume.movie dsm
-application/vnd.dir-bi.plate-dl-nosuffix
-application/vnd.dm.delegation+xml
-application/vnd.dna dna
-application/vnd.document+json docjson
-application/vnd.dolby.mobile.1
-application/vnd.dolby.mobile.2
-application/vnd.doremir.scorecloud-binary-document scld
-application/vnd.dpgraph dpg mwc dpgraph
-application/vnd.dreamfactory dfac
-application/vnd.drive+json
-application/vnd.dtg.local
-application/vnd.dtg.local.flash fla
-application/vnd.dtg.local.html
-application/vnd.dvb.ait ait
-# class: application/octet-stream
-application/vnd.dvb.dvbj
-application/vnd.dvb.esgcontainer
-application/vnd.dvb.ipdcdftnotifaccess
-application/vnd.dvb.ipdcesgaccess
-application/vnd.dvb.ipdcesgaccess2
-application/vnd.dvb.ipdcesgpdd
-application/vnd.dvb.ipdcroaming
-application/vnd.dvb.iptv.alfec-base
-application/vnd.dvb.iptv.alfec-enhancement
-application/vnd.dvb.notif-aggregate-root+xml
-application/vnd.dvb.notif-container+xml
-application/vnd.dvb.notif-generic+xml
-application/vnd.dvb.notif-ia-msglist+xml
-application/vnd.dvb.notif-ia-registration-request+xml
-application/vnd.dvb.notif-ia-registration-response+xml
-application/vnd.dvb.notif-init+xml
-# pfr: application/font-tdpfr
-application/vnd.dvb.pfr
-application/vnd.dvb.service svc
-# dxr: application/x-director
-application/vnd.dxr
-application/vnd.dynageo geo
-application/vnd.dzr dzr
-application/vnd.easykaraoke.cdgdownload
-application/vnd.ecdis-update
-application/vnd.ecowin.chart mag
-application/vnd.ecowin.filerequest
-application/vnd.ecowin.fileupdate
-application/vnd.ecowin.series
-application/vnd.ecowin.seriesrequest
-application/vnd.ecowin.seriesupdate
-# img: application/octet-stream
-application/vnd.efi-img
-# iso: application/octet-stream
-application/vnd.efi-iso
-application/vnd.enliven nml
-application/vnd.enphase.envoy
-application/vnd.eprints.data+xml
-application/vnd.epson.esf esf
-application/vnd.epson.msf msf
-application/vnd.epson.quickanime qam
-application/vnd.epson.salt slt
-application/vnd.epson.ssf ssf
-application/vnd.ericsson.quickcall qcall qca
-application/vnd.espass-espass+zip espass
-application/vnd.eszigno3+xml es3 et3
-application/vnd.etsi.aoc+xml
-application/vnd.etsi.asic-e+zip asice sce
-# scs: application/scvp-cv-response
-application/vnd.etsi.asic-s+zip asics
-application/vnd.etsi.cug+xml
-application/vnd.etsi.iptvcommand+xml
-application/vnd.etsi.iptvdiscovery+xml
-application/vnd.etsi.iptvprofile+xml
-application/vnd.etsi.iptvsad-bc+xml
-application/vnd.etsi.iptvsad-cod+xml
-application/vnd.etsi.iptvsad-npvr+xml
-application/vnd.etsi.iptvservice+xml
-application/vnd.etsi.iptvsync+xml
-application/vnd.etsi.iptvueprofile+xml
-application/vnd.etsi.mcid+xml
-application/vnd.etsi.mheg5
-application/vnd.etsi.overload-control-policy-dataset+xml
-application/vnd.etsi.pstn+xml
-application/vnd.etsi.sci+xml
-application/vnd.etsi.simservs+xml
-application/vnd.etsi.timestamp-token tst
-application/vnd.etsi.tsl.der
-application/vnd.etsi.tsl+xml
-application/vnd.eudora.data
-application/vnd.ezpix-album ez2
-application/vnd.ezpix-package ez3
-application/vnd.f-secure.mobile
-application/vnd.fastcopy-disk-image dim
-application/vnd.fdf fdf
-application/vnd.fdsn.mseed msd mseed
-application/vnd.fdsn.seed seed dataless
-application/vnd.ffsns
-application/vnd.filmit.zfc zfc
-# all extensions: application/vnd.hbci
-application/vnd.fints
-application/vnd.firemonkeys.cloudcell
-application/vnd.FloGraphIt gph
-application/vnd.fluxtime.clip ftc
-application/vnd.font-fontforge-sfd sfd
-application/vnd.framemaker fm
-application/vnd.frogans.fnc fnc
-application/vnd.frogans.ltf ltf
-application/vnd.fsc.weblaunch fsc
-application/vnd.fujitsu.oasys oas
-application/vnd.fujitsu.oasys2 oa2
-application/vnd.fujitsu.oasys3 oa3
-application/vnd.fujitsu.oasysgp fg5
-application/vnd.fujitsu.oasysprs bh2
-application/vnd.fujixerox.ART-EX
-application/vnd.fujixerox.ART4
-application/vnd.fujixerox.ddd ddd
-application/vnd.fujixerox.docuworks xdw
-application/vnd.fujixerox.docuworks.binder xbd
-application/vnd.fujixerox.docuworks.container xct
-application/vnd.fujixerox.HBPL
-application/vnd.fut-misnet
-application/vnd.fuzzysheet fzs
-application/vnd.genomatix.tuxedo txd
-# application/vnd.geo+json obsoleted by application/geo+json
-application/vnd.geocube+xml g3 g³
-application/vnd.geogebra.file ggb
-application/vnd.geogebra.tool ggt
-application/vnd.geometry-explorer gex gre
-application/vnd.geonext gxt
-application/vnd.geoplan g2w
-application/vnd.geospace g3w
-# gbr: application/rpki-ghostbusters
-application/vnd.gerber
-application/vnd.globalplatform.card-content-mgt
-application/vnd.globalplatform.card-content-mgt-response
-application/vnd.gmx gmx
-application/vnd.google-earth.kml+xml kml
-application/vnd.google-earth.kmz kmz
-application/vnd.gov.sk.e-form+xml
-application/vnd.gov.sk.e-form+zip
-application/vnd.gov.sk.xmldatacontainer+xml
-application/vnd.grafeq gqf gqs
-application/vnd.gridmp
-application/vnd.groove-account gac
-application/vnd.groove-help ghf
-application/vnd.groove-identity-message gim
-application/vnd.groove-injector grv
-application/vnd.groove-tool-message gtm
-application/vnd.groove-tool-template tpl
-application/vnd.groove-vcard vcg
-application/vnd.hal+json
-application/vnd.hal+xml hal
-application/vnd.HandHeld-Entertainment+xml zmm
-application/vnd.hbci hbci hbc kom upa pkd bpd
-application/vnd.hc+json
-# rep: application/vnd.businessobjects
-application/vnd.hcl-bireports
-application/vnd.hdt hdt
-application/vnd.heroku+json
-application/vnd.hhe.lesson-player les
-application/vnd.hp-HPGL hpgl
-application/vnd.hp-hpid hpi hpid
-application/vnd.hp-hps hps
-application/vnd.hp-jlyt jlt
-application/vnd.hp-PCL pcl
-application/vnd.hp-PCLXL
-application/vnd.httphone
-application/vnd.hydrostatix.sof-data sfd-hdstx
-application/vnd.hyperdrive+json
-application/vnd.hzn-3d-crossword x3d
-application/vnd.ibm.afplinedata
-application/vnd.ibm.electronic-media emm
-application/vnd.ibm.MiniPay mpy
-application/vnd.ibm.modcap list3820 listafp afp pseg3820
-application/vnd.ibm.rights-management irm
-application/vnd.ibm.secure-container sc
-application/vnd.iccprofile icc icm
-application/vnd.ieee.1905 1905.1
-application/vnd.igloader igl
-application/vnd.imagemeter.folder+zip imf
-application/vnd.imagemeter.image+zip imi
-application/vnd.immervision-ivp ivp
-application/vnd.immervision-ivu ivu
-application/vnd.ims.imsccv1p1 imscc
-application/vnd.ims.imsccv1p2
-application/vnd.ims.imsccv1p3
-application/vnd.ims.lis.v2.result+json
-application/vnd.ims.lti.v2.toolconsumerprofile+json
-application/vnd.ims.lti.v2.toolproxy.id+json
-application/vnd.ims.lti.v2.toolproxy+json
-application/vnd.ims.lti.v2.toolsettings+json
-application/vnd.ims.lti.v2.toolsettings.simple+json
-application/vnd.informedcontrol.rms+xml
-# application/vnd.informix-visionary obsoleted by application/vnd.visionary
-application/vnd.infotech.project
-application/vnd.infotech.project+xml
-application/vnd.innopath.wamp.notification
-application/vnd.insors.igm igm
-application/vnd.intercon.formnet xpw xpx
-application/vnd.intergeo i2g
-application/vnd.intertrust.digibox
-application/vnd.intertrust.nncp
-application/vnd.intu.qbo qbo
-application/vnd.intu.qfx qfx
-application/vnd.iptc.g2.catalogitem+xml
-application/vnd.iptc.g2.conceptitem+xml
-application/vnd.iptc.g2.knowledgeitem+xml
-application/vnd.iptc.g2.newsitem+xml
-application/vnd.iptc.g2.newsmessage+xml
-application/vnd.iptc.g2.packageitem+xml
-application/vnd.iptc.g2.planningitem+xml
-application/vnd.ipunplugged.rcprofile rcprofile
-application/vnd.irepository.package+xml irp
-application/vnd.is-xpr xpr
-application/vnd.isac.fcs fcs
-application/vnd.jam jam
-application/vnd.japannet-directory-service
-application/vnd.japannet-jpnstore-wakeup
-application/vnd.japannet-payment-wakeup
-application/vnd.japannet-registration
-application/vnd.japannet-registration-wakeup
-application/vnd.japannet-setstore-wakeup
-application/vnd.japannet-verification
-application/vnd.japannet-verification-wakeup
-application/vnd.jcp.javame.midlet-rms rms
-application/vnd.jisp jisp
-application/vnd.joost.joda-archive joda
-application/vnd.jsk.isdn-ngn
-application/vnd.kahootz ktz ktr
-application/vnd.kde.karbon karbon
-application/vnd.kde.kchart chrt
-application/vnd.kde.kformula kfo
-application/vnd.kde.kivio flw
-application/vnd.kde.kontour kon
-application/vnd.kde.kpresenter kpr kpt
-application/vnd.kde.kspread ksp
-application/vnd.kde.kword kwd kwt
-application/vnd.kenameaapp htke
-application/vnd.kidspiration kia
-application/vnd.Kinar kne knp sdf
-application/vnd.koan skp skd skm skt
-application/vnd.kodak-descriptor sse
-application/vnd.las.las+json lasjson
-application/vnd.las.las+xml lasxml
-application/vnd.liberty-request+xml
-application/vnd.llamagraphics.life-balance.desktop lbd
-application/vnd.llamagraphics.life-balance.exchange+xml lbe
-application/vnd.lotus-1-2-3 123 wk4 wk3 wk1
-application/vnd.lotus-approach apr vew
-application/vnd.lotus-freelance prz pre
-application/vnd.lotus-notes nsf ntf ndl ns4 ns3 ns2 nsh nsg
-application/vnd.lotus-organizer or3 or2 org
-application/vnd.lotus-screencam scm
-application/vnd.lotus-wordpro lwp sam
-application/vnd.macports.portpkg portpkg
-application/vnd.mapbox-vector-tile mvt
-application/vnd.marlin.drm.actiontoken+xml
-application/vnd.marlin.drm.conftoken+xml
-application/vnd.marlin.drm.license+xml
-application/vnd.marlin.drm.mdcf mdc
-application/vnd.mason+json
-application/vnd.maxmind.maxmind-db mmdb
-application/vnd.mcd mcd
-application/vnd.medcalcdata mc1
-application/vnd.mediastation.cdkey cdkey
-application/vnd.meridian-slingshot
-application/vnd.MFER mwf
-application/vnd.mfmp mfm
-application/vnd.micro+json
-application/vnd.micrografx.flo flo
-application/vnd.micrografx.igx igx
-application/vnd.microsoft.portable-executable
-application/vnd.microsoft.windows.thumbnail-cache
-application/vnd.miele+json
-application/vnd.mif mif
-application/vnd.minisoft-hp3000-save
-application/vnd.mitsubishi.misty-guard.trustweb
-application/vnd.Mobius.DAF daf
-application/vnd.Mobius.DIS dis
-application/vnd.Mobius.MBK mbk
-application/vnd.Mobius.MQY mqy
-application/vnd.Mobius.MSL msl
-application/vnd.Mobius.PLC plc
-application/vnd.Mobius.TXF txf
-application/vnd.mophun.application mpn
-application/vnd.mophun.certificate mpc
-application/vnd.motorola.flexsuite
-application/vnd.motorola.flexsuite.adsi
-application/vnd.motorola.flexsuite.fis
-application/vnd.motorola.flexsuite.gotap
-application/vnd.motorola.flexsuite.kmr
-application/vnd.motorola.flexsuite.ttc
-application/vnd.motorola.flexsuite.wem
-application/vnd.motorola.iprm
-application/vnd.mozilla.xul+xml xul
-application/vnd.ms-3mfdocument 3mf
-application/vnd.ms-artgalry cil
-application/vnd.ms-asf asf
-application/vnd.ms-cab-compressed cab
-application/vnd.ms-excel xls xlm xla xlc xlt xlw
-application/vnd.ms-excel.template.macroEnabled.12 xltm
-application/vnd.ms-excel.addin.macroEnabled.12 xlam
-application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb
-application/vnd.ms-excel.sheet.macroEnabled.12 xlsm
-application/vnd.ms-fontobject eot
-application/vnd.ms-htmlhelp chm
-application/vnd.ms-ims ims
-application/vnd.ms-lrm lrm
-application/vnd.ms-office.activeX+xml
-application/vnd.ms-officetheme thmx
-application/vnd.ms-playready.initiator+xml
-application/vnd.ms-powerpoint ppt pps pot
-application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam
-application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm
-application/vnd.ms-powerpoint.slide.macroEnabled.12 sldm
-application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm
-application/vnd.ms-powerpoint.template.macroEnabled.12 potm
-application/vnd.ms-PrintDeviceCapabilities+xml
-application/vnd.ms-PrintSchemaTicket+xml
-application/vnd.ms-project mpp mpt
-application/vnd.ms-tnef tnef tnf
-application/vnd.ms-windows.devicepairing
-application/vnd.ms-windows.nwprinting.oob
-application/vnd.ms-windows.printerpairing
-application/vnd.ms-windows.wsd.oob
-application/vnd.ms-wmdrm.lic-chlg-req
-application/vnd.ms-wmdrm.lic-resp
-application/vnd.ms-wmdrm.meter-chlg-req
-application/vnd.ms-wmdrm.meter-resp
-application/vnd.ms-word.document.macroEnabled.12 docm
-application/vnd.ms-word.template.macroEnabled.12 dotm
-application/vnd.ms-works wcm wdb wks wps
-application/vnd.ms-wpl wpl
-application/vnd.ms-xpsdocument xps
-application/vnd.msa-disk-image msa
-application/vnd.mseq mseq
-application/vnd.msign
-application/vnd.multiad.creator crtr
-application/vnd.multiad.creator.cif cif
-application/vnd.music-niff
-application/vnd.musician mus
-application/vnd.muvee.style msty
-application/vnd.mynfc taglet
-application/vnd.ncd.control
-application/vnd.ncd.reference
-application/vnd.nearst.inv+json
-application/vnd.nervana entity request bkm kcm
-application/vnd.netfpx
-# ntf: application/vnd.lotus-notes
-application/vnd.nitf nitf
-application/vnd.neurolanguage.nlu nlu
-application/vnd.nintendo.nitro.rom nds
-application/vnd.nintendo.snes.rom sfc smc
-application/vnd.noblenet-directory nnd
-application/vnd.noblenet-sealer nns
-application/vnd.noblenet-web nnw
-application/vnd.nokia.catalogs
-application/vnd.nokia.conml+wbxml
-application/vnd.nokia.conml+xml
-application/vnd.nokia.iptv.config+xml
-application/vnd.nokia.iSDS-radio-presets
-application/vnd.nokia.landmark+wbxml
-application/vnd.nokia.landmark+xml
-application/vnd.nokia.landmarkcollection+xml
-application/vnd.nokia.n-gage.ac+xml ac
-application/vnd.nokia.n-gage.data ngdat
-application/vnd.nokia.n-gage.symbian.install n-gage
-application/vnd.nokia.ncd
-application/vnd.nokia.pcd+wbxml
-application/vnd.nokia.pcd+xml
-application/vnd.nokia.radio-preset rpst
-application/vnd.nokia.radio-presets rpss
-application/vnd.novadigm.EDM edm
-application/vnd.novadigm.EDX edx
-application/vnd.novadigm.EXT ext
-application/vnd.ntt-local.content-share
-application/vnd.ntt-local.file-transfer
-application/vnd.ntt-local.ogw_remote-access
-application/vnd.ntt-local.sip-ta_remote
-application/vnd.ntt-local.sip-ta_tcp_stream
-application/vnd.oasis.opendocument.chart odc
-application/vnd.oasis.opendocument.chart-template otc
-application/vnd.oasis.opendocument.database odb
-application/vnd.oasis.opendocument.formula odf
-# otf: font/otf
-application/vnd.oasis.opendocument.formula-template
-application/vnd.oasis.opendocument.graphics odg
-application/vnd.oasis.opendocument.graphics-template otg
-application/vnd.oasis.opendocument.image odi
-application/vnd.oasis.opendocument.image-template oti
-application/vnd.oasis.opendocument.presentation odp
-application/vnd.oasis.opendocument.presentation-template otp
-application/vnd.oasis.opendocument.spreadsheet ods
-application/vnd.oasis.opendocument.spreadsheet-template ots
-application/vnd.oasis.opendocument.text odt
-application/vnd.oasis.opendocument.text-master odm
-application/vnd.oasis.opendocument.text-template ott
-application/vnd.oasis.opendocument.text-web oth
-application/vnd.obn
-application/vnd.ocf+cbor
-application/vnd.oftn.l10n+json
-application/vnd.oipf.contentaccessdownload+xml
-application/vnd.oipf.contentaccessstreaming+xml
-application/vnd.oipf.cspg-hexbinary
-application/vnd.oipf.dae.svg+xml
-application/vnd.oipf.dae.xhtml+xml
-application/vnd.oipf.mippvcontrolmessage+xml
-application/vnd.oipf.pae.gem
-application/vnd.oipf.spdiscovery+xml
-application/vnd.oipf.spdlist+xml
-application/vnd.oipf.ueprofile+xml
-application/vnd.olpc-sugar xo
-application/vnd.oma.bcast.associated-procedure-parameter+xml
-application/vnd.oma.bcast.drm-trigger+xml
-application/vnd.oma.bcast.imd+xml
-application/vnd.oma.bcast.ltkm
-application/vnd.oma.bcast.notification+xml
-application/vnd.oma.bcast.provisioningtrigger
-application/vnd.oma.bcast.sgboot
-application/vnd.oma.bcast.sgdd+xml
-application/vnd.oma.bcast.sgdu
-application/vnd.oma.bcast.simple-symbol-container
-application/vnd.oma.bcast.smartcard-trigger+xml
-application/vnd.oma.bcast.sprov+xml
-application/vnd.oma.bcast.stkm
-application/vnd.oma.cab-address-book+xml
-application/vnd.oma.cab-feature-handler+xml
-application/vnd.oma.cab-pcc+xml
-application/vnd.oma.cab-subs-invite+xml
-application/vnd.oma.cab-user-prefs+xml
-application/vnd.oma.dcd
-application/vnd.oma.dcdc
-application/vnd.oma.dd2+xml dd2
-application/vnd.oma.drm.risd+xml
-application/vnd.oma.group-usage-list+xml
-application/vnd.oma.lwm2m+json
-application/vnd.oma.lwm2m+tlv
-application/vnd.oma.pal+xml
-application/vnd.oma.poc.detailed-progress-report+xml
-application/vnd.oma.poc.final-report+xml
-application/vnd.oma.poc.groups+xml
-application/vnd.oma.poc.invocation-descriptor+xml
-application/vnd.oma.poc.optimized-progress-report+xml
-application/vnd.oma.push
-application/vnd.oma.scidm.messages+xml
-application/vnd.oma.xcap-directory+xml
-application/vnd.oma-scws-config
-application/vnd.oma-scws-http-request
-application/vnd.oma-scws-http-response
-application/vnd.omads-email+xml
-application/vnd.omads-file+xml
-application/vnd.omads-folder+xml
-application/vnd.omaloc-supl-init
-application/vnd.onepager tam
-application/vnd.onepagertamp tamp
-application/vnd.onepagertamx tamx
-application/vnd.onepagertat tat
-application/vnd.onepagertatp tatp
-application/vnd.onepagertatx tatx
-application/vnd.openblox.game+xml obgx
-application/vnd.openblox.game-binary obg
-application/vnd.openeye.oeb oeb
-application/vnd.openofficeorg.extension oxt
-application/vnd.openstreetmap.data+xml osm
-application/vnd.openxmlformats-officedocument.custom-properties+xml
-application/vnd.openxmlformats-officedocument.customXmlProperties+xml
-application/vnd.openxmlformats-officedocument.drawing+xml
-application/vnd.openxmlformats-officedocument.drawingml.chart+xml
-application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml
-application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml
-application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml
-application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml
-application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml
-application/vnd.openxmlformats-officedocument.extended-properties+xml
-application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml
-application/vnd.openxmlformats-officedocument.presentationml.comments+xml
-application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml
-application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml
-application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml
-application/vnd.openxmlformats-officedocument.presentationml.presProps+xml
-application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
-application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml
-application/vnd.openxmlformats-officedocument.presentationml.slide sldx
-application/vnd.openxmlformats-officedocument.presentationml.slide+xml
-application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml
-application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml
-application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml
-application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
-application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml
-application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml
-application/vnd.openxmlformats-officedocument.presentationml.tags+xml
-application/vnd.openxmlformats-officedocument.presentationml.template potx
-application/vnd.openxmlformats-officedocument.presentationml.template.main+xml
-application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
-application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
-application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml
-application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml
-application/vnd.openxmlformats-officedocument.theme+xml
-application/vnd.openxmlformats-officedocument.themeOverride+xml
-application/vnd.openxmlformats-officedocument.vmlDrawing
-application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
-application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
-application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml
-application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml
-application/vnd.openxmlformats-package.core-properties+xml
-application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml
-application/vnd.openxmlformats-package.relationships+xml
-application/vnd.oracle.resource+json
-application/vnd.orange.indata
-application/vnd.osa.netdeploy ndc
-application/vnd.osgeo.mapguide.package mgp
-# jar: application/x-java-archive
-application/vnd.osgi.bundle
-application/vnd.osgi.dp dp
-application/vnd.osgi.subsystem esa
-application/vnd.otps.ct-kip+xml
-application/vnd.oxli.countgraph oxlicg
-application/vnd.pagerduty+json
-application/vnd.palm prc pdb pqa oprc
-application/vnd.panoply plp
-application/vnd.paos+xml
-application/vnd.pawaafile paw
-application/vnd.pcos
-application/vnd.pg.format str
-application/vnd.pg.osasli ei6
-application/vnd.piaccess.application-license pil
-application/vnd.picsel efif
-application/vnd.pmi.widget wg
-application/vnd.poc.group-advertisement+xml
-application/vnd.pocketlearn plf
-application/vnd.powerbuilder6 pbd
-application/vnd.powerbuilder6-s
-application/vnd.powerbuilder7
-application/vnd.powerbuilder7-s
-application/vnd.powerbuilder75
-application/vnd.powerbuilder75-s
-application/vnd.preminet preminet
-application/vnd.previewsystems.box box vbox
-application/vnd.proteus.magazine mgz
-application/vnd.publishare-delta-tree qps
-# pti: image/prs.pti
-application/vnd.pvi.ptid1 ptid
-application/vnd.pwg-multiplexed
-application/vnd.pwg-xhtml-print+xml
-application/vnd.qualcomm.brew-app-res bar
-application/vnd.quarantainenet
-application/vnd.Quark.QuarkXPress qxd qxt qwd qwt qxl qxb
-application/vnd.quobject-quoxdocument quox quiz
-application/vnd.radisys.moml+xml
-application/vnd.radisys.msml-audit-conf+xml
-application/vnd.radisys.msml-audit-conn+xml
-application/vnd.radisys.msml-audit-dialog+xml
-application/vnd.radisys.msml-audit-stream+xml
-application/vnd.radisys.msml-audit+xml
-application/vnd.radisys.msml-conf+xml
-application/vnd.radisys.msml-dialog-base+xml
-application/vnd.radisys.msml-dialog-fax-detect+xml
-application/vnd.radisys.msml-dialog-fax-sendrecv+xml
-application/vnd.radisys.msml-dialog-group+xml
-application/vnd.radisys.msml-dialog-speech+xml
-application/vnd.radisys.msml-dialog-transform+xml
-application/vnd.radisys.msml-dialog+xml
-application/vnd.radisys.msml+xml
-application/vnd.rainstor.data tree
-application/vnd.rapid
-application/vnd.rar rar
-application/vnd.realvnc.bed bed
-application/vnd.recordare.musicxml mxl
-application/vnd.recordare.musicxml+xml
-application/vnd.RenLearn.rlprint
-application/vnd.rig.cryptonote cryptonote
-application/vnd.route66.link66+xml link66
-# gbr: application/rpki-ghostbusters
-application/vnd.rs-274x
-application/vnd.ruckus.download
-application/vnd.s3sms
-application/vnd.sailingtracker.track st
-application/vnd.sbm.cid
-application/vnd.sbm.mid2
-application/vnd.scribus scd sla slaz
-application/vnd.sealed.3df s3df
-application/vnd.sealed.csf scsf
-application/vnd.sealed.doc sdoc sdo s1w
-application/vnd.sealed.eml seml sem
-application/vnd.sealed.mht smht smh
-application/vnd.sealed.net
-# spp: application/scvp-vp-response
-application/vnd.sealed.ppt sppt s1p
-application/vnd.sealed.tiff stif
-application/vnd.sealed.xls sxls sxl s1e
-# stm: audio/x-stm
-application/vnd.sealedmedia.softseal.html stml s1h
-application/vnd.sealedmedia.softseal.pdf spdf spd s1a
-application/vnd.seemail see
-application/vnd.sema sema
-application/vnd.semd semd
-application/vnd.semf semf
-application/vnd.shana.informed.formdata ifm
-application/vnd.shana.informed.formtemplate itp
-application/vnd.shana.informed.interchange iif
-application/vnd.shana.informed.package ipk
-application/vnd.SimTech-MindMapper twd twds
-application/vnd.siren+json
-application/vnd.smaf mmf
-application/vnd.smart.notebook notebook
-application/vnd.smart.teacher teacher
-application/vnd.software602.filler.form+xml fo
-application/vnd.software602.filler.form-xml-zip zfo
-application/vnd.solent.sdkm+xml sdkm sdkd
-application/vnd.spotfire.dxp dxp
-application/vnd.spotfire.sfs sfs
-application/vnd.sss-cod
-application/vnd.sss-dtf
-application/vnd.sss-ntf
-application/vnd.stepmania.package smzip
-application/vnd.stepmania.stepchart sm
-application/vnd.street-stream
-application/vnd.sun.wadl+xml wadl
-application/vnd.sus-calendar sus susp
-application/vnd.svd
-application/vnd.swiftview-ics
-application/vnd.syncml+xml xsm
-application/vnd.syncml.dm+wbxml bdm
-application/vnd.syncml.dm+xml xdm
-application/vnd.syncml.dm.notification
-application/vnd.syncml.dmddf+wbxml
-application/vnd.syncml.dmddf+xml ddf
-application/vnd.syncml.dmtnds+wbxml
-application/vnd.syncml.dmtnds+xml
-application/vnd.syncml.ds.notification
-application/vnd.tableschema+json
-application/vnd.tao.intent-module-archive tao
-application/vnd.tcpdump.pcap pcap cap dmp
-application/vnd.theqvd qvd
-application/vnd.tmd.mediaflex.api+xml
-application/vnd.tml vfr viaframe
-application/vnd.tmobile-livetv tmo
-application/vnd.tri.onesource
-application/vnd.trid.tpt tpt
-application/vnd.triscape.mxs mxs
-application/vnd.trueapp tra
-application/vnd.truedoc
-# cab: application/vnd.ms-cab-compressed
-application/vnd.ubisoft.webplayer
-application/vnd.ufdl ufdl ufd frm
-application/vnd.uiq.theme utz
-application/vnd.umajin umj
-application/vnd.unity unityweb
-application/vnd.uoml+xml uoml uo
-application/vnd.uplanet.alert
-application/vnd.uplanet.alert-wbxml
-application/vnd.uplanet.bearer-choice
-application/vnd.uplanet.bearer-choice-wbxml
-application/vnd.uplanet.cacheop
-application/vnd.uplanet.cacheop-wbxml
-application/vnd.uplanet.channel
-application/vnd.uplanet.channel-wbxml
-application/vnd.uplanet.list
-application/vnd.uplanet.list-wbxml
-application/vnd.uplanet.listcmd
-application/vnd.uplanet.listcmd-wbxml
-application/vnd.uplanet.signal
-application/vnd.uri-map urim urimap
-application/vnd.valve.source.material vmt
-application/vnd.vcx vcx
-# sxi: application/vnd.sun.xml.impress
-application/vnd.vd-study mxi study-inter model-inter
-# mcd: application/vnd.mcd
-application/vnd.vectorworks vwx
-application/vnd.vel+json
-application/vnd.verimatrix.vcas
-application/vnd.vidsoft.vidconference vsc
-application/vnd.visio vsd vst vsw vss
-application/vnd.visionary vis
-# vsc: application/vnd.vidsoft.vidconference
-application/vnd.vividence.scriptfile
-application/vnd.vsf vsf
-application/vnd.wap.sic sic
-application/vnd.wap.slc slc
-application/vnd.wap.wbxml wbxml
-application/vnd.wap.wmlc wmlc
-application/vnd.wap.wmlscriptc wmlsc
-application/vnd.webturbo wtb
-application/vnd.wfa.p2p p2p
-application/vnd.wfa.wsc wsc
-application/vnd.windows.devicepairing
-application/vnd.wmc wmc
-application/vnd.wmf.bootstrap
-# nb: application/mathematica for now
-application/vnd.wolfram.mathematica
-application/vnd.wolfram.mathematica.package m
-application/vnd.wolfram.player nbp
-application/vnd.wordperfect wpd
-application/vnd.wqd wqd
-application/vnd.wrq-hp3000-labelled
-application/vnd.wt.stf stf
-application/vnd.wv.csp+xml
-application/vnd.wv.csp+wbxml wv
-application/vnd.wv.ssp+xml
-application/vnd.xacml+json
-application/vnd.xara xar
-application/vnd.xfdl xfdl xfd
-application/vnd.xfdl.webform
-application/vnd.xmi+xml
-application/vnd.xmpie.cpkg cpkg
-application/vnd.xmpie.dpkg dpkg
-# dpkg: application/vnd.xmpie.dpkg
-application/vnd.xmpie.plan
-application/vnd.xmpie.ppkg ppkg
-application/vnd.xmpie.xlim xlim
-application/vnd.yamaha.hv-dic hvd
-application/vnd.yamaha.hv-script hvs
-application/vnd.yamaha.hv-voice hvp
-application/vnd.yamaha.openscoreformat osf
-application/vnd.yamaha.openscoreformat.osfpvg+xml
-application/vnd.yamaha.remote-setup
-application/vnd.yamaha.smaf-audio saf
-application/vnd.yamaha.smaf-phrase spf
-application/vnd.yamaha.through-ngn
-application/vnd.yamaha.tunnel-udpencap
-application/vnd.yaoweme yme
-application/vnd.yellowriver-custom-menu cmp
-application/vnd.zul zir zirz
-application/vnd.zzazz.deck+xml zaz
-application/voicexml+xml vxml
-application/vq-rtcp-xr
-application/watcherinfo+xml wif
-application/whoispp-query
-application/whoispp-response
-application/widget wgt
-application/wita
-application/wordperfect5.1
-application/wsdl+xml wsdl
-application/wspolicy+xml wspolicy
-# yes, this *is* IANA registered despite of x-
-application/x-www-form-urlencoded
-application/x400-bp
-application/xacml+xml
-application/xcap-att+xml xav
-application/xcap-caps+xml xca
-application/xcap-diff+xml xdf
-application/xcap-el+xml xel
-application/xcap-error+xml xer
-application/xcap-ns+xml xns
-application/xcon-conference-info-diff+xml
-application/xcon-conference-info+xml
-application/xenc+xml
-application/xhtml+xml xhtml xhtm xht
-# xml, xsd, rng: text/xml
-application/xml
-# mod: audio/x-mod
-application/xml-dtd dtd
-# ent: text/xml-external-parsed-entity
-application/xml-external-parsed-entity
-application/xml-patch+xml
-application/xmpp+xml
-application/xop+xml xop
-application/xslt+xml xsl xslt
-application/xv+xml mxml xhvml xvml xvm
-application/yang yang
-application/yang-data+json
-application/yang-data+xml
-application/yang-patch+json
-application/yang-patch+xml
-application/yin+xml yin
-application/zip zip
-application/zlib
-audio/1d-interleaved-parityfec
-audio/32kadpcm 726
-# 3gp, 3gpp: video/3gpp
-audio/3gpp
-# 3g2, 3gpp2: video/3gpp2
-audio/3gpp2
-audio/ac3 ac3
-audio/AMR amr
-audio/AMR-WB awb
-audio/amr-wb+
-audio/aptx
-audio/asc acn
-# aa3, omg: audio/ATRAC3
-audio/ATRAC-ADVANCED-LOSSLESS aal
-# aa3, omg: audio/ATRAC3
-audio/ATRAC-X atx
-audio/ATRAC3 at3 aa3 omg
-audio/basic au snd
-audio/BV16
-audio/BV32
-audio/clearmode
-audio/CN
-audio/DAT12
-audio/dls dls
-audio/dsr-es201108
-audio/dsr-es202050
-audio/dsr-es202211
-audio/dsr-es202212
-audio/DV
-audio/DVI4
-audio/eac3
-audio/encaprtp
-audio/EVRC evc
-# qcp: audio/qcelp
-audio/EVRC-QCP
-audio/EVRC0
-audio/EVRC1
-audio/EVRCB evb
-audio/EVRCB0
-audio/EVRCB1
-audio/EVRCNW enw
-audio/EVRCNW0
-audio/EVRCNW1
-audio/EVRCWB evw
-audio/EVRCWB0
-audio/EVRCWB1
-audio/EVS
-audio/example
-audio/fwdred
-audio/G711-0
-audio/G719
-audio/G722
-audio/G7221
-audio/G723
-audio/G726-16
-audio/G726-24
-audio/G726-32
-audio/G726-40
-audio/G728
-audio/G729
-audio/G7291
-audio/G729D
-audio/G729E
-audio/GSM
-audio/GSM-EFR
-audio/GSM-HR-08
-audio/iLBC lbc
-audio/ip-mr_v2.5
-# wav: audio/x-wav
-audio/L16 l16
-audio/L20
-audio/L24
-audio/L8
-audio/LPC
-audio/MELP
-audio/MELP600
-audio/MELP1200
-audio/MELP2400
-audio/mobile-xmf mxmf
-# mp4, mpg4: video/mp4, see RFC 4337
-audio/mp4 m4a
-audio/MP4A-LATM
-audio/MPA
-audio/mpa-robust
-audio/mpeg mp3 mpga mp1 mp2
-audio/mpeg4-generic
-audio/ogg oga ogg opus spx
-audio/opus
-audio/parityfec
-audio/PCMA
-audio/PCMA-WB
-audio/PCMU
-audio/PCMU-WB
-audio/prs.sid sid psid
-audio/qcelp qcp
-audio/raptorfec
-audio/RED
-audio/rtp-enc-aescm128
-audio/rtp-midi
-audio/rtploopback
-audio/rtx
-audio/SMV smv
-# qcp: audio/qcelp, see RFC 3625
-audio/SMV-QCP
-audio/SMV0
-# mid: audio/midi
-audio/sp-midi
-audio/speex
-audio/t140c
-audio/t38
-audio/telephone-event
-audio/tone
-audio/UEMCLIP
-audio/ulpfec
-audio/VDVI
-audio/VMR-WB
-audio/vnd.3gpp.iufp
-audio/vnd.4SB
-audio/vnd.audikoz koz
-audio/vnd.CELP
-audio/vnd.cisco.nse
-audio/vnd.cmles.radio-events
-audio/vnd.cns.anp1
-audio/vnd.cns.inf1
-audio/vnd.dece.audio uva uvva
-audio/vnd.digital-winds eol
-audio/vnd.dlna.adts
-audio/vnd.dolby.heaac.1
-audio/vnd.dolby.heaac.2
-audio/vnd.dolby.mlp mlp
-audio/vnd.dolby.mps
-audio/vnd.dolby.pl2
-audio/vnd.dolby.pl2x
-audio/vnd.dolby.pl2z
-audio/vnd.dolby.pulse.1
-audio/vnd.dra
-# wav: audio/x-wav, cpt: application/mac-compactpro
-audio/vnd.dts dts
-audio/vnd.dts.hd dtshd
-# dvb: video/vnd.dvb.file
-audio/vnd.dvb.file
-audio/vnd.everad.plj plj
-# rm: audio/x-pn-realaudio
-audio/vnd.hns.audio
-audio/vnd.lucent.voice lvp
-audio/vnd.ms-playready.media.pya pya
-# mxmf: audio/mobile-xmf
-audio/vnd.nokia.mobile-xmf
-audio/vnd.nortel.vbk vbk
-audio/vnd.nuera.ecelp4800 ecelp4800
-audio/vnd.nuera.ecelp7470 ecelp7470
-audio/vnd.nuera.ecelp9600 ecelp9600
-audio/vnd.octel.sbc
-# audio/vnd.qcelp deprecated in favour of audio/qcelp
-audio/vnd.rhetorex.32kadpcm
-audio/vnd.rip rip
-audio/vnd.sealedmedia.softseal.mpeg smp3 smp s1m
-audio/vnd.vmx.cvsd
-audio/vorbis
-audio/vorbis-config
-font/collection ttc
-font/otf otf
-font/sfnt
-font/ttf ttf
-font/woff woff
-font/woff2 woff2
-image/bmp bmp dib
-image/cgm cgm
-image/dicom-rle drle
-image/emf emf
-image/example
-image/fits fits fit fts
-image/g3fax
-image/gif gif
-image/ief ief
-image/jls jls
-image/jp2 jp2 jpg2
-image/jpeg jpg jpeg jpe jfif
-image/jpm jpm jpgm
-image/jpx jpx jpf
-image/ktx ktx
-image/naplps
-image/png png
-image/prs.btif btif btf
-image/prs.pti pti
-image/pwg-raster
-image/svg+xml svg svgz
-image/t38 t38
-image/tiff tiff tif
-image/tiff-fx tfx
-image/vnd.adobe.photoshop psd
-image/vnd.airzip.accelerator.azv azv
-image/vnd.cns.inf2
-image/vnd.dece.graphic uvi uvvi uvg uvvg
-image/vnd.djvu djvu djv
-# sub: text/vnd.dvb.subtitle
-image/vnd.dvb.subtitle
-image/vnd.dwg dwg
-image/vnd.dxf dxf
-image/vnd.fastbidsheet fbs
-image/vnd.fpx fpx
-image/vnd.fst fst
-image/vnd.fujixerox.edmics-mmr mmr
-image/vnd.fujixerox.edmics-rlc rlc
-image/vnd.globalgraphics.pgb pgb
-image/vnd.microsoft.icon ico
-image/vnd.mix
-image/vnd.mozilla.apng apng
-image/vnd.ms-modi mdi
-image/vnd.net-fpx
-image/vnd.radiance hdr rgbe xyze
-image/vnd.sealed.png spng spn s1n
-image/vnd.sealedmedia.softseal.gif sgif sgi s1g
-image/vnd.sealedmedia.softseal.jpg sjpg sjp s1j
-image/vnd.svf
-image/vnd.tencent.tap tap
-image/vnd.valve.source.texture vtf
-image/vnd.wap.wbmp wbmp
-image/vnd.xiff xif
-image/vnd.zbrush.pcx pcx
-image/wmf wmf
-message/CPIM
-message/delivery-status
-message/disposition-notification
-message/example
-message/external-body
-message/feedback-report
-message/global u8msg
-message/global-delivery-status u8dsn
-message/global-disposition-notification u8mdn
-message/global-headers u8hdr
-message/http
-# cl: application/simple-filter+xml
-message/imdn+xml
-# message/news obsoleted by message/rfc822
-message/partial
-message/rfc822 eml mail art
-message/s-http
-message/sip
-message/sipfrag
-message/tracking-status
-message/vnd.si.simp
-# wsc: application/vnd.wfa.wsc
-message/vnd.wfa.wsc
-model/example
-model/gltf+json gltf
-model/iges igs iges
-model/mesh msh mesh silo
-model/vnd.collada+xml dae
-model/vnd.dwf dwf
-# 3dml, 3dm: text/vnd.in3d.3dml
-model/vnd.flatland.3dml
-model/vnd.gdl gdl gsm win dor lmp rsm msm ism
-model/vnd.gs-gdl
-model/vnd.gtw gtw
-model/vnd.moml+xml moml
-model/vnd.mts mts
-model/vnd.opengex ogex
-model/vnd.parasolid.transmit.binary x_b xmt_bin
-model/vnd.parasolid.transmit.text x_t xmt_txt
-model/vnd.rosette.annotated-data-model
-model/vnd.valve.source.compiled-map bsp
-model/vnd.vtu vtu
-model/vrml wrl vrml
-# x3db: model/x3d+xml
-model/x3d+fastinfoset
-# x3d: application/vnd.hzn-3d-crossword
-model/x3d+xml x3db
-model/x3d-vrml x3dv x3dvz
-multipart/alternative
-multipart/appledouble
-multipart/byteranges
-multipart/digest
-multipart/encrypted
-multipart/form-data
-multipart/header-set
-multipart/mixed
-multipart/parallel
-multipart/related
-multipart/report
-multipart/signed
-multipart/vnd.bint.med-plus bmed
-multipart/voice-message vpm
-multipart/x-mixed-replace
-text/1d-interleaved-parityfec
-text/cache-manifest appcache manifest
-text/calendar ics ifb
-text/css css
-text/csv csv
-text/csv-schema csvs
-text/directory
-text/dns soa zone
-text/encaprtp
-# text/ecmascript obsoleted by application/ecmascript
-text/enriched
-text/example
-text/fwdred
-text/grammar-ref-list
-text/html html htm
-# text/javascript obsoleted by application/javascript
-text/jcr-cnd cnd
-text/markdown markdown md
-text/mizar miz
-text/n3 n3
-text/parameters
-text/parityfec
-text/plain txt asc text pm el c h cc hh cxx hxx f90 conf log
-text/provenance-notation provn
-text/prs.fallenstein.rst rst
-text/prs.lines.tag tag dsc
-text/prs.prop.logic
-text/raptorfec
-text/RED
-text/rfc822-headers
-text/richtext rtx
-# rtf: application/rtf
-text/rtf
-text/rtp-enc-aescm128
-text/rtploopback
-text/rtx
-text/sgml sgml sgm
-text/strings
-text/t140
-text/tab-separated-values tsv
-text/troff t tr roff
-text/turtle ttl
-text/ulpfec
-text/uri-list uris uri
-text/vcard vcf vcard
-text/vnd.a a
-text/vnd.abc abc
-text/vnd.ascii-art ascii
-# curl: application/vnd.curl
-text/vnd.curl
-text/vnd.debian.copyright copyright
-text/vnd.DMClientScript dms
-text/vnd.dvb.subtitle sub
-text/vnd.esmertec.theme-descriptor jtd
-text/vnd.fly fly
-text/vnd.fmi.flexstor flx
-text/vnd.graphviz gv dot
-text/vnd.in3d.3dml 3dml 3dm
-text/vnd.in3d.spot spot spo
-text/vnd.IPTC.NewsML
-text/vnd.IPTC.NITF
-text/vnd.latex-z
-text/vnd.motorola.reflex
-text/vnd.ms-mediapackage mpf
-text/vnd.net2phone.commcenter.command ccc
-text/vnd.radisys.msml-basic-layout
-text/vnd.si.uricatalogue uric
-text/vnd.sun.j2me.app-descriptor jad
-text/vnd.trolltech.linguist ts
-text/vnd.wap.si si
-text/vnd.wap.sl sl
-text/vnd.wap.wml wml
-text/vnd.wap.wmlscript wmls
-text/xml xml xsd rng
-text/xml-external-parsed-entity ent
-video/1d-interleaved-parityfec
-video/3gpp 3gp 3gpp
-video/3gpp2 3g2 3gpp2
-video/3gpp-tt
-video/BMPEG
-video/BT656
-video/CelB
-video/DV
-video/encaprtp
-video/example
-video/H261
-video/H263
-video/H263-1998
-video/H263-2000
-video/H264
-video/H264-RCDO
-video/H264-SVC
-video/H265
-video/iso.segment m4s
-video/JPEG
-video/jpeg2000
-video/mj2 mj2 mjp2
-video/MP1S
-video/MP2P
-video/MP2T
-video/mp4 mp4 mpg4 m4v
-video/MP4V-ES
-video/mpeg mpeg mpg mpe m1v m2v
-video/mpeg4-generic
-video/MPV
-video/nv
-video/ogg ogv
-video/parityfec
-video/pointer
-video/quicktime mov qt
-video/raptorfec
-video/raw
-video/rtp-enc-aescm128
-video/rtploopback
-video/rtx
-video/SMPTE292M
-video/ulpfec
-video/vc1
-video/vnd.CCTV
-video/vnd.dece.hd uvh uvvh
-video/vnd.dece.mobile uvm uvvm
-video/vnd.dece.mp4 uvu uvvu
-video/vnd.dece.pd uvp uvvp
-video/vnd.dece.sd uvs uvvs
-video/vnd.dece.video uvv uvvv
-video/vnd.directv.mpeg
-video/vnd.directv.mpeg-tts
-video/vnd.dlna.mpeg-tts
-video/vnd.dvb.file dvb
-video/vnd.fvt fvt
-# rm: audio/x-pn-realaudio
-video/vnd.hns.video
-video/vnd.iptvforum.1dparityfec-1010
-video/vnd.iptvforum.1dparityfec-2005
-video/vnd.iptvforum.2dparityfec-1010
-video/vnd.iptvforum.2dparityfec-2005
-video/vnd.iptvforum.ttsavc
-video/vnd.iptvforum.ttsmpeg2
-video/vnd.motorola.video
-video/vnd.motorola.videop
-video/vnd.mpegurl mxu m4u
-video/vnd.ms-playready.media.pyv pyv
-video/vnd.nokia.interleaved-multimedia nim
-video/vnd.nokia.videovoip
-# mp4: video/mp4
-video/vnd.objectvideo
-video/vnd.radgamettools.bink bik bk2
-video/vnd.radgamettools.smacker smk
-video/vnd.sealed.mpeg1 smpg s11
-# smpg: video/vnd.sealed.mpeg1
-video/vnd.sealed.mpeg4 s14
-video/vnd.sealed.swf sswf ssw
-video/vnd.sealedmedia.softseal.mov smov smo s1q
-# uvu, uvvu: video/vnd.dece.mp4
-video/vnd.uvvu.mp4
-video/vnd.vivo viv
-video/VP8
-
-# Non-IANA types
-
-application/mac-compactpro cpt
-application/metalink+xml metalink
-application/owl+xml owx
-application/rss+xml rss
-application/vnd.android.package-archive apk
-application/vnd.oma.dd+xml dd
-application/vnd.oma.drm.content dcf
-# odf: application/vnd.oasis.opendocument.formula
-application/vnd.oma.drm.dcf o4a o4v
-application/vnd.oma.drm.message dm
-application/vnd.oma.drm.rights+wbxml drc
-application/vnd.oma.drm.rights+xml dr
-application/vnd.sun.xml.calc sxc
-application/vnd.sun.xml.calc.template stc
-application/vnd.sun.xml.draw sxd
-application/vnd.sun.xml.draw.template std
-application/vnd.sun.xml.impress sxi
-application/vnd.sun.xml.impress.template sti
-application/vnd.sun.xml.math sxm
-application/vnd.sun.xml.writer sxw
-application/vnd.sun.xml.writer.global sxg
-application/vnd.sun.xml.writer.template stw
-application/vnd.symbian.install sis
-application/vnd.wap.mms-message mms
-application/x-annodex anx
-application/x-bcpio bcpio
-application/x-bittorrent torrent
-application/x-bzip2 bz2
-application/x-cdlink vcd
-application/x-chrome-extension crx
-application/x-cpio cpio
-application/x-csh csh
-application/x-director dcr dir dxr
-application/x-dvi dvi
-application/x-futuresplash spl
-application/x-gtar gtar
-application/x-hdf hdf
-application/x-java-archive jar
-application/x-java-jnlp-file jnlp
-application/x-java-pack200 pack
-application/x-killustrator kil
-application/x-latex latex
-application/x-netcdf nc cdf
-application/x-perl pl
-application/x-rpm rpm
-application/x-sh sh
-application/x-shar shar
-application/x-stuffit sit
-application/x-sv4cpio sv4cpio
-application/x-sv4crc sv4crc
-application/x-tar tar
-application/x-tcl tcl
-application/x-tex tex
-application/x-texinfo texinfo texi
-application/x-troff-man man 1 2 3 4 5 6 7 8
-application/x-troff-me me
-application/x-troff-ms ms
-application/x-ustar ustar
-application/x-wais-source src
-application/x-xpinstall xpi
-application/x-xspf+xml xspf
-application/x-xz xz
-audio/midi mid midi kar
-audio/x-aiff aif aiff aifc
-audio/x-annodex axa
-audio/x-flac flac
-audio/x-matroska mka
-audio/x-mod mod ult uni m15 mtm 669 med
-audio/x-mpegurl m3u
-audio/x-ms-wax wax
-audio/x-ms-wma wma
-audio/x-pn-realaudio ram rm
-audio/x-realaudio ra
-audio/x-s3m s3m
-audio/x-stm stm
-audio/x-wav wav
-chemical/x-xyz xyz
-image/webp webp
-image/x-cmu-raster ras
-image/x-portable-anymap pnm
-image/x-portable-bitmap pbm
-image/x-portable-graymap pgm
-image/x-portable-pixmap ppm
-image/x-rgb rgb
-image/x-targa tga
-image/x-xbitmap xbm
-image/x-xpixmap xpm
-image/x-xwindowdump xwd
-text/html-sandboxed sandboxed
-text/x-pod pod
-text/x-setext etx
-video/webm webm
-video/x-annodex axv
-video/x-flv flv
-video/x-javafx fxm
-video/x-matroska mkv
-video/x-matroska-3d mk3d
-video/x-ms-asf asx
-video/x-ms-wm wm
-video/x-ms-wmv wmv
-video/x-ms-wmx wmx
-video/x-ms-wvx wvx
-video/x-msvideo avi
-video/x-sgi-movie movie
-x-conference/x-cooltalk ice
-x-epoc/x-sisx-app sisx
diff --git a/cmd/swarm/run_test.go b/cmd/swarm/run_test.go
deleted file mode 100644
index b68a1e897..000000000
--- a/cmd/swarm/run_test.go
+++ /dev/null
@@ -1,502 +0,0 @@
-// Copyright 2017 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 (
- "context"
- "crypto/ecdsa"
- "flag"
- "fmt"
- "io/ioutil"
- "net"
- "os"
- "path"
- "path/filepath"
- "runtime"
- "sync"
- "syscall"
- "testing"
- "time"
-
- "github.com/docker/docker/pkg/reexec"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/accounts/keystore"
- "github.com/ethereum/go-ethereum/internal/cmdtest"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm"
- "github.com/ethereum/go-ethereum/swarm/api"
- swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
-)
-
-var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
-
-func init() {
- // Run the app if we've been exec'd as "swarm-test" in runSwarm.
- reexec.Register("swarm-test", func() {
- if err := app.Run(os.Args); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- os.Exit(0)
- })
-}
-
-const clusterSize = 3
-
-func serverFunc(api *api.API) swarmhttp.TestServer {
- return swarmhttp.NewServer(api, "")
-}
-func TestMain(m *testing.M) {
- // check if we have been reexec'd
- if reexec.Init() {
- return
- }
- os.Exit(m.Run())
-}
-
-func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
- tt := cmdtest.NewTestCmd(t, nil)
-
- found := false
- for _, v := range args {
- if v == "--bootnodes" {
- found = true
- break
- }
- }
-
- if !found {
- args = append([]string{"--bootnodes", ""}, args...)
- }
-
- // Boot "swarm". This actually runs the test binary but the TestMain
- // function will prevent any tests from running.
- tt.Run("swarm-test", args...)
-
- return tt
-}
-
-type testCluster struct {
- Nodes []*testNode
- TmpDir string
-}
-
-// newTestCluster starts a test swarm cluster of the given size.
-//
-// A temporary directory is created and each node gets a data directory inside
-// it.
-//
-// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
-// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
-// as flags).
-//
-// When starting more than one node, they are connected together using the
-// admin SetPeer RPC method.
-
-func newTestCluster(t *testing.T, size int) *testCluster {
- cluster := &testCluster{}
- defer func() {
- if t.Failed() {
- cluster.Shutdown()
- }
- }()
-
- tmpdir, err := ioutil.TempDir("", "swarm-test")
- if err != nil {
- t.Fatal(err)
- }
- cluster.TmpDir = tmpdir
-
- // start the nodes
- cluster.StartNewNodes(t, size)
-
- if size == 1 {
- return cluster
- }
-
- // connect the nodes together
- for _, node := range cluster.Nodes {
- if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
- t.Fatal(err)
- }
- }
-
- // wait until all nodes have the correct number of peers
-outer:
- for _, node := range cluster.Nodes {
- var peers []*p2p.PeerInfo
- for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
- if err := node.Client.Call(&peers, "admin_peers"); err != nil {
- t.Fatal(err)
- }
- if len(peers) == len(cluster.Nodes)-1 {
- continue outer
- }
- }
- t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
- }
-
- return cluster
-}
-
-func (c *testCluster) Shutdown() {
- c.Stop()
- c.Cleanup()
-}
-
-func (c *testCluster) Stop() {
- for _, node := range c.Nodes {
- node.Shutdown()
- }
-}
-
-func (c *testCluster) StartNewNodes(t *testing.T, size int) {
- c.Nodes = make([]*testNode, 0, size)
-
- errors := make(chan error, size)
- nodes := make(chan *testNode, size)
- for i := 0; i < size; i++ {
- go func(nodeIndex int) {
- dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", nodeIndex))
- if err := os.Mkdir(dir, 0700); err != nil {
- errors <- err
- return
- }
-
- node := newTestNode(t, dir)
- node.Name = fmt.Sprintf("swarm%02d", nodeIndex)
- nodes <- node
- }(i)
- }
-
- for i := 0; i < size; i++ {
- select {
- case node := <-nodes:
- c.Nodes = append(c.Nodes, node)
- case err := <-errors:
- t.Error(err)
- }
- }
-
- if t.Failed() {
- c.Shutdown()
- t.FailNow()
- }
-}
-
-func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) {
- c.Nodes = make([]*testNode, 0, size)
- for i := 0; i < size; i++ {
- dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
- node := existingTestNode(t, dir, bzzaccount)
- node.Name = fmt.Sprintf("swarm%02d", i)
-
- c.Nodes = append(c.Nodes, node)
- }
-}
-
-func (c *testCluster) Cleanup() {
- os.RemoveAll(c.TmpDir)
-}
-
-type testNode struct {
- Name string
- Addr string
- URL string
- Enode string
- Dir string
- IpcPath string
- PrivateKey *ecdsa.PrivateKey
- Client *rpc.Client
- Cmd *cmdtest.TestCmd
-}
-
-const testPassphrase = "swarm-test-passphrase"
-
-func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
- // create key
- conf = &node.Config{
- DataDir: dir,
- IPCPath: "bzzd.ipc",
- NoUSB: true,
- }
- n, err := node.New(conf)
- if err != nil {
- t.Fatal(err)
- }
- account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
- if err != nil {
- t.Fatal(err)
- }
-
- // use a unique IPCPath when running tests on Windows
- if runtime.GOOS == "windows" {
- conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
- }
-
- return conf, account
-}
-
-func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
- conf, _ := getTestAccount(t, dir)
- node := &testNode{Dir: dir}
-
- // use a unique IPCPath when running tests on Windows
- if runtime.GOOS == "windows" {
- conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount)
- }
-
- // assign ports
- ports, err := getAvailableTCPPorts(2)
- if err != nil {
- t.Fatal(err)
- }
- p2pPort := ports[0]
- httpPort := ports[1]
-
- // start the node
- node.Cmd = runSwarm(t,
- "--bootnodes", "",
- "--port", p2pPort,
- "--nat", "extip:127.0.0.1",
- "--datadir", dir,
- "--ipcpath", conf.IPCPath,
- "--ens-api", "",
- "--bzzaccount", bzzaccount,
- "--bzznetworkid", "321",
- "--bzzport", httpPort,
- "--verbosity", fmt.Sprint(*loglevel),
- )
- node.Cmd.InputLine(testPassphrase)
- defer func() {
- if t.Failed() {
- node.Shutdown()
- }
- }()
-
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- // ensure that all ports have active listeners
- // so that the next node will not get the same
- // when calling getAvailableTCPPorts
- err = waitTCPPorts(ctx, ports...)
- if err != nil {
- t.Fatal(err)
- }
-
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
- node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
- node.URL = "http://" + node.Addr
-
- var nodeInfo p2p.NodeInfo
- if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
- t.Fatal(err)
- }
- node.Enode = nodeInfo.Enode
- node.IpcPath = conf.IPCPath
- return node
-}
-
-func newTestNode(t *testing.T, dir string) *testNode {
-
- conf, account := getTestAccount(t, dir)
- ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
-
- pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
-
- node := &testNode{Dir: dir, PrivateKey: pk}
-
- // assign ports
- ports, err := getAvailableTCPPorts(2)
- if err != nil {
- t.Fatal(err)
- }
- p2pPort := ports[0]
- httpPort := ports[1]
-
- // start the node
- node.Cmd = runSwarm(t,
- "--bootnodes", "",
- "--port", p2pPort,
- "--nat", "extip:127.0.0.1",
- "--datadir", dir,
- "--ipcpath", conf.IPCPath,
- "--ens-api", "",
- "--bzzaccount", account.Address.String(),
- "--bzznetworkid", "321",
- "--bzzport", httpPort,
- "--verbosity", fmt.Sprint(*loglevel),
- )
- node.Cmd.InputLine(testPassphrase)
- defer func() {
- if t.Failed() {
- node.Shutdown()
- }
- }()
-
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
-
- // ensure that all ports have active listeners
- // so that the next node will not get the same
- // when calling getAvailableTCPPorts
- err = waitTCPPorts(ctx, ports...)
- if err != nil {
- t.Fatal(err)
- }
-
- // wait for the node to start
- for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
- node.Client, err = rpc.Dial(conf.IPCEndpoint())
- if err == nil {
- break
- }
- }
- if node.Client == nil {
- t.Fatal(err)
- }
-
- // load info
- var info swarm.Info
- if err := node.Client.Call(&info, "bzz_info"); err != nil {
- t.Fatal(err)
- }
- node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
- node.URL = "http://" + node.Addr
-
- var nodeInfo p2p.NodeInfo
- if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
- t.Fatal(err)
- }
- node.Enode = nodeInfo.Enode
- node.IpcPath = conf.IPCPath
- return node
-}
-
-func (n *testNode) Shutdown() {
- if n.Cmd != nil {
- n.Cmd.Kill()
- }
-}
-
-// getAvailableTCPPorts returns a set of ports that
-// nothing is listening on at the time.
-//
-// Function assignTCPPort cannot be called in sequence
-// and guardantee that the same port will be returned in
-// different calls as the listener is closed within the function,
-// not after all listeners are started and selected unique
-// available ports.
-func getAvailableTCPPorts(count int) (ports []string, err error) {
- for i := 0; i < count; i++ {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- return nil, err
- }
- // defer close in the loop to be sure the same port will not
- // be selected in the next iteration
- defer l.Close()
-
- _, port, err := net.SplitHostPort(l.Addr().String())
- if err != nil {
- return nil, err
- }
- ports = append(ports, port)
- }
- return ports, nil
-}
-
-// waitTCPPorts blocks until tcp connections can be
-// established on all provided ports. It runs all
-// ports dialers in parallel, and returns the first
-// encountered error.
-// See waitTCPPort also.
-func waitTCPPorts(ctx context.Context, ports ...string) error {
- var err error
- // mu locks err variable that is assigned in
- // other goroutines
- var mu sync.Mutex
-
- // cancel is canceling all goroutines
- // when the firs error is returned
- // to prevent unnecessary waiting
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- var wg sync.WaitGroup
- for _, port := range ports {
- wg.Add(1)
- go func(port string) {
- defer wg.Done()
-
- e := waitTCPPort(ctx, port)
-
- mu.Lock()
- defer mu.Unlock()
- if e != nil && err == nil {
- err = e
- cancel()
- }
- }(port)
- }
- wg.Wait()
-
- return err
-}
-
-// waitTCPPort blocks until tcp connection can be established
-// ona provided port. It has a 3 minute timeout as maximum,
-// to prevent long waiting, but it can be shortened with
-// a provided context instance. Dialer has a 10 second timeout
-// in every iteration, and connection refused error will be
-// retried in 100 milliseconds periods.
-func waitTCPPort(ctx context.Context, port string) error {
- ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
- defer cancel()
-
- for {
- c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
- if err != nil {
- if operr, ok := err.(*net.OpError); ok {
- if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
- time.Sleep(100 * time.Millisecond)
- continue
- }
- }
- return err
- }
- return c.Close()
- }
-}
diff --git a/cmd/swarm/swarm-smoke/feed_upload_and_sync.go b/cmd/swarm/swarm-smoke/feed_upload_and_sync.go
deleted file mode 100644
index b5ffc43d2..000000000
--- a/cmd/swarm/swarm-smoke/feed_upload_and_sync.go
+++ /dev/null
@@ -1,291 +0,0 @@
-package main
-
-import (
- "bytes"
- "crypto/md5"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/swarm/storage/feed"
- "github.com/ethereum/go-ethereum/swarm/testutil"
- "github.com/pborman/uuid"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-const (
- feedRandomDataLength = 8
-)
-
-func feedUploadAndSyncCmd(ctx *cli.Context) error {
- errc := make(chan error)
-
- go func() {
- errc <- feedUploadAndSync(ctx)
- }()
-
- select {
- case err := <-errc:
- if err != nil {
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
- }
- return err
- case <-time.After(time.Duration(timeout) * time.Second):
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
-
- return fmt.Errorf("timeout after %v sec", timeout)
- }
-}
-
-func feedUploadAndSync(c *cli.Context) error {
- log.Info("generating and uploading feeds to " + httpEndpoint(hosts[0]) + " and syncing")
-
- // create a random private key to sign updates with and derive the address
- pkFile, err := ioutil.TempFile("", "swarm-feed-smoke-test")
- if err != nil {
- return err
- }
- defer pkFile.Close()
- defer os.Remove(pkFile.Name())
-
- privkeyHex := "0000000000000000000000000000000000000000000000000000000000001976"
- privKey, err := crypto.HexToECDSA(privkeyHex)
- if err != nil {
- return err
- }
- user := crypto.PubkeyToAddress(privKey.PublicKey)
- userHex := hexutil.Encode(user.Bytes())
-
- // save the private key to a file
- _, err = io.WriteString(pkFile, privkeyHex)
- if err != nil {
- return err
- }
-
- // keep hex strings for topic and subtopic
- var topicHex string
- var subTopicHex string
-
- // and create combination hex topics for bzz-feed retrieval
- // xor'ed with topic (zero-value topic if no topic)
- var subTopicOnlyHex string
- var mergedSubTopicHex string
-
- // generate random topic and subtopic and put a hex on them
- topicBytes, err := generateRandomData(feed.TopicLength)
- topicHex = hexutil.Encode(topicBytes)
- subTopicBytes, err := generateRandomData(8)
- subTopicHex = hexutil.Encode(subTopicBytes)
- if err != nil {
- return err
- }
- mergedSubTopic, err := feed.NewTopic(subTopicHex, topicBytes)
- if err != nil {
- return err
- }
- mergedSubTopicHex = hexutil.Encode(mergedSubTopic[:])
- subTopicOnlyBytes, err := feed.NewTopic(subTopicHex, nil)
- if err != nil {
- return err
- }
- subTopicOnlyHex = hexutil.Encode(subTopicOnlyBytes[:])
-
- // create feed manifest, topic only
- var out bytes.Buffer
- cmd := exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--user", userHex)
- cmd.Stdout = &out
- log.Debug("create feed manifest topic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- return err
- }
- manifestWithTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
- if len(manifestWithTopic) != 64 {
- return fmt.Errorf("unknown feed create manifest hash format (topic): (%d) %s", len(out.String()), manifestWithTopic)
- }
- log.Debug("create topic feed", "manifest", manifestWithTopic)
- out.Reset()
-
- // create feed manifest, subtopic only
- cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--name", subTopicHex, "--user", userHex)
- cmd.Stdout = &out
- log.Debug("create feed manifest subtopic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- return err
- }
- manifestWithSubTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
- if len(manifestWithSubTopic) != 64 {
- return fmt.Errorf("unknown feed create manifest hash format (subtopic): (%d) %s", len(out.String()), manifestWithSubTopic)
- }
- log.Debug("create subtopic feed", "manifest", manifestWithTopic)
- out.Reset()
-
- // create feed manifest, merged topic
- cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--name", subTopicHex, "--user", userHex)
- cmd.Stdout = &out
- log.Debug("create feed manifest mergetopic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- log.Error(err.Error())
- return err
- }
- manifestWithMergedTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
- if len(manifestWithMergedTopic) != 64 {
- return fmt.Errorf("unknown feed create manifest hash format (mergedtopic): (%d) %s", len(out.String()), manifestWithMergedTopic)
- }
- log.Debug("create mergedtopic feed", "manifest", manifestWithMergedTopic)
- out.Reset()
-
- // create test data
- data, err := generateRandomData(feedRandomDataLength)
- if err != nil {
- return err
- }
- h := md5.New()
- h.Write(data)
- dataHash := h.Sum(nil)
- dataHex := hexutil.Encode(data)
-
- // update with topic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, dataHex)
- cmd.Stdout = &out
- log.Debug("update feed manifest topic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update topic", "out", out)
- out.Reset()
-
- // update with subtopic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, dataHex)
- cmd.Stdout = &out
- log.Debug("update feed manifest subtopic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update subtopic", "out", out)
- out.Reset()
-
- // update with merged topic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, dataHex)
- cmd.Stdout = &out
- log.Debug("update feed manifest merged topic cmd", "cmd", cmd)
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update mergedtopic", "out", out)
- out.Reset()
-
- time.Sleep(3 * time.Second)
-
- // retrieve the data
- wg := sync.WaitGroup{}
- for _, host := range hosts {
- // raw retrieve, topic only
- for _, hex := range []string{topicHex, subTopicOnlyHex, mergedSubTopicHex} {
- wg.Add(1)
- ruid := uuid.New()[:8]
- go func(hex string, endpoint string, ruid string) {
- for {
- err := fetchFeed(hex, userHex, httpEndpoint(host), dataHash, ruid)
- if err != nil {
- continue
- }
-
- wg.Done()
- return
- }
- }(hex, httpEndpoint(host), ruid)
- }
- }
- wg.Wait()
- log.Info("all endpoints synced random data successfully")
-
- // upload test file
- log.Info("feed uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
-
- randomBytes := testutil.RandomBytes(seed, filesize*1000)
-
- hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
- if err != nil {
- return err
- }
- hashBytes, err := hexutil.Decode("0x" + hash)
- if err != nil {
- return err
- }
- multihashHex := hexutil.Encode(hashBytes)
- fileHash := h.Sum(nil)
-
- log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fileHash))
-
- // update file with topic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, multihashHex)
- cmd.Stdout = &out
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update topic", "out", out)
- out.Reset()
-
- // update file with subtopic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, multihashHex)
- cmd.Stdout = &out
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update subtopic", "out", out)
- out.Reset()
-
- // update file with merged topic
- cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, multihashHex)
- cmd.Stdout = &out
- err = cmd.Run()
- if err != nil {
- return err
- }
- log.Debug("feed update mergedtopic", "out", out)
- out.Reset()
-
- time.Sleep(3 * time.Second)
-
- for _, host := range hosts {
-
- // manifest retrieve, topic only
- for _, url := range []string{manifestWithTopic, manifestWithSubTopic, manifestWithMergedTopic} {
- wg.Add(1)
- ruid := uuid.New()[:8]
- go func(url string, endpoint string, ruid string) {
- for {
- err := fetch(url, endpoint, fileHash, ruid)
- if err != nil {
- continue
- }
-
- wg.Done()
- return
- }
- }(url, httpEndpoint(host), ruid)
- }
-
- }
- wg.Wait()
- log.Info("all endpoints synced random file successfully")
-
- return nil
-}
diff --git a/cmd/swarm/swarm-smoke/main.go b/cmd/swarm/swarm-smoke/main.go
deleted file mode 100644
index 03e2cc2c4..000000000
--- a/cmd/swarm/swarm-smoke/main.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2018 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 (
- "fmt"
- "os"
- "sort"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- gethmetrics "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/metrics/influxdb"
- swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
- "github.com/ethereum/go-ethereum/swarm/tracing"
-
- "github.com/ethereum/go-ethereum/log"
-
- cli "gopkg.in/urfave/cli.v1"
-)
-
-var (
- gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
-)
-
-var (
- allhosts string
- hosts []string
- filesize int
- syncDelay bool
- inputSeed int
- httpPort int
- wsPort int
- verbosity int
- timeout int
- single bool
- onlyUpload bool
-)
-
-func main() {
-
- app := cli.NewApp()
- app.Name = "smoke-test"
- app.Usage = ""
-
- app.Flags = []cli.Flag{
- cli.StringFlag{
- Name: "hosts",
- Value: "",
- Usage: "comma-separated list of swarm hosts",
- Destination: &allhosts,
- },
- cli.IntFlag{
- Name: "http-port",
- Value: 80,
- Usage: "http port",
- Destination: &httpPort,
- },
- cli.IntFlag{
- Name: "ws-port",
- Value: 8546,
- Usage: "ws port",
- Destination: &wsPort,
- },
- cli.IntFlag{
- Name: "seed",
- Value: 0,
- Usage: "input seed in case we need deterministic upload",
- Destination: &inputSeed,
- },
- cli.IntFlag{
- Name: "filesize",
- Value: 1024,
- Usage: "file size for generated random file in KB",
- Destination: &filesize,
- },
- cli.BoolFlag{
- Name: "sync-delay",
- Usage: "wait for content to be synced",
- Destination: &syncDelay,
- },
- cli.IntFlag{
- Name: "verbosity",
- Value: 1,
- Usage: "verbosity",
- Destination: &verbosity,
- },
- cli.IntFlag{
- Name: "timeout",
- Value: 180,
- Usage: "timeout in seconds after which kill the process",
- Destination: &timeout,
- },
- cli.BoolFlag{
- Name: "single",
- Usage: "whether to fetch content from a single node or from all nodes",
- Destination: &single,
- },
- cli.BoolFlag{
- Name: "only-upload",
- Usage: "whether to only upload content to a single node without fetching",
- Destination: &onlyUpload,
- },
- }
-
- app.Flags = append(app.Flags, []cli.Flag{
- utils.MetricsEnabledFlag,
- swarmmetrics.MetricsInfluxDBEndpointFlag,
- swarmmetrics.MetricsInfluxDBDatabaseFlag,
- swarmmetrics.MetricsInfluxDBUsernameFlag,
- swarmmetrics.MetricsInfluxDBPasswordFlag,
- swarmmetrics.MetricsInfluxDBTagsFlag,
- }...)
-
- app.Flags = append(app.Flags, tracing.Flags...)
-
- app.Commands = []cli.Command{
- {
- Name: "upload_and_sync",
- Aliases: []string{"c"},
- Usage: "upload and sync",
- Action: wrapCliCommand("upload-and-sync", uploadAndSyncCmd),
- },
- {
- Name: "feed_sync",
- Aliases: []string{"f"},
- Usage: "feed update generate, upload and sync",
- Action: wrapCliCommand("feed-and-sync", feedUploadAndSyncCmd),
- },
- {
- Name: "upload_speed",
- Aliases: []string{"u"},
- Usage: "measure upload speed",
- Action: wrapCliCommand("upload-speed", uploadSpeedCmd),
- },
- {
- Name: "sliding_window",
- Aliases: []string{"s"},
- Usage: "measure network aggregate capacity",
- Action: wrapCliCommand("sliding-window", slidingWindowCmd),
- },
- }
-
- sort.Sort(cli.FlagsByName(app.Flags))
- sort.Sort(cli.CommandsByName(app.Commands))
-
- app.Before = func(ctx *cli.Context) error {
- tracing.Setup(ctx)
- return nil
- }
-
- app.After = func(ctx *cli.Context) error {
- return emitMetrics(ctx)
- }
-
- err := app.Run(os.Args)
- if err != nil {
- log.Error(err.Error())
-
- os.Exit(1)
- }
-}
-
-func emitMetrics(ctx *cli.Context) error {
- if gethmetrics.Enabled {
- var (
- endpoint = ctx.GlobalString(swarmmetrics.MetricsInfluxDBEndpointFlag.Name)
- database = ctx.GlobalString(swarmmetrics.MetricsInfluxDBDatabaseFlag.Name)
- username = ctx.GlobalString(swarmmetrics.MetricsInfluxDBUsernameFlag.Name)
- password = ctx.GlobalString(swarmmetrics.MetricsInfluxDBPasswordFlag.Name)
- tags = ctx.GlobalString(swarmmetrics.MetricsInfluxDBTagsFlag.Name)
- )
-
- tagsMap := utils.SplitTagsFlag(tags)
- tagsMap["version"] = gitCommit
- tagsMap["filesize"] = fmt.Sprintf("%v", filesize)
-
- return influxdb.InfluxDBWithTagsOnce(gethmetrics.DefaultRegistry, endpoint, database, username, password, "swarm-smoke.", tagsMap)
- }
-
- return nil
-}
diff --git a/cmd/swarm/swarm-smoke/sliding_window.go b/cmd/swarm/swarm-smoke/sliding_window.go
deleted file mode 100644
index 6ca3d3947..000000000
--- a/cmd/swarm/swarm-smoke/sliding_window.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "fmt"
- "math/rand"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/swarm/testutil"
- "github.com/pborman/uuid"
-
- cli "gopkg.in/urfave/cli.v1"
-)
-
-type uploadResult struct {
- hash string
- digest []byte
-}
-
-func slidingWindowCmd(ctx *cli.Context) error {
- errc := make(chan error)
-
- go func() {
- errc <- slidingWindow(ctx)
- }()
-
- err := <-errc
- if err != nil {
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
- }
- return err
-}
-
-func slidingWindow(ctx *cli.Context) error {
- var hashes []uploadResult //swarm hashes of the uploads
- nodes := len(hosts)
- log.Info("sliding window test started", "nodes", nodes, "filesize(kb)", filesize, "timeout", timeout)
- uploadedBytes := 0
- networkDepth := 0
- errored := false
-
-outer:
- for {
- seed = int(time.Now().UTC().UnixNano())
- log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
-
- t1 := time.Now()
-
- randomBytes := testutil.RandomBytes(seed, filesize*1000)
-
- hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
- if err != nil {
- log.Error(err.Error())
- return err
- }
-
- metrics.GetOrRegisterResettingTimer("sliding-window.upload-time", nil).UpdateSince(t1)
- metrics.GetOrRegisterGauge("sliding-window.upload-depth", nil).Update(int64(len(hashes)))
-
- fhash, err := digest(bytes.NewReader(randomBytes))
- if err != nil {
- log.Error(err.Error())
- return err
- }
-
- log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash), "wait for sync", syncDelay)
- hashes = append(hashes, uploadResult{hash: hash, digest: fhash})
-
- if syncDelay {
- waitToSync()
- }
-
- uploadedBytes += filesize * 1000
- q := make(chan struct{}, 1)
- d := make(chan struct{})
- defer close(q)
- defer close(d)
- for i, v := range hashes {
- timeoutC := time.After(time.Duration(timeout) * time.Second)
- errored = false
-
- task:
- for {
- select {
- case q <- struct{}{}:
- go func() {
- var start time.Time
- done := false
- for !done {
- log.Info("trying to retrieve hash", "hash", v.hash)
- idx := 1 + rand.Intn(len(hosts)-1)
- ruid := uuid.New()[:8]
- start = time.Now()
- // fetch hangs when swarm dies out, so we have to jump through a bit more hoops to actually
- // catch the timeout, but also allow this retry logic
- err := fetch(v.hash, httpEndpoint(hosts[idx]), v.digest, ruid)
- if err != nil {
- log.Error("error fetching hash", "err", err)
- continue
- }
- done = true
- }
- metrics.GetOrRegisterResettingTimer("sliding-window.single.fetch-time", nil).UpdateSince(start)
- d <- struct{}{}
- }()
- case <-d:
- <-q
- break task
- case <-timeoutC:
- errored = true
- log.Error("error retrieving hash. timeout", "hash idx", i)
- metrics.GetOrRegisterCounter("sliding-window.single.error", nil).Inc(1)
- break outer
- default:
- }
- }
-
- networkDepth = i
- metrics.GetOrRegisterGauge("sliding-window.network-depth", nil).Update(int64(networkDepth))
- log.Info("sliding window test successfully fetched file", "currentDepth", networkDepth)
- // this test might take a long time to finish - but we'd like to see metrics while they accumulate and not just when
- // the test finishes. therefore emit the metrics on each iteration
- emitMetrics(ctx)
- }
- }
-
- log.Info("sliding window test finished", "errored?", errored, "networkDepth", networkDepth, "networkDepth(kb)", networkDepth*filesize)
- log.Info("stats", "uploadedFiles", len(hashes), "uploadedKb", uploadedBytes/1000, "filesizeKb", filesize)
-
- return nil
-}
diff --git a/cmd/swarm/swarm-smoke/upload_and_sync.go b/cmd/swarm/swarm-smoke/upload_and_sync.go
deleted file mode 100644
index 7338e3473..000000000
--- a/cmd/swarm/swarm-smoke/upload_and_sync.go
+++ /dev/null
@@ -1,376 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "context"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "math/rand"
- "os"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/rpc"
- "github.com/ethereum/go-ethereum/swarm/chunk"
- "github.com/ethereum/go-ethereum/swarm/storage"
- "github.com/ethereum/go-ethereum/swarm/testutil"
-
- cli "gopkg.in/urfave/cli.v1"
-)
-
-func uploadAndSyncCmd(ctx *cli.Context) error {
- // use input seed if it has been set
- if inputSeed != 0 {
- seed = inputSeed
- }
-
- randomBytes := testutil.RandomBytes(seed, filesize*1000)
-
- errc := make(chan error)
-
- go func() {
- errc <- uploadAndSync(ctx, randomBytes)
- }()
-
- var err error
- select {
- case err = <-errc:
- if err != nil {
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
- }
- case <-time.After(time.Duration(timeout) * time.Second):
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
-
- err = fmt.Errorf("timeout after %v sec", timeout)
- }
-
- // trigger debug functionality on randomBytes
- e := trackChunks(randomBytes[:], true)
- if e != nil {
- log.Error(e.Error())
- }
-
- return err
-}
-
-func trackChunks(testData []byte, submitMetrics bool) error {
- addrs, err := getAllRefs(testData)
- if err != nil {
- return err
- }
-
- for i, ref := range addrs {
- log.Debug(fmt.Sprintf("ref %d", i), "ref", ref)
- }
-
- var globalYes, globalNo int
- var globalMu sync.Mutex
- var hasErr bool
-
- var wg sync.WaitGroup
- wg.Add(len(hosts))
-
- var mu sync.Mutex // mutex protecting the allHostsChunks and bzzAddrs maps
- allHostChunks := map[string]string{} // host->bitvector of presence for chunks
- bzzAddrs := map[string]string{} // host->bzzAddr
-
- for _, host := range hosts {
- host := host
- go func() {
- defer wg.Done()
- httpHost := fmt.Sprintf("ws://%s:%d", host, 8546)
-
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
- defer cancel()
-
- rpcClient, err := rpc.DialContext(ctx, httpHost)
- if rpcClient != nil {
- defer rpcClient.Close()
- }
- if err != nil {
- log.Error("error dialing host", "err", err, "host", httpHost)
- hasErr = true
- return
- }
-
- hostChunks, err := getChunksBitVectorFromHost(rpcClient, addrs)
- if err != nil {
- log.Error("error getting chunks bit vector from host", "err", err, "host", httpHost)
- hasErr = true
- return
- }
-
- bzzAddr, err := getBzzAddrFromHost(rpcClient)
- if err != nil {
- log.Error("error getting bzz addrs from host", "err", err, "host", httpHost)
- hasErr = true
- return
- }
-
- mu.Lock()
- allHostChunks[host] = hostChunks
- bzzAddrs[host] = bzzAddr
- mu.Unlock()
-
- yes, no := 0, 0
- for _, val := range hostChunks {
- if val == '1' {
- yes++
- } else {
- no++
- }
- }
-
- if no == 0 {
- log.Info("host reported to have all chunks", "host", host)
- }
-
- log.Debug("chunks", "chunks", hostChunks, "yes", yes, "no", no, "host", host)
-
- if submitMetrics {
- globalMu.Lock()
- globalYes += yes
- globalNo += no
- globalMu.Unlock()
- }
- }()
- }
-
- wg.Wait()
-
- checkChunksVsMostProxHosts(addrs, allHostChunks, bzzAddrs)
-
- if !hasErr && submitMetrics {
- // remove the chunks stored on the uploader node
- globalYes -= len(addrs)
-
- metrics.GetOrRegisterCounter("deployment.chunks.yes", nil).Inc(int64(globalYes))
- metrics.GetOrRegisterCounter("deployment.chunks.no", nil).Inc(int64(globalNo))
- metrics.GetOrRegisterCounter("deployment.chunks.refs", nil).Inc(int64(len(addrs)))
- }
-
- return nil
-}
-
-// getChunksBitVectorFromHost returns a bit vector of presence for a given slice of chunks from a given host
-func getChunksBitVectorFromHost(client *rpc.Client, addrs []storage.Address) (string, error) {
- var hostChunks string
-
- err := client.Call(&hostChunks, "bzz_has", addrs)
- if err != nil {
- return "", err
- }
-
- return hostChunks, nil
-}
-
-// getBzzAddrFromHost returns the bzzAddr for a given host
-func getBzzAddrFromHost(client *rpc.Client) (string, error) {
- var hive string
-
- err := client.Call(&hive, "bzz_hive")
- if err != nil {
- return "", err
- }
-
- // we make an ugly assumption about the output format of the hive.String() method
- // ideally we should replace this with an API call that returns the bzz addr for a given host,
- // but this also works for now (provided we don't change the hive.String() method, which we haven't in some time
- ss := strings.Split(strings.Split(hive, "\n")[3], " ")
- return ss[len(ss)-1], nil
-}
-
-// checkChunksVsMostProxHosts is checking:
-// 1. whether a chunk has been found at less than 2 hosts. Considering our NN size, this should not happen.
-// 2. if a chunk is not found at its closest node. This should also not happen.
-// Together with the --only-upload flag, we could run this smoke test and make sure that our syncing
-// functionality is correct (without even trying to retrieve the content).
-//
-// addrs - a slice with all uploaded chunk refs
-// allHostChunks - host->bit vector, showing what chunks are present on what hosts
-// bzzAddrs - host->bzz address, used when determining the most proximate host for a given chunk
-func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[string]string, bzzAddrs map[string]string) {
- for k, v := range bzzAddrs {
- log.Trace("bzzAddr", "bzz", v, "host", k)
- }
-
- for i := range addrs {
- var foundAt int
- maxProx := -1
- var maxProxHost string
- for host := range allHostChunks {
- if allHostChunks[host][i] == '1' {
- foundAt++
- }
-
- ba, err := hex.DecodeString(bzzAddrs[host])
- if err != nil {
- panic(err)
- }
-
- // calculate the host closest to any chunk
- prox := chunk.Proximity(addrs[i], ba)
- if prox > maxProx {
- maxProx = prox
- maxProxHost = host
- }
- }
-
- if allHostChunks[maxProxHost][i] == '0' {
- log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
- } else {
- log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
- }
-
- // if chunk found at less than 2 hosts
- if foundAt < 2 {
- log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
- }
- }
-}
-
-func getAllRefs(testData []byte) (storage.AddressCollection, error) {
- datadir, err := ioutil.TempDir("", "chunk-debug")
- if err != nil {
- return nil, fmt.Errorf("unable to create temp dir: %v", err)
- }
- defer os.RemoveAll(datadir)
- fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
- if err != nil {
- return nil, err
- }
-
- reader := bytes.NewReader(testData)
- return fileStore.GetAllReferences(context.Background(), reader, false)
-}
-
-func uploadAndSync(c *cli.Context, randomBytes []byte) error {
- log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
-
- t1 := time.Now()
- hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
- if err != nil {
- log.Error(err.Error())
- return err
- }
- t2 := time.Since(t1)
- metrics.GetOrRegisterResettingTimer("upload-and-sync.upload-time", nil).Update(t2)
-
- fhash, err := digest(bytes.NewReader(randomBytes))
- if err != nil {
- log.Error(err.Error())
- return err
- }
-
- log.Info("uploaded successfully", "hash", hash, "took", t2, "digest", fmt.Sprintf("%x", fhash))
-
- // wait to sync and log chunks before fetch attempt, only if syncDelay is set to true
- if syncDelay {
- waitToSync()
-
- log.Debug("chunks before fetch attempt", "hash", hash)
-
- err = trackChunks(randomBytes, false)
- if err != nil {
- log.Error(err.Error())
- }
- }
-
- if onlyUpload {
- log.Debug("only-upload is true, stoppping test", "hash", hash)
- return nil
- }
-
- randIndex := 1 + rand.Intn(len(hosts)-1)
-
- for {
- start := time.Now()
- err := fetch(hash, httpEndpoint(hosts[randIndex]), fhash, "")
- if err != nil {
- time.Sleep(2 * time.Second)
- continue
- }
- ended := time.Since(start)
-
- metrics.GetOrRegisterResettingTimer("upload-and-sync.single.fetch-time", nil).Update(ended)
- log.Info("fetch successful", "took", ended, "endpoint", httpEndpoint(hosts[randIndex]))
- break
- }
-
- return nil
-}
-
-func isSyncing(wsHost string) (bool, error) {
- rpcClient, err := rpc.Dial(wsHost)
- if rpcClient != nil {
- defer rpcClient.Close()
- }
-
- if err != nil {
- log.Error("error dialing host", "err", err)
- return false, err
- }
-
- var isSyncing bool
- err = rpcClient.Call(&isSyncing, "bzz_isSyncing")
- if err != nil {
- log.Error("error calling host for isSyncing", "err", err)
- return false, err
- }
-
- log.Debug("isSyncing result", "host", wsHost, "isSyncing", isSyncing)
-
- return isSyncing, nil
-}
-
-func waitToSync() {
- t1 := time.Now()
-
- ns := uint64(1)
-
- for ns > 0 {
- time.Sleep(3 * time.Second)
-
- notSynced := uint64(0)
- var wg sync.WaitGroup
- wg.Add(len(hosts))
- for i := 0; i < len(hosts); i++ {
- i := i
- go func(idx int) {
- stillSyncing, err := isSyncing(wsEndpoint(hosts[idx]))
-
- if stillSyncing || err != nil {
- atomic.AddUint64(&notSynced, 1)
- }
- wg.Done()
- }(i)
- }
- wg.Wait()
-
- ns = atomic.LoadUint64(&notSynced)
- }
-
- t2 := time.Since(t1)
- metrics.GetOrRegisterResettingTimer("upload-and-sync.single.wait-for-sync.deployment", nil).Update(t2)
-}
diff --git a/cmd/swarm/swarm-smoke/upload_speed.go b/cmd/swarm/swarm-smoke/upload_speed.go
deleted file mode 100644
index 047ea0092..000000000
--- a/cmd/swarm/swarm-smoke/upload_speed.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/swarm/testutil"
-
- cli "gopkg.in/urfave/cli.v1"
-)
-
-func uploadSpeedCmd(ctx *cli.Context) error {
- log.Info("uploading to "+hosts[0], "seed", seed)
- randomBytes := testutil.RandomBytes(seed, filesize*1000)
-
- errc := make(chan error)
-
- go func() {
- errc <- uploadSpeed(ctx, randomBytes)
- }()
-
- select {
- case err := <-errc:
- if err != nil {
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
- }
- return err
- case <-time.After(time.Duration(timeout) * time.Second):
- metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
-
- // trigger debug functionality on randomBytes
-
- return fmt.Errorf("timeout after %v sec", timeout)
- }
-}
-
-func uploadSpeed(c *cli.Context, data []byte) error {
- t1 := time.Now()
- hash, err := upload(data, hosts[0])
- if err != nil {
- log.Error(err.Error())
- return err
- }
- metrics.GetOrRegisterCounter("upload-speed.upload-time", nil).Inc(int64(time.Since(t1)))
-
- fhash, err := digest(bytes.NewReader(data))
- if err != nil {
- log.Error(err.Error())
- return err
- }
-
- log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
- return nil
-}
diff --git a/cmd/swarm/swarm-smoke/util.go b/cmd/swarm/swarm-smoke/util.go
deleted file mode 100644
index b95f993e8..000000000
--- a/cmd/swarm/swarm-smoke/util.go
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2018 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 (
- "bytes"
- "context"
- "crypto/md5"
- crand "crypto/rand"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math/rand"
- "net/http"
- "net/http/httptrace"
- "os"
- "strings"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/swarm/api"
- "github.com/ethereum/go-ethereum/swarm/api/client"
- "github.com/ethereum/go-ethereum/swarm/spancontext"
- opentracing "github.com/opentracing/opentracing-go"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-var (
- commandName = ""
- seed = int(time.Now().UTC().UnixNano())
-)
-
-func init() {
- rand.Seed(int64(seed))
-}
-
-func httpEndpoint(host string) string {
- return fmt.Sprintf("http://%s:%d", host, httpPort)
-}
-
-func wsEndpoint(host string) string {
- return fmt.Sprintf("ws://%s:%d", host, wsPort)
-}
-
-func wrapCliCommand(name string, command func(*cli.Context) error) func(*cli.Context) error {
- return func(ctx *cli.Context) error {
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
-
- commandName = name
-
- hosts = strings.Split(allhosts, ",")
-
- defer func(now time.Time) {
- totalTime := time.Since(now)
- log.Info("total time", "time", totalTime, "kb", filesize)
- metrics.GetOrRegisterResettingTimer(name+".total-time", nil).Update(totalTime)
- }(time.Now())
-
- log.Info("smoke test starting", "task", name, "timeout", timeout)
- metrics.GetOrRegisterCounter(name, nil).Inc(1)
-
- return command(ctx)
- }
-}
-
-func fetchFeed(topic string, user string, endpoint string, original []byte, ruid string) error {
- ctx, sp := spancontext.StartSpan(context.Background(), "feed-and-sync.fetch")
- defer sp.Finish()
-
- log.Trace("sleeping", "ruid", ruid)
- time.Sleep(3 * time.Second)
-
- log.Trace("http get request (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user)
-
- var tn time.Time
- reqUri := endpoint + "/bzz-feed:/?topic=" + topic + "&user=" + user
- req, _ := http.NewRequest("GET", reqUri, nil)
-
- opentracing.GlobalTracer().Inject(
- sp.Context(),
- opentracing.HTTPHeaders,
- opentracing.HTTPHeadersCarrier(req.Header))
-
- trace := client.GetClientTrace("feed-and-sync - http get", "feed-and-sync", ruid, &tn)
-
- req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
- transport := http.DefaultTransport
-
- //transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- tn = time.Now()
- res, err := transport.RoundTrip(req)
- if err != nil {
- log.Error(err.Error(), "ruid", ruid)
- return err
- }
-
- log.Trace("http get response (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user, "code", res.StatusCode, "len", res.ContentLength)
-
- if res.StatusCode != 200 {
- return fmt.Errorf("expected status code %d, got %v (ruid %v)", 200, res.StatusCode, ruid)
- }
-
- defer res.Body.Close()
-
- rdigest, err := digest(res.Body)
- if err != nil {
- log.Warn(err.Error(), "ruid", ruid)
- return err
- }
-
- if !bytes.Equal(rdigest, original) {
- err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
- log.Warn(err.Error(), "ruid", ruid)
- return err
- }
-
- log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
-
- return nil
-}
-
-// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file
-func fetch(hash string, endpoint string, original []byte, ruid string) error {
- ctx, sp := spancontext.StartSpan(context.Background(), "upload-and-sync.fetch")
- defer sp.Finish()
-
- log.Info("http get request", "ruid", ruid, "endpoint", endpoint, "hash", hash)
-
- var tn time.Time
- reqUri := endpoint + "/bzz:/" + hash + "/"
- req, _ := http.NewRequest("GET", reqUri, nil)
-
- opentracing.GlobalTracer().Inject(
- sp.Context(),
- opentracing.HTTPHeaders,
- opentracing.HTTPHeadersCarrier(req.Header))
-
- trace := client.GetClientTrace(commandName+" - http get", commandName, ruid, &tn)
-
- req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
- transport := http.DefaultTransport
-
- //transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
- tn = time.Now()
- res, err := transport.RoundTrip(req)
- if err != nil {
- log.Error(err.Error(), "ruid", ruid)
- return err
- }
- log.Info("http get response", "ruid", ruid, "endpoint", endpoint, "hash", hash, "code", res.StatusCode, "len", res.ContentLength)
-
- if res.StatusCode != 200 {
- err := fmt.Errorf("expected status code %d, got %v", 200, res.StatusCode)
- log.Warn(err.Error(), "ruid", ruid)
- return err
- }
-
- defer res.Body.Close()
-
- rdigest, err := digest(res.Body)
- if err != nil {
- log.Warn(err.Error(), "ruid", ruid)
- return err
- }
-
- if !bytes.Equal(rdigest, original) {
- err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
- log.Warn(err.Error(), "ruid", ruid)
- return err
- }
-
- log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
-
- return nil
-}
-
-// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
-func upload(data []byte, endpoint string) (string, error) {
- swarm := client.NewClient(endpoint)
- f := &client.File{
- ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
- ManifestEntry: api.ManifestEntry{
- ContentType: "text/plain",
- Mode: 0660,
- Size: int64(len(data)),
- },
- }
-
- // upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded.
- return swarm.Upload(f, "", false)
-}
-
-func digest(r io.Reader) ([]byte, error) {
- h := md5.New()
- _, err := io.Copy(h, r)
- if err != nil {
- return nil, err
- }
- return h.Sum(nil), nil
-}
-
-// generates random data in heap buffer
-func generateRandomData(datasize int) ([]byte, error) {
- b := make([]byte, datasize)
- c, err := crand.Read(b)
- if err != nil {
- return nil, err
- } else if c != datasize {
- return nil, errors.New("short read")
- }
- return b, nil
-}
diff --git a/cmd/swarm/swarm-snapshot/create.go b/cmd/swarm/swarm-snapshot/create.go
deleted file mode 100644
index 434561a49..000000000
--- a/cmd/swarm/swarm-snapshot/create.go
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2018 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 (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/p2p/simulations"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
- "github.com/ethereum/go-ethereum/swarm/network"
- "github.com/ethereum/go-ethereum/swarm/network/simulation"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-// create is used as the entry function for "create" app command.
-func create(ctx *cli.Context) error {
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
-
- if len(ctx.Args()) < 1 {
- return errors.New("argument should be the filename to verify or write-to")
- }
- filename, err := touchPath(ctx.Args()[0])
- if err != nil {
- return err
- }
- return createSnapshot(filename, ctx.Int("nodes"), strings.Split(ctx.String("services"), ","))
-}
-
-// createSnapshot creates a new snapshot on filesystem with provided filename,
-// number of nodes and service names.
-func createSnapshot(filename string, nodes int, services []string) (err error) {
- log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services)
-
- sim := simulation.New(map[string]simulation.ServiceFunc{
- "bzz": func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
- addr := network.NewAddr(ctx.Config.Node())
- kad := network.NewKademlia(addr.Over(), network.NewKadParams())
- hp := network.NewHiveParams()
- hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
- hp.Discovery = true // discovery must be enabled when creating a snapshot
-
- // store the kademlia in the bucket, needed later in the WaitTillHealthy function
- bucket.Store(simulation.BucketKeyKademlia, kad)
-
- config := &network.BzzConfig{
- OverlayAddr: addr.Over(),
- UnderlayAddr: addr.Under(),
- HiveParams: hp,
- }
- return network.NewBzz(config, kad, nil, nil, nil), nil, nil
- },
- })
- defer sim.Close()
-
- ids, err := sim.AddNodes(nodes)
- if err != nil {
- return fmt.Errorf("add nodes: %v", err)
- }
-
- err = sim.Net.ConnectNodesRing(ids)
- if err != nil {
- return fmt.Errorf("connect nodes: %v", err)
- }
-
- ctx, cancelSimRun := context.WithTimeout(context.Background(), 3*time.Minute)
- defer cancelSimRun()
- if _, err := sim.WaitTillHealthy(ctx); err != nil {
- return fmt.Errorf("wait for healthy kademlia: %v", err)
- }
-
- var snap *simulations.Snapshot
- if len(services) > 0 {
- // If service names are provided, include them in the snapshot.
- // But, check if "bzz" service is not among them to remove it
- // form the snapshot as it exists on snapshot creation.
- var removeServices []string
- var wantBzz bool
- for _, s := range services {
- if s == "bzz" {
- wantBzz = true
- break
- }
- }
- if !wantBzz {
- removeServices = []string{"bzz"}
- }
- snap, err = sim.Net.SnapshotWithServices(services, removeServices)
- } else {
- snap, err = sim.Net.Snapshot()
- }
- if err != nil {
- return fmt.Errorf("create snapshot: %v", err)
- }
- jsonsnapshot, err := json.Marshal(snap)
- if err != nil {
- return fmt.Errorf("json encode snapshot: %v", err)
- }
- return ioutil.WriteFile(filename, jsonsnapshot, 0666)
-}
-
-// touchPath creates an empty file and all subdirectories
-// that are missing.
-func touchPath(filename string) (string, error) {
- if path.IsAbs(filename) {
- if _, err := os.Stat(filename); err == nil {
- // path exists, overwrite
- return filename, nil
- }
- }
-
- d, f := path.Split(filename)
- dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
- if err != nil {
- return "", err
- }
-
- _, err = os.Stat(path.Join(dir, filename))
- if err == nil {
- // path exists, overwrite
- return filename, nil
- }
-
- dirPath := path.Join(dir, d)
- filePath := path.Join(dirPath, f)
- if d != "" {
- err = os.MkdirAll(dirPath, os.ModeDir)
- if err != nil {
- return "", err
- }
- }
-
- return filePath, nil
-}
diff --git a/cmd/swarm/swarm-snapshot/create_test.go b/cmd/swarm/swarm-snapshot/create_test.go
deleted file mode 100644
index 4cd78f35a..000000000
--- a/cmd/swarm/swarm-snapshot/create_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2018 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 (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "sort"
- "strconv"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/p2p/simulations"
-)
-
-// TestSnapshotCreate is a high level e2e test that tests for snapshot generation.
-// It runs a few "create" commands with different flag values and loads generated
-// snapshot files to validate their content.
-func TestSnapshotCreate(t *testing.T) {
- t.Skip("test is flaky. disabling until underlying problem is addressed")
-
- for _, v := range []struct {
- name string
- nodes int
- services string
- }{
- {
- name: "defaults",
- },
- {
- name: "more nodes",
- nodes: defaultNodes + 4,
- },
- {
- name: "services",
- services: "stream,pss,zorglub",
- },
- {
- name: "services with bzz",
- services: "bzz,pss",
- },
- } {
- t.Run(v.name, func(t *testing.T) {
- t.Parallel()
-
- file, err := ioutil.TempFile("", "swarm-snapshot")
- if err != nil {
- t.Fatal(err)
- }
- defer os.Remove(file.Name())
-
- if err = file.Close(); err != nil {
- t.Error(err)
- }
-
- args := []string{"create"}
- if v.nodes > 0 {
- args = append(args, "--nodes", strconv.Itoa(v.nodes))
- }
- if v.services != "" {
- args = append(args, "--services", v.services)
- }
- testCmd := runSnapshot(t, append(args, file.Name())...)
-
- testCmd.WaitExit()
- if code := testCmd.ExitStatus(); code != 0 {
- t.Fatalf("command exit code %v, expected 0", code)
- }
-
- f, err := os.Open(file.Name())
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- err := f.Close()
- if err != nil {
- t.Error("closing snapshot file", "err", err)
- }
- }()
-
- b, err := ioutil.ReadAll(f)
- if err != nil {
- t.Fatal(err)
- }
- var snap simulations.Snapshot
- err = json.Unmarshal(b, &snap)
- if err != nil {
- t.Fatal(err)
- }
-
- wantNodes := v.nodes
- if wantNodes == 0 {
- wantNodes = defaultNodes
- }
- gotNodes := len(snap.Nodes)
- if gotNodes != wantNodes {
- t.Errorf("got %v nodes, want %v", gotNodes, wantNodes)
- }
-
- if len(snap.Conns) == 0 {
- t.Error("no connections in a snapshot")
- }
-
- var wantServices []string
- if v.services != "" {
- wantServices = strings.Split(v.services, ",")
- } else {
- wantServices = []string{"bzz"}
- }
- // sort service names so they can be comparable
- // as strings to every node sorted services
- sort.Strings(wantServices)
-
- for i, n := range snap.Nodes {
- gotServices := n.Node.Config.Services
- sort.Strings(gotServices)
- if fmt.Sprint(gotServices) != fmt.Sprint(wantServices) {
- t.Errorf("got services %v for node %v, want %v", gotServices, i, wantServices)
- }
- }
-
- })
- }
-}
diff --git a/cmd/swarm/swarm-snapshot/main.go b/cmd/swarm/swarm-snapshot/main.go
deleted file mode 100644
index 4a1e8b4f5..000000000
--- a/cmd/swarm/swarm-snapshot/main.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2018 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 (
- "os"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "github.com/ethereum/go-ethereum/log"
- cli "gopkg.in/urfave/cli.v1"
-)
-
-var gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
-var gitDate string
-
-// default value for "create" command --nodes flag
-const defaultNodes = 8
-
-func main() {
- err := newApp().Run(os.Args)
- if err != nil {
- log.Error(err.Error())
- os.Exit(1)
- }
-}
-
-// newApp construct a new instance of Swarm Snapshot Utility.
-// Method Run is called on it in the main function and in tests.
-func newApp() (app *cli.App) {
- app = utils.NewApp(gitCommit, gitDate, "Swarm Snapshot Utility")
-
- app.Name = "swarm-snapshot"
- app.Usage = ""
-
- // app flags (for all commands)
- app.Flags = []cli.Flag{
- cli.IntFlag{
- Name: "verbosity",
- Value: 1,
- Usage: "verbosity level",
- },
- }
-
- app.Commands = []cli.Command{
- {
- Name: "create",
- Aliases: []string{"c"},
- Usage: "create a swarm snapshot",
- Action: create,
- // Flags only for "create" command.
- // Allow app flags to be specified after the
- // command argument.
- Flags: append(app.Flags,
- cli.IntFlag{
- Name: "nodes",
- Value: defaultNodes,
- Usage: "number of nodes",
- },
- cli.StringFlag{
- Name: "services",
- Value: "bzz",
- Usage: "comma separated list of services to boot the nodes with",
- },
- ),
- },
- }
-
- return app
-}
diff --git a/cmd/swarm/swarm-snapshot/run_test.go b/cmd/swarm/swarm-snapshot/run_test.go
deleted file mode 100644
index d9a041597..000000000
--- a/cmd/swarm/swarm-snapshot/run_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2018 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 (
- "fmt"
- "os"
- "testing"
-
- "github.com/docker/docker/pkg/reexec"
- "github.com/ethereum/go-ethereum/internal/cmdtest"
-)
-
-func init() {
- reexec.Register("swarm-snapshot", func() {
- if err := newApp().Run(os.Args); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- os.Exit(0)
- })
-}
-
-func runSnapshot(t *testing.T, args ...string) *cmdtest.TestCmd {
- tt := cmdtest.NewTestCmd(t, nil)
- tt.Run("swarm-snapshot", args...)
- return tt
-}
-
-func TestMain(m *testing.M) {
- if reexec.Init() {
- return
- }
- os.Exit(m.Run())
-}
diff --git a/cmd/swarm/testdata/datastore_fixture.go b/cmd/swarm/testdata/datastore_fixture.go
deleted file mode 100644
index 6a147a6a4..000000000
--- a/cmd/swarm/testdata/datastore_fixture.go
+++ /dev/null
@@ -1,1390 +0,0 @@
-package testdata
-
-const DATADIR_MIGRATION_FIXTURE = `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`
diff --git a/cmd/swarm/upload.go b/cmd/swarm/upload.go
deleted file mode 100644
index ab0790835..000000000
--- a/cmd/swarm/upload.go
+++ /dev/null
@@ -1,188 +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/>.
-
-// Command bzzup uploads files to the swarm HTTP API.
-package main
-
-import (
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/user"
- "path"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/ethereum/go-ethereum/log"
- swarm "github.com/ethereum/go-ethereum/swarm/api/client"
-
- "github.com/ethereum/go-ethereum/cmd/utils"
- "gopkg.in/urfave/cli.v1"
-)
-
-var upCommand = cli.Command{
- Action: upload,
- CustomHelpTemplate: helpTemplate,
- Name: "up",
- Usage: "uploads a file or directory to swarm using the HTTP API",
- ArgsUsage: "<file>",
- Flags: []cli.Flag{SwarmEncryptedFlag},
- Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
-}
-
-func upload(ctx *cli.Context) {
- args := ctx.Args()
- var (
- bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
- recursive = ctx.GlobalBool(SwarmRecursiveFlag.Name)
- wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
- defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name)
- fromStdin = ctx.GlobalBool(SwarmUpFromStdinFlag.Name)
- mimeType = ctx.GlobalString(SwarmUploadMimeType.Name)
- client = swarm.NewClient(bzzapi)
- toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name)
- autoDefaultPath = false
- file string
- )
- if autoDefaultPathString := os.Getenv(SwarmAutoDefaultPath); autoDefaultPathString != "" {
- b, err := strconv.ParseBool(autoDefaultPathString)
- if err != nil {
- utils.Fatalf("invalid environment variable %s: %v", SwarmAutoDefaultPath, err)
- }
- autoDefaultPath = b
- }
- if len(args) != 1 {
- if fromStdin {
- tmp, err := ioutil.TempFile("", "swarm-stdin")
- if err != nil {
- utils.Fatalf("error create tempfile: %s", err)
- }
- defer os.Remove(tmp.Name())
- n, err := io.Copy(tmp, os.Stdin)
- if err != nil {
- utils.Fatalf("error copying stdin to tempfile: %s", err)
- } else if n == 0 {
- utils.Fatalf("error reading from stdin: zero length")
- }
- file = tmp.Name()
- } else {
- utils.Fatalf("Need filename as the first and only argument")
- }
- } else {
- file = expandPath(args[0])
- }
-
- if !wantManifest {
- f, err := swarm.Open(file)
- if err != nil {
- utils.Fatalf("Error opening file: %s", err)
- }
- defer f.Close()
- hash, err := client.UploadRaw(f, f.Size, toEncrypt)
- if err != nil {
- utils.Fatalf("Upload failed: %s", err)
- }
- fmt.Println(hash)
- return
- }
-
- stat, err := os.Stat(file)
- if err != nil {
- utils.Fatalf("Error opening file: %s", err)
- }
-
- // define a function which either uploads a directory or single file
- // based on the type of the file being uploaded
- var doUpload func() (hash string, err error)
- if stat.IsDir() {
- doUpload = func() (string, error) {
- if !recursive {
- return "", errors.New("Argument is a directory and recursive upload is disabled")
- }
- if autoDefaultPath && defaultPath == "" {
- defaultEntryCandidate := path.Join(file, "index.html")
- log.Debug("trying to find default path", "path", defaultEntryCandidate)
- defaultEntryStat, err := os.Stat(defaultEntryCandidate)
- if err == nil && !defaultEntryStat.IsDir() {
- log.Debug("setting auto detected default path", "path", defaultEntryCandidate)
- defaultPath = defaultEntryCandidate
- }
- }
- if defaultPath != "" {
- // construct absolute default path
- absDefaultPath, _ := filepath.Abs(defaultPath)
- absFile, _ := filepath.Abs(file)
- // make sure absolute directory ends with only one "/"
- // to trim it from absolute default path and get relative default path
- absFile = strings.TrimRight(absFile, "/") + "/"
- if absDefaultPath != "" && absFile != "" && strings.HasPrefix(absDefaultPath, absFile) {
- defaultPath = strings.TrimPrefix(absDefaultPath, absFile)
- }
- }
- return client.UploadDirectory(file, defaultPath, "", toEncrypt)
- }
- } else {
- doUpload = func() (string, error) {
- f, err := swarm.Open(file)
- if err != nil {
- return "", fmt.Errorf("error opening file: %s", err)
- }
- defer f.Close()
- if mimeType != "" {
- f.ContentType = mimeType
- }
- return client.Upload(f, "", toEncrypt)
- }
- }
- hash, err := doUpload()
- if err != nil {
- utils.Fatalf("Upload failed: %s", err)
- }
- fmt.Println(hash)
-}
-
-// Expands a file path
-// 1. replace tilde with users home dir
-// 2. expands embedded environment variables
-// 3. cleans the path, e.g. /a/b/../c -> /a/c
-// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
-func expandPath(p string) string {
- if i := strings.Index(p, ":"); i > 0 {
- return p
- }
- if i := strings.Index(p, "@"); i > 0 {
- return p
- }
- if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
- if home := homeDir(); home != "" {
- p = home + p[1:]
- }
- }
- return path.Clean(os.ExpandEnv(p))
-}
-
-func homeDir() string {
- if home := os.Getenv("HOME"); home != "" {
- return home
- }
- if usr, err := user.Current(); err == nil {
- return usr.HomeDir
- }
- return ""
-}
diff --git a/cmd/swarm/upload_test.go b/cmd/swarm/upload_test.go
deleted file mode 100644
index 356424c66..000000000
--- a/cmd/swarm/upload_test.go
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright 2017 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 (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- swarmapi "github.com/ethereum/go-ethereum/swarm/api/client"
- "github.com/ethereum/go-ethereum/swarm/testutil"
- "github.com/mattn/go-colorable"
-)
-
-func init() {
- log.PrintOrigins(true)
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
-}
-
-func TestSwarmUp(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip()
- }
-
- cluster := newTestCluster(t, clusterSize)
- defer cluster.Shutdown()
-
- cases := []struct {
- name string
- f func(t *testing.T, cluster *testCluster)
- }{
- {"NoEncryption", testNoEncryption},
- {"Encrypted", testEncrypted},
- {"RecursiveNoEncryption", testRecursiveNoEncryption},
- {"RecursiveEncrypted", testRecursiveEncrypted},
- {"DefaultPathAll", testDefaultPathAll},
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- tc.f(t, cluster)
- })
- }
-}
-
-// testNoEncryption tests that running 'swarm up' makes the resulting file
-// available from all nodes via the HTTP API
-func testNoEncryption(t *testing.T, cluster *testCluster) {
- testDefault(t, cluster, false)
-}
-
-// testEncrypted tests that running 'swarm up --encrypted' makes the resulting file
-// available from all nodes via the HTTP API
-func testEncrypted(t *testing.T, cluster *testCluster) {
- testDefault(t, cluster, true)
-}
-
-func testRecursiveNoEncryption(t *testing.T, cluster *testCluster) {
- testRecursive(t, cluster, false)
-}
-
-func testRecursiveEncrypted(t *testing.T, cluster *testCluster) {
- testRecursive(t, cluster, true)
-}
-
-func testDefault(t *testing.T, cluster *testCluster, toEncrypt bool) {
- tmpFileName := testutil.TempFileWithContent(t, data)
- defer os.Remove(tmpFileName)
-
- // write data to file
- hashRegexp := `[a-f\d]{64}`
- flags := []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "up",
- tmpFileName}
- if toEncrypt {
- hashRegexp = `[a-f\d]{128}`
- flags = []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "up",
- "--encrypt",
- tmpFileName}
- }
- // upload the file with 'swarm up' and expect a hash
- log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
- up := runSwarm(t, flags...)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
- hash := matches[0]
- log.Info("file uploaded", "hash", hash)
-
- // get the file from the HTTP API of each node
- for _, node := range cluster.Nodes {
- log.Info("getting file from node", "node", node.Name)
-
- res, err := http.Get(node.URL + "/bzz:/" + hash)
- if err != nil {
- t.Fatal(err)
- }
- defer res.Body.Close()
-
- reply, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(err)
- }
- if res.StatusCode != 200 {
- t.Fatalf("expected HTTP status 200, got %s", res.Status)
- }
- if string(reply) != data {
- t.Fatalf("expected HTTP body %q, got %q", data, reply)
- }
- log.Debug("verifying uploaded file using `swarm down`")
- //try to get the content with `swarm down`
- tmpDownload, err := ioutil.TempDir("", "swarm-test")
- tmpDownload = path.Join(tmpDownload, "tmpfile.tmp")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDownload)
-
- bzzLocator := "bzz:/" + hash
- flags = []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "down",
- bzzLocator,
- tmpDownload,
- }
-
- down := runSwarm(t, flags...)
- down.ExpectExit()
-
- fi, err := os.Stat(tmpDownload)
- if err != nil {
- t.Fatalf("could not stat path: %v", err)
- }
-
- switch mode := fi.Mode(); {
- case mode.IsRegular():
- downloadedBytes, err := ioutil.ReadFile(tmpDownload)
- if err != nil {
- t.Fatalf("had an error reading the downloaded file: %v", err)
- }
- if !bytes.Equal(downloadedBytes, bytes.NewBufferString(data).Bytes()) {
- t.Fatalf("retrieved data and posted data not equal!")
- }
-
- default:
- t.Fatalf("expected to download regular file, got %s", fi.Mode())
- }
- }
-
- timeout := time.Duration(2 * time.Second)
- httpClient := http.Client{
- Timeout: timeout,
- }
-
- // try to squeeze a timeout by getting an non-existent hash from each node
- for _, node := range cluster.Nodes {
- _, err := httpClient.Get(node.URL + "/bzz:/1023e8bae0f70be7d7b5f74343088ba408a218254391490c85ae16278e230340")
- // we're speeding up the timeout here since netstore has a 60 seconds timeout on a request
- if err != nil && !strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") {
- t.Fatal(err)
- }
- // this is disabled since it takes 60s due to netstore timeout
- // if res.StatusCode != 404 {
- // t.Fatalf("expected HTTP status 404, got %s", res.Status)
- // }
- }
-}
-
-func testRecursive(t *testing.T, cluster *testCluster, toEncrypt bool) {
- tmpUploadDir, err := ioutil.TempDir("", "swarm-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpUploadDir)
- // create tmp files
- for _, path := range []string{"tmp1", "tmp2"} {
- if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
- t.Fatal(err)
- }
- }
-
- hashRegexp := `[a-f\d]{64}`
- flags := []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "--recursive",
- "up",
- tmpUploadDir}
- if toEncrypt {
- hashRegexp = `[a-f\d]{128}`
- flags = []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "--recursive",
- "up",
- "--encrypt",
- tmpUploadDir}
- }
- // upload the file with 'swarm up' and expect a hash
- log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
- up := runSwarm(t, flags...)
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
- hash := matches[0]
- log.Info("dir uploaded", "hash", hash)
-
- // get the file from the HTTP API of each node
- for _, node := range cluster.Nodes {
- log.Info("getting file from node", "node", node.Name)
- //try to get the content with `swarm down`
- tmpDownload, err := ioutil.TempDir("", "swarm-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpDownload)
- bzzLocator := "bzz:/" + hash
- flagss := []string{
- "--bzzapi", cluster.Nodes[0].URL,
- "down",
- "--recursive",
- bzzLocator,
- tmpDownload,
- }
-
- fmt.Println("downloading from swarm with recursive")
- down := runSwarm(t, flagss...)
- down.ExpectExit()
-
- files, err := ioutil.ReadDir(tmpDownload)
- for _, v := range files {
- fi, err := os.Stat(path.Join(tmpDownload, v.Name()))
- if err != nil {
- t.Fatalf("got an error: %v", err)
- }
-
- switch mode := fi.Mode(); {
- case mode.IsRegular():
- if file, err := swarmapi.Open(path.Join(tmpDownload, v.Name())); err != nil {
- t.Fatalf("encountered an error opening the file returned from the CLI: %v", err)
- } else {
- ff := make([]byte, len(data))
- io.ReadFull(file, ff)
- buf := bytes.NewBufferString(data)
-
- if !bytes.Equal(ff, buf.Bytes()) {
- t.Fatalf("retrieved data and posted data not equal!")
- }
- }
- default:
- t.Fatalf("this shouldnt happen")
- }
- }
- if err != nil {
- t.Fatalf("could not list files at: %v", files)
- }
- }
-}
-
-// testDefaultPathAll tests swarm recursive upload with relative and absolute
-// default paths and with encryption.
-func testDefaultPathAll(t *testing.T, cluster *testCluster) {
- testDefaultPath(t, cluster, false, false)
- testDefaultPath(t, cluster, false, true)
- testDefaultPath(t, cluster, true, false)
- testDefaultPath(t, cluster, true, true)
-}
-
-func testDefaultPath(t *testing.T, cluster *testCluster, toEncrypt bool, absDefaultPath bool) {
- tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
-
- err = ioutil.WriteFile(filepath.Join(tmp, "index.html"), []byte("<h1>Test</h1>"), 0666)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(tmp, "robots.txt"), []byte("Disallow: /"), 0666)
- if err != nil {
- t.Fatal(err)
- }
-
- defaultPath := "index.html"
- if absDefaultPath {
- defaultPath = filepath.Join(tmp, defaultPath)
- }
-
- args := []string{
- "--bzzapi",
- cluster.Nodes[0].URL,
- "--recursive",
- "--defaultpath",
- defaultPath,
- "up",
- tmp,
- }
- if toEncrypt {
- args = append(args, "--encrypt")
- }
-
- up := runSwarm(t, args...)
- hashRegexp := `[a-f\d]{64,128}`
- _, matches := up.ExpectRegexp(hashRegexp)
- up.ExpectExit()
- hash := matches[0]
-
- client := swarmapi.NewClient(cluster.Nodes[0].URL)
-
- m, isEncrypted, err := client.DownloadManifest(hash)
- if err != nil {
- t.Fatal(err)
- }
-
- if toEncrypt != isEncrypted {
- t.Error("downloaded manifest is not encrypted")
- }
-
- var found bool
- var entriesCount int
- for _, e := range m.Entries {
- entriesCount++
- if e.Path == "" {
- found = true
- }
- }
-
- if !found {
- t.Error("manifest default entry was not found")
- }
-
- if entriesCount != 3 {
- t.Errorf("manifest contains %v entries, expected %v", entriesCount, 3)
- }
-}