| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- // Copyright 2021 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package rawdb
- import (
- "bytes"
- "errors"
- "fmt"
- "math/big"
- "math/rand"
- "os"
- "path"
- "sync"
- "testing"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/stretchr/testify/require"
- )
- var freezerTestTableDef = map[string]bool{"test": true}
- func TestFreezerModify(t *testing.T) {
- t.Parallel()
- // Create test data.
- var valuesRaw [][]byte
- var valuesRLP []*big.Int
- for x := 0; x < 100; x++ {
- v := getChunk(256, x)
- valuesRaw = append(valuesRaw, v)
- iv := big.NewInt(int64(x))
- iv = iv.Exp(iv, iv, nil)
- valuesRLP = append(valuesRLP, iv)
- }
- tables := map[string]bool{"raw": true, "rlp": false}
- f, _ := newFreezerForTesting(t, tables)
- defer f.Close()
- // Commit test data.
- _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
- for i := range valuesRaw {
- if err := op.AppendRaw("raw", uint64(i), valuesRaw[i]); err != nil {
- return err
- }
- if err := op.Append("rlp", uint64(i), valuesRLP[i]); err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- t.Fatal("ModifyAncients failed:", err)
- }
- // Dump indexes.
- for _, table := range f.tables {
- t.Log(table.name, "index:", table.dumpIndexString(0, int64(len(valuesRaw))))
- }
- // Read back test data.
- checkAncientCount(t, f, "raw", uint64(len(valuesRaw)))
- checkAncientCount(t, f, "rlp", uint64(len(valuesRLP)))
- for i := range valuesRaw {
- v, _ := f.Ancient("raw", uint64(i))
- if !bytes.Equal(v, valuesRaw[i]) {
- t.Fatalf("wrong raw value at %d: %x", i, v)
- }
- ivEnc, _ := f.Ancient("rlp", uint64(i))
- want, _ := rlp.EncodeToBytes(valuesRLP[i])
- if !bytes.Equal(ivEnc, want) {
- t.Fatalf("wrong RLP value at %d: %x", i, ivEnc)
- }
- }
- }
- // This checks that ModifyAncients rolls back freezer updates
- // when the function passed to it returns an error.
- func TestFreezerModifyRollback(t *testing.T) {
- t.Parallel()
- f, dir := newFreezerForTesting(t, freezerTestTableDef)
- theError := errors.New("oops")
- _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
- // Append three items. This creates two files immediately,
- // because the table size limit of the test freezer is 2048.
- require.NoError(t, op.AppendRaw("test", 0, make([]byte, 2048)))
- require.NoError(t, op.AppendRaw("test", 1, make([]byte, 2048)))
- require.NoError(t, op.AppendRaw("test", 2, make([]byte, 2048)))
- return theError
- })
- if err != theError {
- t.Errorf("ModifyAncients returned wrong error %q", err)
- }
- checkAncientCount(t, f, "test", 0)
- f.Close()
- // Reopen and check that the rolled-back data doesn't reappear.
- tables := map[string]bool{"test": true}
- f2, err := NewFreezer(dir, "", false, 2049, tables)
- if err != nil {
- t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err)
- }
- defer f2.Close()
- checkAncientCount(t, f2, "test", 0)
- }
- // This test runs ModifyAncients and Ancient concurrently with each other.
- func TestFreezerConcurrentModifyRetrieve(t *testing.T) {
- t.Parallel()
- f, _ := newFreezerForTesting(t, freezerTestTableDef)
- defer f.Close()
- var (
- numReaders = 5
- writeBatchSize = uint64(50)
- written = make(chan uint64, numReaders*6)
- wg sync.WaitGroup
- )
- wg.Add(numReaders + 1)
- // Launch the writer. It appends 10000 items in batches.
- go func() {
- defer wg.Done()
- defer close(written)
- for item := uint64(0); item < 10000; item += writeBatchSize {
- _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
- for i := uint64(0); i < writeBatchSize; i++ {
- item := item + i
- value := getChunk(32, int(item))
- if err := op.AppendRaw("test", item, value); err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- panic(err)
- }
- for i := 0; i < numReaders; i++ {
- written <- item + writeBatchSize
- }
- }
- }()
- // Launch the readers. They read random items from the freezer up to the
- // current frozen item count.
- for i := 0; i < numReaders; i++ {
- go func() {
- defer wg.Done()
- for frozen := range written {
- for rc := 0; rc < 80; rc++ {
- num := uint64(rand.Intn(int(frozen)))
- value, err := f.Ancient("test", num)
- if err != nil {
- panic(fmt.Errorf("error reading %d (frozen %d): %v", num, frozen, err))
- }
- if !bytes.Equal(value, getChunk(32, int(num))) {
- panic(fmt.Errorf("wrong value at %d", num))
- }
- }
- }
- }()
- }
- wg.Wait()
- }
- // This test runs ModifyAncients and TruncateHead concurrently with each other.
- func TestFreezerConcurrentModifyTruncate(t *testing.T) {
- f, _ := newFreezerForTesting(t, freezerTestTableDef)
- defer f.Close()
- var item = make([]byte, 256)
- for i := 0; i < 1000; i++ {
- // First reset and write 100 items.
- if err := f.TruncateHead(0); err != nil {
- t.Fatal("truncate failed:", err)
- }
- _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
- for i := uint64(0); i < 100; i++ {
- if err := op.AppendRaw("test", i, item); err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- t.Fatal("modify failed:", err)
- }
- checkAncientCount(t, f, "test", 100)
- // Now append 100 more items and truncate concurrently.
- var (
- wg sync.WaitGroup
- truncateErr error
- modifyErr error
- )
- wg.Add(3)
- go func() {
- _, modifyErr = f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
- for i := uint64(100); i < 200; i++ {
- if err := op.AppendRaw("test", i, item); err != nil {
- return err
- }
- }
- return nil
- })
- wg.Done()
- }()
- go func() {
- truncateErr = f.TruncateHead(10)
- wg.Done()
- }()
- go func() {
- f.AncientSize("test")
- wg.Done()
- }()
- wg.Wait()
- // Now check the outcome. If the truncate operation went through first, the append
- // fails, otherwise it succeeds. In either case, the freezer should be positioned
- // at 10 after both operations are done.
- if truncateErr != nil {
- t.Fatal("concurrent truncate failed:", err)
- }
- if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) {
- t.Fatal("wrong error from concurrent modify:", modifyErr)
- }
- checkAncientCount(t, f, "test", 10)
- }
- }
- func TestFreezerReadonlyValidate(t *testing.T) {
- tables := map[string]bool{"a": true, "b": true}
- dir := t.TempDir()
- // Open non-readonly freezer and fill individual tables
- // with different amount of data.
- f, err := NewFreezer(dir, "", false, 2049, tables)
- if err != nil {
- t.Fatal("can't open freezer", err)
- }
- var item = make([]byte, 1024)
- aBatch := f.tables["a"].newBatch()
- require.NoError(t, aBatch.AppendRaw(0, item))
- require.NoError(t, aBatch.AppendRaw(1, item))
- require.NoError(t, aBatch.AppendRaw(2, item))
- require.NoError(t, aBatch.commit())
- bBatch := f.tables["b"].newBatch()
- require.NoError(t, bBatch.AppendRaw(0, item))
- require.NoError(t, bBatch.commit())
- if f.tables["a"].items != 3 {
- t.Fatalf("unexpected number of items in table")
- }
- if f.tables["b"].items != 1 {
- t.Fatalf("unexpected number of items in table")
- }
- require.NoError(t, f.Close())
- // Re-openening as readonly should fail when validating
- // table lengths.
- _, err = NewFreezer(dir, "", true, 2049, tables)
- if err == nil {
- t.Fatal("readonly freezer should fail with differing table lengths")
- }
- }
- func newFreezerForTesting(t *testing.T, tables map[string]bool) (*Freezer, string) {
- t.Helper()
- dir := t.TempDir()
- // note: using low max table size here to ensure the tests actually
- // switch between multiple files.
- f, err := NewFreezer(dir, "", false, 2049, tables)
- if err != nil {
- t.Fatal("can't open freezer", err)
- }
- return f, dir
- }
- // checkAncientCount verifies that the freezer contains n items.
- func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
- t.Helper()
- if frozen, _ := f.Ancients(); frozen != n {
- t.Fatalf("Ancients() returned %d, want %d", frozen, n)
- }
- // Check at index n-1.
- if n > 0 {
- index := n - 1
- if ok, _ := f.HasAncient(kind, index); !ok {
- t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index)
- }
- if _, err := f.Ancient(kind, index); err != nil {
- t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
- }
- }
- // Check at index n.
- index := n
- if ok, _ := f.HasAncient(kind, index); ok {
- t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index)
- }
- if _, err := f.Ancient(kind, index); err == nil {
- t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index)
- } else if err != errOutOfBounds {
- t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
- }
- }
- func TestRenameWindows(t *testing.T) {
- var (
- fname = "file.bin"
- fname2 = "file2.bin"
- data = []byte{1, 2, 3, 4}
- data2 = []byte{2, 3, 4, 5}
- data3 = []byte{3, 5, 6, 7}
- dataLen = 4
- )
- // Create 2 temp dirs
- dir1 := t.TempDir()
- dir2 := t.TempDir()
- // Create file in dir1 and fill with data
- f, err := os.Create(path.Join(dir1, fname))
- if err != nil {
- t.Fatal(err)
- }
- f2, err := os.Create(path.Join(dir1, fname2))
- if err != nil {
- t.Fatal(err)
- }
- f3, err := os.Create(path.Join(dir2, fname2))
- if err != nil {
- t.Fatal(err)
- }
- if _, err := f.Write(data); err != nil {
- t.Fatal(err)
- }
- if _, err := f2.Write(data2); err != nil {
- t.Fatal(err)
- }
- if _, err := f3.Write(data3); err != nil {
- t.Fatal(err)
- }
- if err := f.Close(); err != nil {
- t.Fatal(err)
- }
- if err := f2.Close(); err != nil {
- t.Fatal(err)
- }
- if err := f3.Close(); err != nil {
- t.Fatal(err)
- }
- if err := os.Rename(f.Name(), path.Join(dir2, fname)); err != nil {
- t.Fatal(err)
- }
- if err := os.Rename(f2.Name(), path.Join(dir2, fname2)); err != nil {
- t.Fatal(err)
- }
- // Check file contents
- f, err = os.Open(path.Join(dir2, fname))
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- defer os.Remove(f.Name())
- buf := make([]byte, dataLen)
- if _, err := f.Read(buf); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(buf, data) {
- t.Errorf("unexpected file contents. Got %v\n", buf)
- }
- f, err = os.Open(path.Join(dir2, fname2))
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- defer os.Remove(f.Name())
- if _, err := f.Read(buf); err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(buf, data2) {
- t.Errorf("unexpected file contents. Got %v\n", buf)
- }
- }
|