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