github.com/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  }