freezer_table_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser 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. // The go-ethereum library 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 Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package rawdb
  17. import (
  18. "bytes"
  19. "fmt"
  20. "github.com/ethereum/go-ethereum/metrics"
  21. "math/rand"
  22. "os"
  23. "path/filepath"
  24. "testing"
  25. "time"
  26. )
  27. func init() {
  28. rand.Seed(time.Now().Unix())
  29. }
  30. // Gets a chunk of data, filled with 'b'
  31. func getChunk(size int, b byte) []byte {
  32. data := make([]byte, size)
  33. for i, _ := range data {
  34. data[i] = b
  35. }
  36. return data
  37. }
  38. func print(t *testing.T, f *freezerTable, item uint64) {
  39. a, err := f.Retrieve(item)
  40. if err != nil {
  41. t.Fatal(err)
  42. }
  43. fmt.Printf("db[%d] = %x\n", item, a)
  44. }
  45. // TestFreezerBasics test initializing a freezertable from scratch, writing to the table,
  46. // and reading it back.
  47. func TestFreezerBasics(t *testing.T) {
  48. t.Parallel()
  49. // set cutoff at 50 bytes
  50. f, err := newCustomTable(os.TempDir(),
  51. fmt.Sprintf("unittest-%d", rand.Uint64()),
  52. metrics.NewMeter(), metrics.NewMeter(), 50, true)
  53. if err != nil {
  54. t.Fatal(err)
  55. }
  56. defer f.Close()
  57. // Write 15 bytes 255 times, results in 85 files
  58. for x := byte(0); x < 255; x++ {
  59. data := getChunk(15, x)
  60. f.Append(uint64(x), data)
  61. }
  62. //print(t, f, 0)
  63. //print(t, f, 1)
  64. //print(t, f, 2)
  65. //
  66. //db[0] = 000000000000000000000000000000
  67. //db[1] = 010101010101010101010101010101
  68. //db[2] = 020202020202020202020202020202
  69. for y := byte(0); y < 255; y++ {
  70. exp := getChunk(15, y)
  71. got, err := f.Retrieve(uint64(y))
  72. if err != nil {
  73. t.Fatal(err)
  74. }
  75. if !bytes.Equal(got, exp) {
  76. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  77. }
  78. }
  79. }
  80. // TestFreezerBasicsClosing tests same as TestFreezerBasics, but also closes and reopens the freezer between
  81. // every operation
  82. func TestFreezerBasicsClosing(t *testing.T) {
  83. t.Parallel()
  84. // set cutoff at 50 bytes
  85. var (
  86. fname = fmt.Sprintf("basics-close-%d", rand.Uint64())
  87. m1, m2 = metrics.NewMeter(), metrics.NewMeter()
  88. f *freezerTable
  89. err error
  90. )
  91. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. // Write 15 bytes 255 times, results in 85 files
  96. for x := byte(0); x < 255; x++ {
  97. data := getChunk(15, x)
  98. f.Append(uint64(x), data)
  99. f.Close()
  100. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  101. if err != nil {
  102. t.Fatal(err)
  103. }
  104. }
  105. defer f.Close()
  106. for y := byte(0); y < 255; y++ {
  107. exp := getChunk(15, y)
  108. got, err := f.Retrieve(uint64(y))
  109. if err != nil {
  110. t.Fatal(err)
  111. }
  112. if !bytes.Equal(got, exp) {
  113. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  114. }
  115. f.Close()
  116. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. }
  121. }
  122. // TestFreezerRepairDanglingHead tests that we can recover if index entries are removed
  123. func TestFreezerRepairDanglingHead(t *testing.T) {
  124. t.Parallel()
  125. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  126. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  127. { // Fill table
  128. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  129. if err != nil {
  130. t.Fatal(err)
  131. }
  132. // Write 15 bytes 255 times
  133. for x := byte(0); x < 0xff; x++ {
  134. data := getChunk(15, x)
  135. f.Append(uint64(x), data)
  136. }
  137. // The last item should be there
  138. if _, err = f.Retrieve(0xfe); err != nil {
  139. t.Fatal(err)
  140. }
  141. f.Close()
  142. }
  143. // open the index
  144. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  145. if err != nil {
  146. t.Fatalf("Failed to open index file: %v", err)
  147. }
  148. // Remove 4 bytes
  149. stat, err := idxFile.Stat()
  150. if err != nil {
  151. t.Fatalf("Failed to stat index file: %v", err)
  152. }
  153. idxFile.Truncate(stat.Size() - 4)
  154. idxFile.Close()
  155. // Now open it again
  156. {
  157. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  158. // The last item should be missing
  159. if _, err = f.Retrieve(0xff); err == nil {
  160. t.Errorf("Expected error for missing index entry")
  161. }
  162. // The one before should still be there
  163. if _, err = f.Retrieve(0xfd); err != nil {
  164. t.Fatalf("Expected no error, got %v", err)
  165. }
  166. }
  167. }
  168. // TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed
  169. func TestFreezerRepairDanglingHeadLarge(t *testing.T) {
  170. t.Parallel()
  171. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  172. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  173. { // Fill a table and close it
  174. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  175. if err != nil {
  176. t.Fatal(err)
  177. }
  178. // Write 15 bytes 255 times
  179. for x := byte(0); x < 0xff; x++ {
  180. data := getChunk(15, x)
  181. f.Append(uint64(x), data)
  182. }
  183. // The last item should be there
  184. if _, err = f.Retrieve(f.items - 1); err == nil {
  185. if err != nil {
  186. t.Fatal(err)
  187. }
  188. }
  189. f.Close()
  190. }
  191. // open the index
  192. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  193. if err != nil {
  194. t.Fatalf("Failed to open index file: %v", err)
  195. }
  196. // Remove everything but the first item, and leave data unaligned
  197. // 0-indexEntry, 1-indexEntry, corrupt-indexEntry
  198. idxFile.Truncate(indexEntrySize + indexEntrySize + indexEntrySize/2)
  199. idxFile.Close()
  200. // Now open it again
  201. {
  202. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  203. // The first item should be there
  204. if _, err = f.Retrieve(0); err != nil {
  205. t.Fatal(err)
  206. }
  207. // The second item should be missing
  208. if _, err = f.Retrieve(1); err == nil {
  209. t.Errorf("Expected error for missing index entry")
  210. }
  211. // We should now be able to store items again, from item = 1
  212. for x := byte(1); x < 0xff; x++ {
  213. data := getChunk(15, ^x)
  214. f.Append(uint64(x), data)
  215. }
  216. f.Close()
  217. }
  218. // And if we open it, we should now be able to read all of them (new values)
  219. {
  220. f, _ := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  221. for y := byte(1); y < 255; y++ {
  222. exp := getChunk(15, ^y)
  223. got, err := f.Retrieve(uint64(y))
  224. if err != nil {
  225. t.Fatal(err)
  226. }
  227. if !bytes.Equal(got, exp) {
  228. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  229. }
  230. }
  231. }
  232. }
  233. // TestSnappyDetection tests that we fail to open a snappy database and vice versa
  234. func TestSnappyDetection(t *testing.T) {
  235. t.Parallel()
  236. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  237. fname := fmt.Sprintf("snappytest-%d", rand.Uint64())
  238. // Open with snappy
  239. {
  240. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  241. if err != nil {
  242. t.Fatal(err)
  243. }
  244. // Write 15 bytes 255 times
  245. for x := byte(0); x < 0xff; x++ {
  246. data := getChunk(15, x)
  247. f.Append(uint64(x), data)
  248. }
  249. f.Close()
  250. }
  251. // Open without snappy
  252. {
  253. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, false)
  254. if _, err = f.Retrieve(0); err == nil {
  255. f.Close()
  256. t.Fatalf("expected empty table")
  257. }
  258. }
  259. // Open with snappy
  260. {
  261. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  262. // There should be 255 items
  263. if _, err = f.Retrieve(0xfe); err != nil {
  264. f.Close()
  265. t.Fatalf("expected no error, got %v", err)
  266. }
  267. }
  268. }
  269. func assertFileSize(f string, size int64) error {
  270. stat, err := os.Stat(f)
  271. if err != nil {
  272. return err
  273. }
  274. if stat.Size() != size {
  275. return fmt.Errorf("error, expected size %d, got %d", size, stat.Size())
  276. }
  277. return nil
  278. }
  279. // TestFreezerRepairDanglingIndex checks that if the index has more entries than there are data,
  280. // the index is repaired
  281. func TestFreezerRepairDanglingIndex(t *testing.T) {
  282. t.Parallel()
  283. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  284. fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64())
  285. { // Fill a table and close it
  286. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  287. if err != nil {
  288. t.Fatal(err)
  289. }
  290. // Write 15 bytes 9 times : 150 bytes
  291. for x := byte(0); x < 9; x++ {
  292. data := getChunk(15, x)
  293. f.Append(uint64(x), data)
  294. }
  295. // The last item should be there
  296. if _, err = f.Retrieve(f.items - 1); err != nil {
  297. f.Close()
  298. t.Fatal(err)
  299. }
  300. f.Close()
  301. // File sizes should be 45, 45, 45 : items[3, 3, 3)
  302. }
  303. // Crop third file
  304. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.2.rdat", fname))
  305. // Truncate third file: 45 ,45, 20
  306. {
  307. if err := assertFileSize(fileToCrop, 45); err != nil {
  308. t.Fatal(err)
  309. }
  310. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  311. if err != nil {
  312. t.Fatal(err)
  313. }
  314. file.Truncate(20)
  315. file.Close()
  316. }
  317. // Open db it again
  318. // It should restore the file(s) to
  319. // 45, 45, 15
  320. // with 3+3+1 items
  321. {
  322. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  323. if err != nil {
  324. t.Fatal(err)
  325. }
  326. if f.items != 7 {
  327. f.Close()
  328. t.Fatalf("expected %d items, got %d", 7, f.items)
  329. }
  330. if err := assertFileSize(fileToCrop, 15); err != nil {
  331. t.Fatal(err)
  332. }
  333. }
  334. }
  335. func TestFreezerTruncate(t *testing.T) {
  336. t.Parallel()
  337. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  338. fname := fmt.Sprintf("truncation-%d", rand.Uint64())
  339. { // Fill table
  340. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  341. if err != nil {
  342. t.Fatal(err)
  343. }
  344. // Write 15 bytes 30 times
  345. for x := byte(0); x < 30; x++ {
  346. data := getChunk(15, x)
  347. f.Append(uint64(x), data)
  348. }
  349. // The last item should be there
  350. if _, err = f.Retrieve(f.items - 1); err != nil {
  351. t.Fatal(err)
  352. }
  353. f.Close()
  354. }
  355. // Reopen, truncate
  356. {
  357. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  358. if err != nil {
  359. t.Fatal(err)
  360. }
  361. defer f.Close()
  362. f.truncate(10) // 150 bytes
  363. if f.items != 10 {
  364. t.Fatalf("expected %d items, got %d", 10, f.items)
  365. }
  366. // 45, 45, 45, 15 -- bytes should be 15
  367. if f.headBytes != 15 {
  368. t.Fatalf("expected %d bytes, got %d", 15, f.headBytes)
  369. }
  370. }
  371. }
  372. // TestFreezerRepairFirstFile tests a head file with the very first item only half-written.
  373. // That will rewind the index, and _should_ truncate the head file
  374. func TestFreezerRepairFirstFile(t *testing.T) {
  375. t.Parallel()
  376. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  377. fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64())
  378. { // Fill table
  379. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  380. if err != nil {
  381. t.Fatal(err)
  382. }
  383. // Write 80 bytes, splitting out into two files
  384. f.Append(0, getChunk(40, 0xFF))
  385. f.Append(1, getChunk(40, 0xEE))
  386. // The last item should be there
  387. if _, err = f.Retrieve(f.items - 1); err != nil {
  388. t.Fatal(err)
  389. }
  390. f.Close()
  391. }
  392. // Truncate the file in half
  393. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.1.rdat", fname))
  394. {
  395. if err := assertFileSize(fileToCrop, 40); err != nil {
  396. t.Fatal(err)
  397. }
  398. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  399. if err != nil {
  400. t.Fatal(err)
  401. }
  402. file.Truncate(20)
  403. file.Close()
  404. }
  405. // Reopen
  406. {
  407. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  408. if err != nil {
  409. t.Fatal(err)
  410. }
  411. if f.items != 1 {
  412. f.Close()
  413. t.Fatalf("expected %d items, got %d", 0, f.items)
  414. }
  415. // Write 40 bytes
  416. f.Append(1, getChunk(40, 0xDD))
  417. f.Close()
  418. // Should have been truncated down to zero and then 40 written
  419. if err := assertFileSize(fileToCrop, 40); err != nil {
  420. t.Fatal(err)
  421. }
  422. }
  423. }
  424. // TestFreezerReadAndTruncate tests:
  425. // - we have a table open
  426. // - do some reads, so files are open in readonly
  427. // - truncate so those files are 'removed'
  428. // - check that we did not keep the rdonly file descriptors
  429. func TestFreezerReadAndTruncate(t *testing.T) {
  430. t.Parallel()
  431. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  432. fname := fmt.Sprintf("read_truncate-%d", rand.Uint64())
  433. { // Fill table
  434. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  435. if err != nil {
  436. t.Fatal(err)
  437. }
  438. // Write 15 bytes 30 times
  439. for x := byte(0); x < 30; x++ {
  440. data := getChunk(15, x)
  441. f.Append(uint64(x), data)
  442. }
  443. // The last item should be there
  444. if _, err = f.Retrieve(f.items - 1); err != nil {
  445. t.Fatal(err)
  446. }
  447. f.Close()
  448. }
  449. // Reopen and read all files
  450. {
  451. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  452. if err != nil {
  453. t.Fatal(err)
  454. }
  455. if f.items != 30 {
  456. f.Close()
  457. t.Fatalf("expected %d items, got %d", 0, f.items)
  458. }
  459. for y := byte(0); y < 30; y++ {
  460. f.Retrieve(uint64(y))
  461. }
  462. // Now, truncate back to zero
  463. f.truncate(0)
  464. // Write the data again
  465. for x := byte(0); x < 30; x++ {
  466. data := getChunk(15, ^x)
  467. if err := f.Append(uint64(x), data); err != nil {
  468. t.Fatalf("error %v", err)
  469. }
  470. }
  471. f.Close()
  472. }
  473. }
  474. // TODO (?)
  475. // - test that if we remove several head-files, aswell as data last data-file,
  476. // the index is truncated accordingly
  477. // Right now, the freezer would fail on these conditions:
  478. // 1. have data files d0, d1, d2, d3
  479. // 2. remove d2,d3
  480. //
  481. // However, all 'normal' failure modes arising due to failing to sync() or save a file should be
  482. // handled already, and the case described above can only (?) happen if an external process/user
  483. // deletes files from the filesystem.