manifest_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "bytes"
  19. "io/ioutil"
  20. "os"
  21. "path/filepath"
  22. "runtime"
  23. "testing"
  24. "github.com/ethereum/go-ethereum/swarm/api"
  25. swarm "github.com/ethereum/go-ethereum/swarm/api/client"
  26. )
  27. // TestManifestChange tests manifest add, update and remove
  28. // cli commands without encryption.
  29. func TestManifestChange(t *testing.T) {
  30. if runtime.GOOS == "windows" {
  31. t.Skip()
  32. }
  33. testManifestChange(t, false)
  34. }
  35. // TestManifestChange tests manifest add, update and remove
  36. // cli commands with encryption enabled.
  37. func TestManifestChangeEncrypted(t *testing.T) {
  38. if runtime.GOOS == "windows" {
  39. t.Skip()
  40. }
  41. testManifestChange(t, true)
  42. }
  43. // testManifestChange performs cli commands:
  44. // - manifest add
  45. // - manifest update
  46. // - manifest remove
  47. // on a manifest, testing the functionality of this
  48. // comands on paths that are in root manifest or a nested one.
  49. // Argument encrypt controls whether to use encryption or not.
  50. func testManifestChange(t *testing.T, encrypt bool) {
  51. t.Parallel()
  52. cluster := newTestCluster(t, 1)
  53. defer cluster.Shutdown()
  54. tmp, err := ioutil.TempDir("", "swarm-manifest-test")
  55. if err != nil {
  56. t.Fatal(err)
  57. }
  58. defer os.RemoveAll(tmp)
  59. origDir := filepath.Join(tmp, "orig")
  60. if err := os.Mkdir(origDir, 0777); err != nil {
  61. t.Fatal(err)
  62. }
  63. indexDataFilename := filepath.Join(origDir, "index.html")
  64. err = ioutil.WriteFile(indexDataFilename, []byte("<h1>Test</h1>"), 0666)
  65. if err != nil {
  66. t.Fatal(err)
  67. }
  68. // Files paths robots.txt and robots.html share the same prefix "robots."
  69. // which will result a manifest with a nested manifest under path "robots.".
  70. // This will allow testing manifest changes on both root and nested manifest.
  71. err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666)
  72. if err != nil {
  73. t.Fatal(err)
  74. }
  75. err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("<strong>No Robots Allowed</strong>"), 0666)
  76. if err != nil {
  77. t.Fatal(err)
  78. }
  79. err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666)
  80. if err != nil {
  81. t.Fatal(err)
  82. }
  83. args := []string{
  84. "--bzzapi",
  85. cluster.Nodes[0].URL,
  86. "--recursive",
  87. "--defaultpath",
  88. indexDataFilename,
  89. "up",
  90. origDir,
  91. }
  92. if encrypt {
  93. args = append(args, "--encrypt")
  94. }
  95. origManifestHash := runSwarmExpectHash(t, args...)
  96. checkHashLength(t, origManifestHash, encrypt)
  97. client := swarm.NewClient(cluster.Nodes[0].URL)
  98. // upload a new file and use its manifest to add it the original manifest.
  99. t.Run("add", func(t *testing.T) {
  100. humansData := []byte("Ann\nBob")
  101. humansDataFilename := filepath.Join(tmp, "humans.txt")
  102. err = ioutil.WriteFile(humansDataFilename, humansData, 0666)
  103. if err != nil {
  104. t.Fatal(err)
  105. }
  106. humansManifestHash := runSwarmExpectHash(t,
  107. "--bzzapi",
  108. cluster.Nodes[0].URL,
  109. "up",
  110. humansDataFilename,
  111. )
  112. newManifestHash := runSwarmExpectHash(t,
  113. "--bzzapi",
  114. cluster.Nodes[0].URL,
  115. "manifest",
  116. "add",
  117. origManifestHash,
  118. "humans.txt",
  119. humansManifestHash,
  120. )
  121. checkHashLength(t, newManifestHash, encrypt)
  122. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  123. var found bool
  124. for _, e := range newManifest.Entries {
  125. if e.Path == "humans.txt" {
  126. found = true
  127. if e.Size != int64(len(humansData)) {
  128. t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size)
  129. }
  130. if e.ModTime.IsZero() {
  131. t.Errorf("got zero mod time for humans.txt")
  132. }
  133. ct := "text/plain; charset=utf-8"
  134. if e.ContentType != ct {
  135. t.Errorf("expected content type %q, got %q", ct, e.ContentType)
  136. }
  137. break
  138. }
  139. }
  140. if !found {
  141. t.Fatal("no humans.txt in new manifest")
  142. }
  143. checkFile(t, client, newManifestHash, "humans.txt", humansData)
  144. })
  145. // upload a new file and use its manifest to add it the original manifest,
  146. // but ensure that the file will be in the nested manifest of the original one.
  147. t.Run("add nested", func(t *testing.T) {
  148. robotsData := []byte(`{"disallow": "/"}`)
  149. robotsDataFilename := filepath.Join(tmp, "robots.json")
  150. err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
  151. if err != nil {
  152. t.Fatal(err)
  153. }
  154. robotsManifestHash := runSwarmExpectHash(t,
  155. "--bzzapi",
  156. cluster.Nodes[0].URL,
  157. "up",
  158. robotsDataFilename,
  159. )
  160. newManifestHash := runSwarmExpectHash(t,
  161. "--bzzapi",
  162. cluster.Nodes[0].URL,
  163. "manifest",
  164. "add",
  165. origManifestHash,
  166. "robots.json",
  167. robotsManifestHash,
  168. )
  169. checkHashLength(t, newManifestHash, encrypt)
  170. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  171. var found bool
  172. loop:
  173. for _, e := range newManifest.Entries {
  174. if e.Path == "robots." {
  175. nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
  176. for _, e := range nestedManifest.Entries {
  177. if e.Path == "json" {
  178. found = true
  179. if e.Size != int64(len(robotsData)) {
  180. t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size)
  181. }
  182. if e.ModTime.IsZero() {
  183. t.Errorf("got zero mod time for robots.json")
  184. }
  185. ct := "application/json"
  186. if e.ContentType != ct {
  187. t.Errorf("expected content type %q, got %q", ct, e.ContentType)
  188. }
  189. break loop
  190. }
  191. }
  192. }
  193. }
  194. if !found {
  195. t.Fatal("no robots.json in new manifest")
  196. }
  197. checkFile(t, client, newManifestHash, "robots.json", robotsData)
  198. })
  199. // upload a new file and use its manifest to change the file it the original manifest.
  200. t.Run("update", func(t *testing.T) {
  201. indexData := []byte("<h1>Ethereum Swarm</h1>")
  202. indexDataFilename := filepath.Join(tmp, "index.html")
  203. err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
  204. if err != nil {
  205. t.Fatal(err)
  206. }
  207. indexManifestHash := runSwarmExpectHash(t,
  208. "--bzzapi",
  209. cluster.Nodes[0].URL,
  210. "up",
  211. indexDataFilename,
  212. )
  213. newManifestHash := runSwarmExpectHash(t,
  214. "--bzzapi",
  215. cluster.Nodes[0].URL,
  216. "manifest",
  217. "update",
  218. origManifestHash,
  219. "index.html",
  220. indexManifestHash,
  221. )
  222. checkHashLength(t, newManifestHash, encrypt)
  223. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  224. var found bool
  225. for _, e := range newManifest.Entries {
  226. if e.Path == "index.html" {
  227. found = true
  228. if e.Size != int64(len(indexData)) {
  229. t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size)
  230. }
  231. if e.ModTime.IsZero() {
  232. t.Errorf("got zero mod time for index.html")
  233. }
  234. ct := "text/html; charset=utf-8"
  235. if e.ContentType != ct {
  236. t.Errorf("expected content type %q, got %q", ct, e.ContentType)
  237. }
  238. break
  239. }
  240. }
  241. if !found {
  242. t.Fatal("no index.html in new manifest")
  243. }
  244. checkFile(t, client, newManifestHash, "index.html", indexData)
  245. // check default entry change
  246. checkFile(t, client, newManifestHash, "", indexData)
  247. })
  248. // upload a new file and use its manifest to change the file it the original manifest,
  249. // but ensure that the file is in the nested manifest of the original one.
  250. t.Run("update nested", func(t *testing.T) {
  251. robotsData := []byte(`<string>Only humans allowed!!!</strong>`)
  252. robotsDataFilename := filepath.Join(tmp, "robots.html")
  253. err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
  254. if err != nil {
  255. t.Fatal(err)
  256. }
  257. humansManifestHash := runSwarmExpectHash(t,
  258. "--bzzapi",
  259. cluster.Nodes[0].URL,
  260. "up",
  261. robotsDataFilename,
  262. )
  263. newManifestHash := runSwarmExpectHash(t,
  264. "--bzzapi",
  265. cluster.Nodes[0].URL,
  266. "manifest",
  267. "update",
  268. origManifestHash,
  269. "robots.html",
  270. humansManifestHash,
  271. )
  272. checkHashLength(t, newManifestHash, encrypt)
  273. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  274. var found bool
  275. loop:
  276. for _, e := range newManifest.Entries {
  277. if e.Path == "robots." {
  278. nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
  279. for _, e := range nestedManifest.Entries {
  280. if e.Path == "html" {
  281. found = true
  282. if e.Size != int64(len(robotsData)) {
  283. t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size)
  284. }
  285. if e.ModTime.IsZero() {
  286. t.Errorf("got zero mod time for robots.html")
  287. }
  288. ct := "text/html; charset=utf-8"
  289. if e.ContentType != ct {
  290. t.Errorf("expected content type %q, got %q", ct, e.ContentType)
  291. }
  292. break loop
  293. }
  294. }
  295. }
  296. }
  297. if !found {
  298. t.Fatal("no robots.html in new manifest")
  299. }
  300. checkFile(t, client, newManifestHash, "robots.html", robotsData)
  301. })
  302. // remove a file from the manifest.
  303. t.Run("remove", func(t *testing.T) {
  304. newManifestHash := runSwarmExpectHash(t,
  305. "--bzzapi",
  306. cluster.Nodes[0].URL,
  307. "manifest",
  308. "remove",
  309. origManifestHash,
  310. "mutants.txt",
  311. )
  312. checkHashLength(t, newManifestHash, encrypt)
  313. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  314. var found bool
  315. for _, e := range newManifest.Entries {
  316. if e.Path == "mutants.txt" {
  317. found = true
  318. break
  319. }
  320. }
  321. if found {
  322. t.Fatal("mutants.txt is not removed")
  323. }
  324. })
  325. // remove a file from the manifest, but ensure that the file is in
  326. // the nested manifest of the original one.
  327. t.Run("remove nested", func(t *testing.T) {
  328. newManifestHash := runSwarmExpectHash(t,
  329. "--bzzapi",
  330. cluster.Nodes[0].URL,
  331. "manifest",
  332. "remove",
  333. origManifestHash,
  334. "robots.html",
  335. )
  336. checkHashLength(t, newManifestHash, encrypt)
  337. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  338. var found bool
  339. loop:
  340. for _, e := range newManifest.Entries {
  341. if e.Path == "robots." {
  342. nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
  343. for _, e := range nestedManifest.Entries {
  344. if e.Path == "html" {
  345. found = true
  346. break loop
  347. }
  348. }
  349. }
  350. }
  351. if found {
  352. t.Fatal("robots.html in not removed")
  353. }
  354. })
  355. }
  356. // TestNestedDefaultEntryUpdate tests if the default entry is updated
  357. // if the file in nested manifest used for it is also updated.
  358. func TestNestedDefaultEntryUpdate(t *testing.T) {
  359. if runtime.GOOS == "windows" {
  360. t.Skip()
  361. }
  362. testNestedDefaultEntryUpdate(t, false)
  363. }
  364. // TestNestedDefaultEntryUpdateEncrypted tests if the default entry
  365. // of encrypted upload is updated if the file in nested manifest
  366. // used for it is also updated.
  367. func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
  368. if runtime.GOOS == "windows" {
  369. t.Skip()
  370. }
  371. testNestedDefaultEntryUpdate(t, true)
  372. }
  373. func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
  374. t.Parallel()
  375. cluster := newTestCluster(t, 1)
  376. defer cluster.Shutdown()
  377. tmp, err := ioutil.TempDir("", "swarm-manifest-test")
  378. if err != nil {
  379. t.Fatal(err)
  380. }
  381. defer os.RemoveAll(tmp)
  382. origDir := filepath.Join(tmp, "orig")
  383. if err := os.Mkdir(origDir, 0777); err != nil {
  384. t.Fatal(err)
  385. }
  386. indexData := []byte("<h1>Test</h1>")
  387. indexDataFilename := filepath.Join(origDir, "index.html")
  388. err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
  389. if err != nil {
  390. t.Fatal(err)
  391. }
  392. // Add another file with common prefix as the default entry to test updates of
  393. // default entry with nested manifests.
  394. err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666)
  395. if err != nil {
  396. t.Fatal(err)
  397. }
  398. args := []string{
  399. "--bzzapi",
  400. cluster.Nodes[0].URL,
  401. "--recursive",
  402. "--defaultpath",
  403. indexDataFilename,
  404. "up",
  405. origDir,
  406. }
  407. if encrypt {
  408. args = append(args, "--encrypt")
  409. }
  410. origManifestHash := runSwarmExpectHash(t, args...)
  411. checkHashLength(t, origManifestHash, encrypt)
  412. client := swarm.NewClient(cluster.Nodes[0].URL)
  413. newIndexData := []byte("<h1>Ethereum Swarm</h1>")
  414. newIndexDataFilename := filepath.Join(tmp, "index.html")
  415. err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666)
  416. if err != nil {
  417. t.Fatal(err)
  418. }
  419. newIndexManifestHash := runSwarmExpectHash(t,
  420. "--bzzapi",
  421. cluster.Nodes[0].URL,
  422. "up",
  423. newIndexDataFilename,
  424. )
  425. newManifestHash := runSwarmExpectHash(t,
  426. "--bzzapi",
  427. cluster.Nodes[0].URL,
  428. "manifest",
  429. "update",
  430. origManifestHash,
  431. "index.html",
  432. newIndexManifestHash,
  433. )
  434. checkHashLength(t, newManifestHash, encrypt)
  435. newManifest := downloadManifest(t, client, newManifestHash, encrypt)
  436. var found bool
  437. for _, e := range newManifest.Entries {
  438. if e.Path == "index." {
  439. found = true
  440. newManifest = downloadManifest(t, client, e.Hash, encrypt)
  441. break
  442. }
  443. }
  444. if !found {
  445. t.Fatal("no index. path in new manifest")
  446. }
  447. found = false
  448. for _, e := range newManifest.Entries {
  449. if e.Path == "html" {
  450. found = true
  451. if e.Size != int64(len(newIndexData)) {
  452. t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size)
  453. }
  454. if e.ModTime.IsZero() {
  455. t.Errorf("got zero mod time for index.html")
  456. }
  457. ct := "text/html; charset=utf-8"
  458. if e.ContentType != ct {
  459. t.Errorf("expected content type %q, got %q", ct, e.ContentType)
  460. }
  461. break
  462. }
  463. }
  464. if !found {
  465. t.Fatal("no html in new manifest")
  466. }
  467. checkFile(t, client, newManifestHash, "index.html", newIndexData)
  468. // check default entry change
  469. checkFile(t, client, newManifestHash, "", newIndexData)
  470. }
  471. func runSwarmExpectHash(t *testing.T, args ...string) (hash string) {
  472. t.Helper()
  473. hashRegexp := `[a-f\d]{64,128}`
  474. up := runSwarm(t, args...)
  475. _, matches := up.ExpectRegexp(hashRegexp)
  476. up.ExpectExit()
  477. if len(matches) < 1 {
  478. t.Fatal("no matches found")
  479. }
  480. return matches[0]
  481. }
  482. func checkHashLength(t *testing.T, hash string, encrypted bool) {
  483. t.Helper()
  484. l := len(hash)
  485. if encrypted && l != 128 {
  486. t.Errorf("expected hash length 128, got %v", l)
  487. }
  488. if !encrypted && l != 64 {
  489. t.Errorf("expected hash length 64, got %v", l)
  490. }
  491. }
  492. func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) {
  493. t.Helper()
  494. m, isEncrypted, err := client.DownloadManifest(hash)
  495. if err != nil {
  496. t.Fatal(err)
  497. }
  498. if encrypted != isEncrypted {
  499. t.Error("new manifest encryption flag is not correct")
  500. }
  501. return m
  502. }
  503. func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) {
  504. t.Helper()
  505. f, err := client.Download(hash, path)
  506. if err != nil {
  507. t.Fatal(err)
  508. }
  509. got, err := ioutil.ReadAll(f)
  510. if err != nil {
  511. t.Fatal(err)
  512. }
  513. if !bytes.Equal(got, expected) {
  514. t.Errorf("expected file content %q, got %q", expected, got)
  515. }
  516. }