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  }