From 46d5ba723d5e9371b1ff4b481572e4efd1723602 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Mon, 30 Jul 2018 18:09:54 +0200 Subject: [PATCH] cmd/swarm: manifest update: update default entry for non-encrypted uploads --- cmd/swarm/manifest.go | 35 ++++++++-- cmd/swarm/manifest_test.go | 135 ++++++++++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 8 deletions(-) diff --git a/cmd/swarm/manifest.go b/cmd/swarm/manifest.go index 7557cda9b4..0216ffc1dd 100644 --- a/cmd/swarm/manifest.go +++ b/cmd/swarm/manifest.go @@ -19,6 +19,7 @@ package main import ( "fmt" + "os" "strings" "github.com/ethereum/go-ethereum/cmd/utils" @@ -91,7 +92,13 @@ func manifestUpdate(ctx *cli.Context) { utils.Fatalf("Too many entries in manifest %s", hash) } - newManifest := updateEntryInManifest(client, mhash, path, m.Entries[0]) + 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) } @@ -165,7 +172,13 @@ func addEntryToManifest(client *swarm.Client, mhash, path string, entry api.Mani return newManifestHash } -func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry) string { +// 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{} @@ -180,6 +193,9 @@ func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.M 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) @@ -197,7 +213,8 @@ func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.M if longestPathEntry.Path != "" { // Load the child Manifest add the entry there newPath := path[len(longestPathEntry.Path):] - newHash := updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry) + var newHash string + newHash, oldHash, _ = updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry, false) // Replace the hash for parent Manifests newMRoot := &api.Manifest{} @@ -211,13 +228,19 @@ func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.M mroot = newMRoot } - if newEntry.Path != "" { + // 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) } @@ -225,11 +248,11 @@ func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.M mroot = newMRoot } - newManifestHash, err := client.UploadManifest(mroot, isEncrypted) + newManifestHash, err = client.UploadManifest(mroot, isEncrypted) if err != nil { utils.Fatalf("Manifest upload failed: %v", err) } - return newManifestHash + return newManifestHash, oldHash, defaultEntryUpdated } func removeEntryFromManifest(client *swarm.Client, mhash, path string) string { diff --git a/cmd/swarm/manifest_test.go b/cmd/swarm/manifest_test.go index b99a81e112..43e8fc2414 100644 --- a/cmd/swarm/manifest_test.go +++ b/cmd/swarm/manifest_test.go @@ -226,7 +226,7 @@ func testManifestChange(t *testing.T, encrypt bool) { t.Fatal(err) } - humansManifestHash := runSwarmExpectHash(t, + indexManifestHash := runSwarmExpectHash(t, "--bzzapi", cluster.Nodes[0].URL, "up", @@ -240,7 +240,7 @@ func testManifestChange(t *testing.T, encrypt bool) { "update", origManifestHash, "index.html", - humansManifestHash, + indexManifestHash, ) checkHashLength(t, newManifestHash, encrypt) @@ -269,6 +269,14 @@ func testManifestChange(t *testing.T, encrypt bool) { } checkFile(t, client, newManifestHash, "index.html", indexData) + + // check default entry change + // TODO: encrypted uploads generate manifest with default entry + // with different hash compared to the same file that is used + // for default entry. + if !encrypt { + checkFile(t, client, newManifestHash, "", indexData) + } }) // upload a new file and use its manifest to change the file it the original manifest, @@ -394,6 +402,129 @@ func testManifestChange(t *testing.T, encrypt bool) { }) } +// TestNestedDefaultEntryUpdate tests if the default entry is updated +// if the file in nested manifest used for it is also updated. +// TODO: create a similar test for encrypted uploads when it becomes +// functional. +func TestNestedDefaultEntryUpdate(t *testing.T) { + testNestedDefaultEntryUpdate(t, false) +} + +func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) { + t.Parallel() + cluster := newTestCluster(t, 1) + defer cluster.Shutdown() + + 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("

Test

") + 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", + cluster.Nodes[0].URL, + "--recursive", + "--defaultpath", + indexDataFilename, + "up", + origDir, + } + if encrypt { + args = append(args, "--encrypt") + } + + origManifestHash := runSwarmExpectHash(t, args...) + + checkHashLength(t, origManifestHash, encrypt) + + client := swarm.NewClient(cluster.Nodes[0].URL) + + newIndexData := []byte("

Ethereum Swarm

") + newIndexDataFilename := filepath.Join(tmp, "index.html") + err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666) + if err != nil { + t.Fatal(err) + } + + newIndexManifestHash := runSwarmExpectHash(t, + "--bzzapi", + cluster.Nodes[0].URL, + "up", + newIndexDataFilename, + ) + + newManifestHash := runSwarmExpectHash(t, + "--bzzapi", + cluster.Nodes[0].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}`