freezer_table_test.go 16 KB

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