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