freezer_table_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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. "math/rand"
  21. "os"
  22. "path/filepath"
  23. "testing"
  24. "time"
  25. "github.com/ethereum/go-ethereum/metrics"
  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 int) []byte {
  32. data := make([]byte, size)
  33. for i := range data {
  34. data[i] = byte(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 := 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 := 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. // Check that we cannot read too far
  80. _, err = f.Retrieve(uint64(255))
  81. if err != errOutOfBounds {
  82. t.Fatal(err)
  83. }
  84. }
  85. // TestFreezerBasicsClosing tests same as TestFreezerBasics, but also closes and reopens the freezer between
  86. // every operation
  87. func TestFreezerBasicsClosing(t *testing.T) {
  88. t.Parallel()
  89. // set cutoff at 50 bytes
  90. var (
  91. fname = fmt.Sprintf("basics-close-%d", rand.Uint64())
  92. m1, m2 = metrics.NewMeter(), metrics.NewMeter()
  93. f *freezerTable
  94. err error
  95. )
  96. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. // Write 15 bytes 255 times, results in 85 files
  101. for x := 0; x < 255; x++ {
  102. data := getChunk(15, x)
  103. f.Append(uint64(x), data)
  104. f.Close()
  105. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  106. }
  107. defer f.Close()
  108. for y := 0; y < 255; y++ {
  109. exp := getChunk(15, y)
  110. got, err := f.Retrieve(uint64(y))
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. if !bytes.Equal(got, exp) {
  115. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  116. }
  117. f.Close()
  118. f, err = newCustomTable(os.TempDir(), fname, m1, m2, 50, true)
  119. if err != nil {
  120. t.Fatal(err)
  121. }
  122. }
  123. }
  124. // TestFreezerRepairDanglingHead tests that we can recover if index entries are removed
  125. func TestFreezerRepairDanglingHead(t *testing.T) {
  126. t.Parallel()
  127. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  128. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  129. { // Fill table
  130. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  131. if err != nil {
  132. t.Fatal(err)
  133. }
  134. // Write 15 bytes 255 times
  135. for x := 0; x < 255; x++ {
  136. data := getChunk(15, x)
  137. f.Append(uint64(x), data)
  138. }
  139. // The last item should be there
  140. if _, err = f.Retrieve(0xfe); err != nil {
  141. t.Fatal(err)
  142. }
  143. f.Close()
  144. }
  145. // open the index
  146. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  147. if err != nil {
  148. t.Fatalf("Failed to open index file: %v", err)
  149. }
  150. // Remove 4 bytes
  151. stat, err := idxFile.Stat()
  152. if err != nil {
  153. t.Fatalf("Failed to stat index file: %v", err)
  154. }
  155. idxFile.Truncate(stat.Size() - 4)
  156. idxFile.Close()
  157. // Now open it again
  158. {
  159. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  160. // The last item should be missing
  161. if _, err = f.Retrieve(0xff); err == nil {
  162. t.Errorf("Expected error for missing index entry")
  163. }
  164. // The one before should still be there
  165. if _, err = f.Retrieve(0xfd); err != nil {
  166. t.Fatalf("Expected no error, got %v", err)
  167. }
  168. }
  169. }
  170. // TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed
  171. func TestFreezerRepairDanglingHeadLarge(t *testing.T) {
  172. t.Parallel()
  173. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  174. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  175. { // Fill a table and close it
  176. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  177. if err != nil {
  178. t.Fatal(err)
  179. }
  180. // Write 15 bytes 255 times
  181. for x := 0; x < 0xff; x++ {
  182. data := getChunk(15, x)
  183. f.Append(uint64(x), data)
  184. }
  185. // The last item should be there
  186. if _, err = f.Retrieve(f.items - 1); err == nil {
  187. if err != nil {
  188. t.Fatal(err)
  189. }
  190. }
  191. f.Close()
  192. }
  193. // open the index
  194. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  195. if err != nil {
  196. t.Fatalf("Failed to open index file: %v", err)
  197. }
  198. // Remove everything but the first item, and leave data unaligned
  199. // 0-indexEntry, 1-indexEntry, corrupt-indexEntry
  200. idxFile.Truncate(indexEntrySize + indexEntrySize + indexEntrySize/2)
  201. idxFile.Close()
  202. // Now open it again
  203. {
  204. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  205. // The first item should be there
  206. if _, err = f.Retrieve(0); err != nil {
  207. t.Fatal(err)
  208. }
  209. // The second item should be missing
  210. if _, err = f.Retrieve(1); err == nil {
  211. t.Errorf("Expected error for missing index entry")
  212. }
  213. // We should now be able to store items again, from item = 1
  214. for x := 1; x < 0xff; x++ {
  215. data := getChunk(15, ^x)
  216. f.Append(uint64(x), data)
  217. }
  218. f.Close()
  219. }
  220. // And if we open it, we should now be able to read all of them (new values)
  221. {
  222. f, _ := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  223. for y := 1; y < 255; y++ {
  224. exp := getChunk(15, ^y)
  225. got, err := f.Retrieve(uint64(y))
  226. if err != nil {
  227. t.Fatal(err)
  228. }
  229. if !bytes.Equal(got, exp) {
  230. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  231. }
  232. }
  233. }
  234. }
  235. // TestSnappyDetection tests that we fail to open a snappy database and vice versa
  236. func TestSnappyDetection(t *testing.T) {
  237. t.Parallel()
  238. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  239. fname := fmt.Sprintf("snappytest-%d", rand.Uint64())
  240. // Open with snappy
  241. {
  242. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  243. if err != nil {
  244. t.Fatal(err)
  245. }
  246. // Write 15 bytes 255 times
  247. for x := 0; x < 0xff; x++ {
  248. data := getChunk(15, x)
  249. f.Append(uint64(x), data)
  250. }
  251. f.Close()
  252. }
  253. // Open without snappy
  254. {
  255. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, false)
  256. if _, err = f.Retrieve(0); err == nil {
  257. f.Close()
  258. t.Fatalf("expected empty table")
  259. }
  260. }
  261. // Open with snappy
  262. {
  263. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  264. // There should be 255 items
  265. if _, err = f.Retrieve(0xfe); err != nil {
  266. f.Close()
  267. t.Fatalf("expected no error, got %v", err)
  268. }
  269. }
  270. }
  271. func assertFileSize(f string, size int64) error {
  272. stat, err := os.Stat(f)
  273. if err != nil {
  274. return err
  275. }
  276. if stat.Size() != size {
  277. return fmt.Errorf("error, expected size %d, got %d", size, stat.Size())
  278. }
  279. return nil
  280. }
  281. // TestFreezerRepairDanglingIndex checks that if the index has more entries than there are data,
  282. // the index is repaired
  283. func TestFreezerRepairDanglingIndex(t *testing.T) {
  284. t.Parallel()
  285. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  286. fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64())
  287. { // Fill a table and close it
  288. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  289. if err != nil {
  290. t.Fatal(err)
  291. }
  292. // Write 15 bytes 9 times : 150 bytes
  293. for x := 0; x < 9; x++ {
  294. data := getChunk(15, x)
  295. f.Append(uint64(x), data)
  296. }
  297. // The last item should be there
  298. if _, err = f.Retrieve(f.items - 1); err != nil {
  299. f.Close()
  300. t.Fatal(err)
  301. }
  302. f.Close()
  303. // File sizes should be 45, 45, 45 : items[3, 3, 3)
  304. }
  305. // Crop third file
  306. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0002.rdat", fname))
  307. // Truncate third file: 45 ,45, 20
  308. {
  309. if err := assertFileSize(fileToCrop, 45); err != nil {
  310. t.Fatal(err)
  311. }
  312. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. file.Truncate(20)
  317. file.Close()
  318. }
  319. // Open db it again
  320. // It should restore the file(s) to
  321. // 45, 45, 15
  322. // with 3+3+1 items
  323. {
  324. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  325. if err != nil {
  326. t.Fatal(err)
  327. }
  328. if f.items != 7 {
  329. f.Close()
  330. t.Fatalf("expected %d items, got %d", 7, f.items)
  331. }
  332. if err := assertFileSize(fileToCrop, 15); err != nil {
  333. t.Fatal(err)
  334. }
  335. }
  336. }
  337. func TestFreezerTruncate(t *testing.T) {
  338. t.Parallel()
  339. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  340. fname := fmt.Sprintf("truncation-%d", rand.Uint64())
  341. { // Fill table
  342. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  343. if err != nil {
  344. t.Fatal(err)
  345. }
  346. // Write 15 bytes 30 times
  347. for x := 0; x < 30; x++ {
  348. data := getChunk(15, x)
  349. f.Append(uint64(x), data)
  350. }
  351. // The last item should be there
  352. if _, err = f.Retrieve(f.items - 1); err != nil {
  353. t.Fatal(err)
  354. }
  355. f.Close()
  356. }
  357. // Reopen, truncate
  358. {
  359. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  360. if err != nil {
  361. t.Fatal(err)
  362. }
  363. defer f.Close()
  364. f.truncate(10) // 150 bytes
  365. if f.items != 10 {
  366. t.Fatalf("expected %d items, got %d", 10, f.items)
  367. }
  368. // 45, 45, 45, 15 -- bytes should be 15
  369. if f.headBytes != 15 {
  370. t.Fatalf("expected %d bytes, got %d", 15, f.headBytes)
  371. }
  372. }
  373. }
  374. // TestFreezerRepairFirstFile tests a head file with the very first item only half-written.
  375. // That will rewind the index, and _should_ truncate the head file
  376. func TestFreezerRepairFirstFile(t *testing.T) {
  377. t.Parallel()
  378. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  379. fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64())
  380. { // Fill table
  381. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  382. if err != nil {
  383. t.Fatal(err)
  384. }
  385. // Write 80 bytes, splitting out into two files
  386. f.Append(0, getChunk(40, 0xFF))
  387. f.Append(1, getChunk(40, 0xEE))
  388. // The last item should be there
  389. if _, err = f.Retrieve(f.items - 1); err != nil {
  390. t.Fatal(err)
  391. }
  392. f.Close()
  393. }
  394. // Truncate the file in half
  395. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0001.rdat", fname))
  396. {
  397. if err := assertFileSize(fileToCrop, 40); err != nil {
  398. t.Fatal(err)
  399. }
  400. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  401. if err != nil {
  402. t.Fatal(err)
  403. }
  404. file.Truncate(20)
  405. file.Close()
  406. }
  407. // Reopen
  408. {
  409. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  410. if err != nil {
  411. t.Fatal(err)
  412. }
  413. if f.items != 1 {
  414. f.Close()
  415. t.Fatalf("expected %d items, got %d", 0, f.items)
  416. }
  417. // Write 40 bytes
  418. f.Append(1, getChunk(40, 0xDD))
  419. f.Close()
  420. // Should have been truncated down to zero and then 40 written
  421. if err := assertFileSize(fileToCrop, 40); err != nil {
  422. t.Fatal(err)
  423. }
  424. }
  425. }
  426. // TestFreezerReadAndTruncate tests:
  427. // - we have a table open
  428. // - do some reads, so files are open in readonly
  429. // - truncate so those files are 'removed'
  430. // - check that we did not keep the rdonly file descriptors
  431. func TestFreezerReadAndTruncate(t *testing.T) {
  432. t.Parallel()
  433. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  434. fname := fmt.Sprintf("read_truncate-%d", rand.Uint64())
  435. { // Fill table
  436. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 50, true)
  437. if err != nil {
  438. t.Fatal(err)
  439. }
  440. // Write 15 bytes 30 times
  441. for x := 0; x < 30; x++ {
  442. data := getChunk(15, x)
  443. f.Append(uint64(x), data)
  444. }
  445. // The last item should be there
  446. if _, err = f.Retrieve(f.items - 1); err != nil {
  447. t.Fatal(err)
  448. }
  449. f.Close()
  450. }
  451. // Reopen and read all files
  452. {
  453. f, err := newCustomTable(os.TempDir(), fname, wm, rm, 50, true)
  454. if err != nil {
  455. t.Fatal(err)
  456. }
  457. if f.items != 30 {
  458. f.Close()
  459. t.Fatalf("expected %d items, got %d", 0, f.items)
  460. }
  461. for y := byte(0); y < 30; y++ {
  462. f.Retrieve(uint64(y))
  463. }
  464. // Now, truncate back to zero
  465. f.truncate(0)
  466. // Write the data again
  467. for x := 0; x < 30; x++ {
  468. data := getChunk(15, ^x)
  469. if err := f.Append(uint64(x), data); err != nil {
  470. t.Fatalf("error %v", err)
  471. }
  472. }
  473. f.Close()
  474. }
  475. }
  476. func TestOffset(t *testing.T) {
  477. t.Parallel()
  478. wm, rm := metrics.NewMeter(), metrics.NewMeter()
  479. fname := fmt.Sprintf("offset-%d", rand.Uint64())
  480. { // Fill table
  481. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 40, true)
  482. if err != nil {
  483. t.Fatal(err)
  484. }
  485. // Write 6 x 20 bytes, splitting out into three files
  486. f.Append(0, getChunk(20, 0xFF))
  487. f.Append(1, getChunk(20, 0xEE))
  488. f.Append(2, getChunk(20, 0xdd))
  489. f.Append(3, getChunk(20, 0xcc))
  490. f.Append(4, getChunk(20, 0xbb))
  491. f.Append(5, getChunk(20, 0xaa))
  492. f.printIndex()
  493. f.Close()
  494. }
  495. // Now crop it.
  496. {
  497. // delete files 0 and 1
  498. for i := 0; i < 2; i++ {
  499. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.%04d.rdat", fname, i))
  500. if err := os.Remove(p); err != nil {
  501. t.Fatal(err)
  502. }
  503. }
  504. // Read the index file
  505. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
  506. indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
  507. if err != nil {
  508. t.Fatal(err)
  509. }
  510. indexBuf := make([]byte, 7*indexEntrySize)
  511. indexFile.Read(indexBuf)
  512. // Update the index file, so that we store
  513. // [ file = 2, offset = 4 ] at index zero
  514. tailId := uint32(2) // First file is 2
  515. itemOffset := uint32(4) // We have removed four items
  516. zeroIndex := indexEntry{
  517. offset: tailId,
  518. filenum: itemOffset,
  519. }
  520. buf := zeroIndex.marshallBinary()
  521. // Overwrite index zero
  522. copy(indexBuf, buf)
  523. // Remove the four next indices by overwriting
  524. copy(indexBuf[indexEntrySize:], indexBuf[indexEntrySize*5:])
  525. indexFile.WriteAt(indexBuf, 0)
  526. // Need to truncate the moved index items
  527. indexFile.Truncate(indexEntrySize * (1 + 2))
  528. indexFile.Close()
  529. }
  530. // Now open again
  531. {
  532. f, err := newCustomTable(os.TempDir(), fname, rm, wm, 40, true)
  533. if err != nil {
  534. t.Fatal(err)
  535. }
  536. f.printIndex()
  537. // It should allow writing item 6
  538. f.Append(6, getChunk(20, 0x99))
  539. // It should be fine to fetch 4,5,6
  540. if got, err := f.Retrieve(4); err != nil {
  541. t.Fatal(err)
  542. } else if exp := getChunk(20, 0xbb); !bytes.Equal(got, exp) {
  543. t.Fatalf("expected %x got %x", exp, got)
  544. }
  545. if got, err := f.Retrieve(5); err != nil {
  546. t.Fatal(err)
  547. } else if exp := getChunk(20, 0xaa); !bytes.Equal(got, exp) {
  548. t.Fatalf("expected %x got %x", exp, got)
  549. }
  550. if got, err := f.Retrieve(6); err != nil {
  551. t.Fatal(err)
  552. } else if exp := getChunk(20, 0x99); !bytes.Equal(got, exp) {
  553. t.Fatalf("expected %x got %x", exp, got)
  554. }
  555. // It should error at 0, 1,2,3
  556. for i := 0; i < 4; i++ {
  557. if _, err := f.Retrieve(uint64(i)); err == nil {
  558. t.Fatal("expected err")
  559. }
  560. }
  561. }
  562. }
  563. // TODO (?)
  564. // - test that if we remove several head-files, aswell as data last data-file,
  565. // the index is truncated accordingly
  566. // Right now, the freezer would fail on these conditions:
  567. // 1. have data files d0, d1, d2, d3
  568. // 2. remove d2,d3
  569. //
  570. // However, all 'normal' failure modes arising due to failing to sync() or save a file should be
  571. // handled already, and the case described above can only (?) happen if an external process/user
  572. // deletes files from the filesystem.