github.com/ethereum/go-ethereum@v1.14.3/core/rawdb/ancienttest/testsuite.go (about)

     1  // Copyright 2024 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 ancienttest
    18  
    19  import (
    20  	"bytes"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/ethereum/go-ethereum/ethdb"
    25  	"github.com/ethereum/go-ethereum/internal/testrand"
    26  )
    27  
    28  // TestAncientSuite runs a suite of tests against an ancient database
    29  // implementation.
    30  func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
    31  	// Test basic read methods
    32  	t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) })
    33  
    34  	// Test batch read method
    35  	t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) })
    36  
    37  	// Test basic write methods
    38  	t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) })
    39  
    40  	// Test if data mutation is allowed after db write
    41  	t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) })
    42  }
    43  
    44  func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
    45  	var (
    46  		db   = newFn([]string{"a"})
    47  		data = makeDataset(100, 32)
    48  	)
    49  	defer db.Close()
    50  
    51  	db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
    52  		for i := 0; i < len(data); i++ {
    53  			op.AppendRaw("a", uint64(i), data[i])
    54  		}
    55  		return nil
    56  	})
    57  	db.TruncateTail(10)
    58  	db.TruncateHead(90)
    59  
    60  	// Test basic tail and head retrievals
    61  	tail, err := db.Tail()
    62  	if err != nil || tail != 10 {
    63  		t.Fatal("Failed to retrieve tail")
    64  	}
    65  	ancient, err := db.Ancients()
    66  	if err != nil || ancient != 90 {
    67  		t.Fatal("Failed to retrieve ancient")
    68  	}
    69  
    70  	// Test the deleted items shouldn't be reachable
    71  	var cases = []struct {
    72  		start int
    73  		limit int
    74  	}{
    75  		{0, 10},
    76  		{90, 100},
    77  	}
    78  	for _, c := range cases {
    79  		for i := c.start; i < c.limit; i++ {
    80  			exist, err := db.HasAncient("a", uint64(i))
    81  			if err != nil {
    82  				t.Fatalf("Failed to check presence, %v", err)
    83  			}
    84  			if exist {
    85  				t.Fatalf("Item %d is already truncated", uint64(i))
    86  			}
    87  			_, err = db.Ancient("a", uint64(i))
    88  			if err == nil {
    89  				t.Fatal("Error is expected for non-existent item")
    90  			}
    91  		}
    92  	}
    93  
    94  	// Test the items in range should be reachable
    95  	for i := 10; i < 90; i++ {
    96  		exist, err := db.HasAncient("a", uint64(i))
    97  		if err != nil {
    98  			t.Fatalf("Failed to check presence, %v", err)
    99  		}
   100  		if !exist {
   101  			t.Fatalf("Item %d is missing", uint64(i))
   102  		}
   103  		blob, err := db.Ancient("a", uint64(i))
   104  		if err != nil {
   105  			t.Fatalf("Failed to retrieve item, %v", err)
   106  		}
   107  		if !bytes.Equal(blob, data[i]) {
   108  			t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob)
   109  		}
   110  	}
   111  
   112  	// Test the items in unknown table shouldn't be reachable
   113  	exist, err := db.HasAncient("b", uint64(0))
   114  	if err != nil {
   115  		t.Fatalf("Failed to check presence, %v", err)
   116  	}
   117  	if exist {
   118  		t.Fatal("Item in unknown table shouldn't be found")
   119  	}
   120  	_, err = db.Ancient("b", uint64(0))
   121  	if err == nil {
   122  		t.Fatal("Error is expected for unknown table")
   123  	}
   124  }
   125  
   126  func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
   127  	var (
   128  		db   = newFn([]string{"a"})
   129  		data = makeDataset(100, 32)
   130  	)
   131  	defer db.Close()
   132  
   133  	db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   134  		for i := 0; i < 100; i++ {
   135  			op.AppendRaw("a", uint64(i), data[i])
   136  		}
   137  		return nil
   138  	})
   139  	db.TruncateTail(10)
   140  	db.TruncateHead(90)
   141  
   142  	// Test the items in range should be reachable
   143  	var cases = []struct {
   144  		start    uint64
   145  		count    uint64
   146  		maxSize  uint64
   147  		expStart int
   148  		expLimit int
   149  	}{
   150  		// Items in range [10, 90) with no size limitation
   151  		{
   152  			10, 80, 0, 10, 90,
   153  		},
   154  		// Items in range [10, 90) with 32 size cap, single item is expected
   155  		{
   156  			10, 80, 32, 10, 11,
   157  		},
   158  		// Items in range [10, 90) with 31 size cap, single item is expected
   159  		{
   160  			10, 80, 31, 10, 11,
   161  		},
   162  		// Items in range [10, 90) with 32*80 size cap, all items are expected
   163  		{
   164  			10, 80, 32 * 80, 10, 90,
   165  		},
   166  		// Extra items above the last item are not returned
   167  		{
   168  			10, 90, 0, 10, 90,
   169  		},
   170  	}
   171  	for i, c := range cases {
   172  		batch, err := db.AncientRange("a", c.start, c.count, c.maxSize)
   173  		if err != nil {
   174  			t.Fatalf("Failed to retrieve item in range, %v", err)
   175  		}
   176  		if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) {
   177  			t.Fatalf("Case %d, Batch content is not matched", i)
   178  		}
   179  	}
   180  
   181  	// Test out-of-range / zero-size retrieval should be rejected
   182  	_, err := db.AncientRange("a", 0, 1, 0)
   183  	if err == nil {
   184  		t.Fatal("Out-of-range retrieval should be rejected")
   185  	}
   186  	_, err = db.AncientRange("a", 90, 1, 0)
   187  	if err == nil {
   188  		t.Fatal("Out-of-range retrieval should be rejected")
   189  	}
   190  	_, err = db.AncientRange("a", 10, 0, 0)
   191  	if err == nil {
   192  		t.Fatal("Zero-size retrieval should be rejected")
   193  	}
   194  
   195  	// Test item in unknown table shouldn't be reachable
   196  	_, err = db.AncientRange("b", 10, 1, 0)
   197  	if err == nil {
   198  		t.Fatal("Item in unknown table shouldn't be found")
   199  	}
   200  }
   201  
   202  func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
   203  	var (
   204  		db    = newFn([]string{"a", "b"})
   205  		dataA = makeDataset(100, 32)
   206  		dataB = makeDataset(100, 32)
   207  	)
   208  	defer db.Close()
   209  
   210  	// The ancient write to tables should be aligned
   211  	_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   212  		for i := 0; i < 100; i++ {
   213  			op.AppendRaw("a", uint64(i), dataA[i])
   214  		}
   215  		return nil
   216  	})
   217  	if err == nil {
   218  		t.Fatal("Unaligned ancient write should be rejected")
   219  	}
   220  
   221  	// Test normal ancient write
   222  	size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   223  		for i := 0; i < 100; i++ {
   224  			op.AppendRaw("a", uint64(i), dataA[i])
   225  			op.AppendRaw("b", uint64(i), dataB[i])
   226  		}
   227  		return nil
   228  	})
   229  	if err != nil {
   230  		t.Fatalf("Failed to write ancient data %v", err)
   231  	}
   232  	wantSize := int64(6400)
   233  	if size != wantSize {
   234  		t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size)
   235  	}
   236  
   237  	// Write should work after head truncating
   238  	db.TruncateHead(90)
   239  	_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   240  		for i := 90; i < 100; i++ {
   241  			op.AppendRaw("a", uint64(i), dataA[i])
   242  			op.AppendRaw("b", uint64(i), dataB[i])
   243  		}
   244  		return nil
   245  	})
   246  	if err != nil {
   247  		t.Fatalf("Failed to write ancient data %v", err)
   248  	}
   249  
   250  	// Write should work after truncating everything
   251  	db.TruncateTail(0)
   252  	_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   253  		for i := 0; i < 100; i++ {
   254  			op.AppendRaw("a", uint64(i), dataA[i])
   255  			op.AppendRaw("b", uint64(i), dataB[i])
   256  		}
   257  		return nil
   258  	})
   259  	if err != nil {
   260  		t.Fatalf("Failed to write ancient data %v", err)
   261  	}
   262  }
   263  
   264  func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
   265  	db := newFn([]string{"a"})
   266  	defer db.Close()
   267  
   268  	// We write 100 zero-bytes to the freezer and immediately mutate the slice
   269  	db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   270  		data := make([]byte, 100)
   271  		op.AppendRaw("a", uint64(0), data)
   272  		for i := range data {
   273  			data[i] = 0xff
   274  		}
   275  		return nil
   276  	})
   277  	// Now read it.
   278  	data, err := db.Ancient("a", uint64(0))
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  	for k, v := range data {
   283  		if v != 0 {
   284  			t.Fatalf("byte %d != 0: %x", k, v)
   285  		}
   286  	}
   287  }
   288  
   289  // TestResettableAncientSuite runs a suite of tests against a resettable ancient
   290  // database implementation.
   291  func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) {
   292  	t.Run("Reset", func(t *testing.T) {
   293  		var (
   294  			db   = newFn([]string{"a"})
   295  			data = makeDataset(100, 32)
   296  		)
   297  		defer db.Close()
   298  
   299  		db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   300  			for i := 0; i < 100; i++ {
   301  				op.AppendRaw("a", uint64(i), data[i])
   302  			}
   303  			return nil
   304  		})
   305  		db.TruncateTail(10)
   306  		db.TruncateHead(90)
   307  
   308  		// Ancient write should work after resetting
   309  		db.Reset()
   310  		db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   311  			for i := 0; i < 100; i++ {
   312  				op.AppendRaw("a", uint64(i), data[i])
   313  			}
   314  			return nil
   315  		})
   316  	})
   317  }
   318  
   319  func makeDataset(size, value int) [][]byte {
   320  	var vals [][]byte
   321  	for i := 0; i < size; i += 1 {
   322  		vals = append(vals, testrand.Bytes(value))
   323  	}
   324  	return vals
   325  }