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  }