github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/core/rawdb/freezer_test.go (about) 1 // Copyright 2021 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 17 package rawdb 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "math/big" 24 "math/rand" 25 "os" 26 "path/filepath" 27 "sync" 28 "testing" 29 30 "github.com/ethereum/go-ethereum/core/rawdb/ancienttest" 31 "github.com/ethereum/go-ethereum/ethdb" 32 "github.com/ethereum/go-ethereum/rlp" 33 "github.com/stretchr/testify/require" 34 ) 35 36 var freezerTestTableDef = map[string]bool{"test": true} 37 38 func TestFreezerModify(t *testing.T) { 39 t.Parallel() 40 41 // Create test data. 42 var valuesRaw [][]byte 43 var valuesRLP []*big.Int 44 for x := 0; x < 100; x++ { 45 v := getChunk(256, x) 46 valuesRaw = append(valuesRaw, v) 47 iv := big.NewInt(int64(x)) 48 iv = iv.Exp(iv, iv, nil) 49 valuesRLP = append(valuesRLP, iv) 50 } 51 52 tables := map[string]bool{"raw": true, "rlp": false} 53 f, _ := newFreezerForTesting(t, tables) 54 defer f.Close() 55 56 // Commit test data. 57 _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 58 for i := range valuesRaw { 59 if err := op.AppendRaw("raw", uint64(i), valuesRaw[i]); err != nil { 60 return err 61 } 62 if err := op.Append("rlp", uint64(i), valuesRLP[i]); err != nil { 63 return err 64 } 65 } 66 return nil 67 }) 68 if err != nil { 69 t.Fatal("ModifyAncients failed:", err) 70 } 71 72 // Dump indexes. 73 for _, table := range f.tables { 74 t.Log(table.name, "index:", table.dumpIndexString(0, int64(len(valuesRaw)))) 75 } 76 77 // Read back test data. 78 checkAncientCount(t, f, "raw", uint64(len(valuesRaw))) 79 checkAncientCount(t, f, "rlp", uint64(len(valuesRLP))) 80 for i := range valuesRaw { 81 v, _ := f.Ancient("raw", uint64(i)) 82 if !bytes.Equal(v, valuesRaw[i]) { 83 t.Fatalf("wrong raw value at %d: %x", i, v) 84 } 85 ivEnc, _ := f.Ancient("rlp", uint64(i)) 86 want, _ := rlp.EncodeToBytes(valuesRLP[i]) 87 if !bytes.Equal(ivEnc, want) { 88 t.Fatalf("wrong RLP value at %d: %x", i, ivEnc) 89 } 90 } 91 } 92 93 // This checks that ModifyAncients rolls back freezer updates 94 // when the function passed to it returns an error. 95 func TestFreezerModifyRollback(t *testing.T) { 96 t.Parallel() 97 98 f, dir := newFreezerForTesting(t, freezerTestTableDef) 99 100 theError := errors.New("oops") 101 _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 102 // Append three items. This creates two files immediately, 103 // because the table size limit of the test freezer is 2048. 104 require.NoError(t, op.AppendRaw("test", 0, make([]byte, 2048))) 105 require.NoError(t, op.AppendRaw("test", 1, make([]byte, 2048))) 106 require.NoError(t, op.AppendRaw("test", 2, make([]byte, 2048))) 107 return theError 108 }) 109 if err != theError { 110 t.Errorf("ModifyAncients returned wrong error %q", err) 111 } 112 checkAncientCount(t, f, "test", 0) 113 f.Close() 114 115 // Reopen and check that the rolled-back data doesn't reappear. 116 tables := map[string]bool{"test": true} 117 f2, err := NewFreezer(dir, "", false, 2049, tables) 118 if err != nil { 119 t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err) 120 } 121 defer f2.Close() 122 checkAncientCount(t, f2, "test", 0) 123 } 124 125 // This test runs ModifyAncients and Ancient concurrently with each other. 126 func TestFreezerConcurrentModifyRetrieve(t *testing.T) { 127 t.Parallel() 128 129 f, _ := newFreezerForTesting(t, freezerTestTableDef) 130 defer f.Close() 131 132 var ( 133 numReaders = 5 134 writeBatchSize = uint64(50) 135 written = make(chan uint64, numReaders*6) 136 wg sync.WaitGroup 137 ) 138 wg.Add(numReaders + 1) 139 140 // Launch the writer. It appends 10000 items in batches. 141 go func() { 142 defer wg.Done() 143 defer close(written) 144 for item := uint64(0); item < 10000; item += writeBatchSize { 145 _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 146 for i := uint64(0); i < writeBatchSize; i++ { 147 item := item + i 148 value := getChunk(32, int(item)) 149 if err := op.AppendRaw("test", item, value); err != nil { 150 return err 151 } 152 } 153 return nil 154 }) 155 if err != nil { 156 panic(err) 157 } 158 for i := 0; i < numReaders; i++ { 159 written <- item + writeBatchSize 160 } 161 } 162 }() 163 164 // Launch the readers. They read random items from the freezer up to the 165 // current frozen item count. 166 for i := 0; i < numReaders; i++ { 167 go func() { 168 defer wg.Done() 169 for frozen := range written { 170 for rc := 0; rc < 80; rc++ { 171 num := uint64(rand.Intn(int(frozen))) 172 value, err := f.Ancient("test", num) 173 if err != nil { 174 panic(fmt.Errorf("error reading %d (frozen %d): %v", num, frozen, err)) 175 } 176 if !bytes.Equal(value, getChunk(32, int(num))) { 177 panic(fmt.Errorf("wrong value at %d", num)) 178 } 179 } 180 } 181 }() 182 } 183 184 wg.Wait() 185 } 186 187 // This test runs ModifyAncients and TruncateHead concurrently with each other. 188 func TestFreezerConcurrentModifyTruncate(t *testing.T) { 189 f, _ := newFreezerForTesting(t, freezerTestTableDef) 190 defer f.Close() 191 192 var item = make([]byte, 256) 193 194 for i := 0; i < 10; i++ { 195 // First reset and write 100 items. 196 if _, err := f.TruncateHead(0); err != nil { 197 t.Fatal("truncate failed:", err) 198 } 199 _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 200 for i := uint64(0); i < 100; i++ { 201 if err := op.AppendRaw("test", i, item); err != nil { 202 return err 203 } 204 } 205 return nil 206 }) 207 if err != nil { 208 t.Fatal("modify failed:", err) 209 } 210 checkAncientCount(t, f, "test", 100) 211 212 // Now append 100 more items and truncate concurrently. 213 var ( 214 wg sync.WaitGroup 215 truncateErr error 216 modifyErr error 217 ) 218 wg.Add(3) 219 go func() { 220 _, modifyErr = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { 221 for i := uint64(100); i < 200; i++ { 222 if err := op.AppendRaw("test", i, item); err != nil { 223 return err 224 } 225 } 226 return nil 227 }) 228 wg.Done() 229 }() 230 go func() { 231 _, truncateErr = f.TruncateHead(10) 232 wg.Done() 233 }() 234 go func() { 235 f.AncientSize("test") 236 wg.Done() 237 }() 238 wg.Wait() 239 240 // Now check the outcome. If the truncate operation went through first, the append 241 // fails, otherwise it succeeds. In either case, the freezer should be positioned 242 // at 10 after both operations are done. 243 if truncateErr != nil { 244 t.Fatal("concurrent truncate failed:", err) 245 } 246 if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) { 247 t.Fatal("wrong error from concurrent modify:", modifyErr) 248 } 249 checkAncientCount(t, f, "test", 10) 250 } 251 } 252 253 func TestFreezerReadonlyValidate(t *testing.T) { 254 tables := map[string]bool{"a": true, "b": true} 255 dir := t.TempDir() 256 // Open non-readonly freezer and fill individual tables 257 // with different amount of data. 258 f, err := NewFreezer(dir, "", false, 2049, tables) 259 if err != nil { 260 t.Fatal("can't open freezer", err) 261 } 262 var item = make([]byte, 1024) 263 aBatch := f.tables["a"].newBatch() 264 require.NoError(t, aBatch.AppendRaw(0, item)) 265 require.NoError(t, aBatch.AppendRaw(1, item)) 266 require.NoError(t, aBatch.AppendRaw(2, item)) 267 require.NoError(t, aBatch.commit()) 268 bBatch := f.tables["b"].newBatch() 269 require.NoError(t, bBatch.AppendRaw(0, item)) 270 require.NoError(t, bBatch.commit()) 271 if f.tables["a"].items.Load() != 3 { 272 t.Fatalf("unexpected number of items in table") 273 } 274 if f.tables["b"].items.Load() != 1 { 275 t.Fatalf("unexpected number of items in table") 276 } 277 require.NoError(t, f.Close()) 278 279 // Re-opening as readonly should fail when validating 280 // table lengths. 281 _, err = NewFreezer(dir, "", true, 2049, tables) 282 if err == nil { 283 t.Fatal("readonly freezer should fail with differing table lengths") 284 } 285 } 286 287 func TestFreezerConcurrentReadonly(t *testing.T) { 288 t.Parallel() 289 290 tables := map[string]bool{"a": true} 291 dir := t.TempDir() 292 293 f, err := NewFreezer(dir, "", false, 2049, tables) 294 if err != nil { 295 t.Fatal("can't open freezer", err) 296 } 297 var item = make([]byte, 1024) 298 batch := f.tables["a"].newBatch() 299 items := uint64(10) 300 for i := uint64(0); i < items; i++ { 301 require.NoError(t, batch.AppendRaw(i, item)) 302 } 303 require.NoError(t, batch.commit()) 304 if loaded := f.tables["a"].items.Load(); loaded != items { 305 t.Fatalf("unexpected number of items in table, want: %d, have: %d", items, loaded) 306 } 307 require.NoError(t, f.Close()) 308 309 var ( 310 wg sync.WaitGroup 311 fs = make([]*Freezer, 5) 312 errs = make([]error, 5) 313 ) 314 for i := 0; i < 5; i++ { 315 wg.Add(1) 316 go func(i int) { 317 defer wg.Done() 318 319 f, err := NewFreezer(dir, "", true, 2049, tables) 320 if err == nil { 321 fs[i] = f 322 } else { 323 errs[i] = err 324 } 325 }(i) 326 } 327 328 wg.Wait() 329 330 for i := range fs { 331 if err := errs[i]; err != nil { 332 t.Fatal("failed to open freezer", err) 333 } 334 require.NoError(t, fs[i].Close()) 335 } 336 } 337 338 func newFreezerForTesting(t *testing.T, tables map[string]bool) (*Freezer, string) { 339 t.Helper() 340 341 dir := t.TempDir() 342 // note: using low max table size here to ensure the tests actually 343 // switch between multiple files. 344 f, err := NewFreezer(dir, "", false, 2049, tables) 345 if err != nil { 346 t.Fatal("can't open freezer", err) 347 } 348 return f, dir 349 } 350 351 // checkAncientCount verifies that the freezer contains n items. 352 func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) { 353 t.Helper() 354 355 if frozen, _ := f.Ancients(); frozen != n { 356 t.Fatalf("Ancients() returned %d, want %d", frozen, n) 357 } 358 359 // Check at index n-1. 360 if n > 0 { 361 index := n - 1 362 if ok, _ := f.HasAncient(kind, index); !ok { 363 t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index) 364 } 365 if _, err := f.Ancient(kind, index); err != nil { 366 t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) 367 } 368 } 369 370 // Check at index n. 371 index := n 372 if ok, _ := f.HasAncient(kind, index); ok { 373 t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index) 374 } 375 if _, err := f.Ancient(kind, index); err == nil { 376 t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index) 377 } else if err != errOutOfBounds { 378 t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) 379 } 380 } 381 382 func TestRenameWindows(t *testing.T) { 383 var ( 384 fname = "file.bin" 385 fname2 = "file2.bin" 386 data = []byte{1, 2, 3, 4} 387 data2 = []byte{2, 3, 4, 5} 388 data3 = []byte{3, 5, 6, 7} 389 dataLen = 4 390 ) 391 392 // Create 2 temp dirs 393 dir1 := t.TempDir() 394 dir2 := t.TempDir() 395 396 // Create file in dir1 and fill with data 397 f, err := os.Create(filepath.Join(dir1, fname)) 398 if err != nil { 399 t.Fatal(err) 400 } 401 f2, err := os.Create(filepath.Join(dir1, fname2)) 402 if err != nil { 403 t.Fatal(err) 404 } 405 f3, err := os.Create(filepath.Join(dir2, fname2)) 406 if err != nil { 407 t.Fatal(err) 408 } 409 if _, err := f.Write(data); err != nil { 410 t.Fatal(err) 411 } 412 if _, err := f2.Write(data2); err != nil { 413 t.Fatal(err) 414 } 415 if _, err := f3.Write(data3); err != nil { 416 t.Fatal(err) 417 } 418 if err := f.Close(); err != nil { 419 t.Fatal(err) 420 } 421 if err := f2.Close(); err != nil { 422 t.Fatal(err) 423 } 424 if err := f3.Close(); err != nil { 425 t.Fatal(err) 426 } 427 if err := os.Rename(f.Name(), filepath.Join(dir2, fname)); err != nil { 428 t.Fatal(err) 429 } 430 if err := os.Rename(f2.Name(), filepath.Join(dir2, fname2)); err != nil { 431 t.Fatal(err) 432 } 433 434 // Check file contents 435 f, err = os.Open(filepath.Join(dir2, fname)) 436 if err != nil { 437 t.Fatal(err) 438 } 439 defer f.Close() 440 defer os.Remove(f.Name()) 441 buf := make([]byte, dataLen) 442 if _, err := f.Read(buf); err != nil { 443 t.Fatal(err) 444 } 445 if !bytes.Equal(buf, data) { 446 t.Errorf("unexpected file contents. Got %v\n", buf) 447 } 448 449 f, err = os.Open(filepath.Join(dir2, fname2)) 450 if err != nil { 451 t.Fatal(err) 452 } 453 defer f.Close() 454 defer os.Remove(f.Name()) 455 if _, err := f.Read(buf); err != nil { 456 t.Fatal(err) 457 } 458 if !bytes.Equal(buf, data2) { 459 t.Errorf("unexpected file contents. Got %v\n", buf) 460 } 461 } 462 463 func TestFreezerCloseSync(t *testing.T) { 464 t.Parallel() 465 f, _ := newFreezerForTesting(t, map[string]bool{"a": true, "b": true}) 466 defer f.Close() 467 468 // Now, close and sync. This mimics the behaviour if the node is shut down, 469 // just as the chain freezer is writing. 470 // 1: thread-1: chain treezer writes, via freezeRange (holds lock) 471 // 2: thread-2: Close called, waits for write to finish 472 // 3: thread-1: finishes writing, releases lock 473 // 4: thread-2: obtains lock, completes Close() 474 // 5: thread-1: calls f.Sync() 475 if err := f.Close(); err != nil { 476 t.Fatal(err) 477 } 478 if err := f.Sync(); err == nil { 479 t.Fatalf("want error, have nil") 480 } else if have, want := err.Error(), "[closed closed]"; have != want { 481 t.Fatalf("want %v, have %v", have, want) 482 } 483 } 484 485 func TestFreezerSuite(t *testing.T) { 486 ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore { 487 tables := make(map[string]bool) 488 for _, kind := range kinds { 489 tables[kind] = true 490 } 491 f, _ := newFreezerForTesting(t, tables) 492 return f 493 }) 494 ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore { 495 tables := make(map[string]bool) 496 for _, kind := range kinds { 497 tables[kind] = true 498 } 499 f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables) 500 return f 501 }) 502 }