github.com/MetalBlockchain/metalgo@v1.11.9/chains/atomic/test_shared_memory.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package atomic
     5  
     6  import (
     7  	"math/rand"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/MetalBlockchain/metalgo/database"
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/utils/units"
    15  )
    16  
    17  // SharedMemoryTests is a list of all shared memory tests
    18  var SharedMemoryTests = []func(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 SharedMemory, db database.Database){
    19  	TestSharedMemoryPutAndGet,
    20  	TestSharedMemoryLargePutGetAndRemove,
    21  	TestSharedMemoryIndexed,
    22  	TestSharedMemoryLargeIndexed,
    23  	TestSharedMemoryCantDuplicatePut,
    24  	TestSharedMemoryCantDuplicateRemove,
    25  	TestSharedMemoryCommitOnPut,
    26  	TestSharedMemoryCommitOnRemove,
    27  	TestSharedMemoryLargeBatchSize,
    28  	TestPutAndRemoveBatch,
    29  }
    30  
    31  func TestSharedMemoryPutAndGet(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 SharedMemory, _ database.Database) {
    32  	require := require.New(t)
    33  
    34  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
    35  		Key:   []byte{0},
    36  		Value: []byte{1},
    37  	}}}}))
    38  
    39  	values, err := sm1.Get(chainID0, [][]byte{{0}})
    40  	require.NoError(err)
    41  	require.Equal([][]byte{{1}}, values, "wrong values returned")
    42  }
    43  
    44  // TestSharedMemoryLargePutGetAndRemove tests to make sure that the interface
    45  // can support large values.
    46  func TestSharedMemoryLargePutGetAndRemove(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 SharedMemory, _ database.Database) {
    47  	require := require.New(t)
    48  	rand.Seed(0)
    49  
    50  	totalSize := 16 * units.MiB  // 16 MiB
    51  	elementSize := 4 * units.KiB // 4 KiB
    52  	pairSize := 2 * elementSize  // 8 KiB
    53  
    54  	b := make([]byte, totalSize)
    55  	_, err := rand.Read(b) // #nosec G404
    56  	require.NoError(err)
    57  
    58  	elems := []*Element{}
    59  	keys := [][]byte{}
    60  	for len(b) > pairSize {
    61  		key := b[:elementSize]
    62  		b = b[elementSize:]
    63  
    64  		value := b[:elementSize]
    65  		b = b[elementSize:]
    66  
    67  		elems = append(elems, &Element{
    68  			Key:   key,
    69  			Value: value,
    70  		})
    71  		keys = append(keys, key)
    72  	}
    73  
    74  	require.NoError(sm0.Apply(map[ids.ID]*Requests{
    75  		chainID1: {
    76  			PutRequests: elems,
    77  		},
    78  	}))
    79  
    80  	values, err := sm1.Get(
    81  		chainID0,
    82  		keys,
    83  	)
    84  	require.NoError(err)
    85  	for i, value := range values {
    86  		require.Equal(elems[i].Value, value)
    87  	}
    88  
    89  	require.NoError(sm1.Apply(map[ids.ID]*Requests{
    90  		chainID0: {
    91  			RemoveRequests: keys,
    92  		},
    93  	}))
    94  }
    95  
    96  func TestSharedMemoryIndexed(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 SharedMemory, _ database.Database) {
    97  	require := require.New(t)
    98  
    99  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
   100  		Key:   []byte{0},
   101  		Value: []byte{1},
   102  		Traits: [][]byte{
   103  			{2},
   104  			{3},
   105  		},
   106  	}}}}))
   107  
   108  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
   109  		Key:   []byte{4},
   110  		Value: []byte{5},
   111  		Traits: [][]byte{
   112  			{2},
   113  			{3},
   114  		},
   115  	}}}}))
   116  
   117  	values, _, _, err := sm0.Indexed(chainID1, [][]byte{{2}}, nil, nil, 1)
   118  	require.NoError(err)
   119  	require.Empty(values, "wrong indexed values returned")
   120  
   121  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 0)
   122  	require.NoError(err)
   123  	require.Empty(values, "wrong indexed values returned")
   124  
   125  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 1)
   126  	require.NoError(err)
   127  	require.Equal([][]byte{{5}}, values, "wrong indexed values returned")
   128  
   129  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 2)
   130  	require.NoError(err)
   131  	require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned")
   132  
   133  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 3)
   134  	require.NoError(err)
   135  	require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned")
   136  
   137  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{3}}, nil, nil, 3)
   138  	require.NoError(err)
   139  	require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned")
   140  
   141  	values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}, {3}}, nil, nil, 3)
   142  	require.NoError(err)
   143  	require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned")
   144  }
   145  
   146  func TestSharedMemoryLargeIndexed(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 SharedMemory, _ database.Database) {
   147  	require := require.New(t)
   148  
   149  	totalSize := 8 * units.MiB   // 8 MiB
   150  	elementSize := 1 * units.KiB // 1 KiB
   151  	pairSize := 3 * elementSize  // 3 KiB
   152  
   153  	b := make([]byte, totalSize)
   154  	_, err := rand.Read(b) // #nosec G404
   155  	require.NoError(err)
   156  
   157  	elems := []*Element{}
   158  	allTraits := [][]byte{}
   159  	for len(b) > pairSize {
   160  		key := b[:elementSize]
   161  		b = b[elementSize:]
   162  
   163  		value := b[:elementSize]
   164  		b = b[elementSize:]
   165  
   166  		traits := [][]byte{
   167  			b[:elementSize],
   168  		}
   169  		allTraits = append(allTraits, traits...)
   170  		b = b[elementSize:]
   171  
   172  		elems = append(elems, &Element{
   173  			Key:    key,
   174  			Value:  value,
   175  			Traits: traits,
   176  		})
   177  	}
   178  
   179  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: elems}}))
   180  
   181  	values, _, _, err := sm1.Indexed(chainID0, allTraits, nil, nil, len(elems)+1)
   182  	require.NoError(err)
   183  	require.Len(values, len(elems), "wrong number of values returned")
   184  }
   185  
   186  func TestSharedMemoryCantDuplicatePut(t *testing.T, _, chainID1 ids.ID, sm0, _ SharedMemory, _ database.Database) {
   187  	require := require.New(t)
   188  
   189  	err := sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{
   190  		{
   191  			Key:   []byte{0},
   192  			Value: []byte{1},
   193  		},
   194  		{
   195  			Key:   []byte{0},
   196  			Value: []byte{2},
   197  		},
   198  	}}})
   199  	// TODO: require error to be errDuplicatedOperation
   200  	require.Error(err) //nolint:forbidigo // currently returns grpc errors too
   201  
   202  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
   203  		Key:   []byte{0},
   204  		Value: []byte{1},
   205  	}}}}))
   206  
   207  	err = sm0.Apply(map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
   208  		Key:   []byte{0},
   209  		Value: []byte{1},
   210  	}}}})
   211  	// TODO: require error to be errDuplicatedOperation
   212  	require.Error(err) //nolint:forbidigo // currently returns grpc errors too
   213  }
   214  
   215  func TestSharedMemoryCantDuplicateRemove(t *testing.T, _, chainID1 ids.ID, sm0, _ SharedMemory, _ database.Database) {
   216  	require := require.New(t)
   217  
   218  	require.NoError(sm0.Apply(map[ids.ID]*Requests{chainID1: {RemoveRequests: [][]byte{{0}}}}))
   219  
   220  	err := sm0.Apply(map[ids.ID]*Requests{chainID1: {RemoveRequests: [][]byte{{0}}}})
   221  	// TODO: require error to be errDuplicatedOperation
   222  	require.Error(err) //nolint:forbidigo // currently returns grpc errors too
   223  }
   224  
   225  func TestSharedMemoryCommitOnPut(t *testing.T, _, chainID1 ids.ID, sm0, _ SharedMemory, db database.Database) {
   226  	require := require.New(t)
   227  
   228  	require.NoError(db.Put([]byte{1}, []byte{2}))
   229  
   230  	batch := db.NewBatch()
   231  
   232  	require.NoError(batch.Put([]byte{0}, []byte{1}))
   233  
   234  	require.NoError(batch.Delete([]byte{1}))
   235  
   236  	require.NoError(sm0.Apply(
   237  		map[ids.ID]*Requests{chainID1: {PutRequests: []*Element{{
   238  			Key:   []byte{0},
   239  			Value: []byte{1},
   240  		}}}},
   241  		batch,
   242  	))
   243  
   244  	val, err := db.Get([]byte{0})
   245  	require.NoError(err)
   246  	require.Equal([]byte{1}, val)
   247  
   248  	has, err := db.Has([]byte{1})
   249  	require.NoError(err)
   250  	require.False(has)
   251  }
   252  
   253  func TestSharedMemoryCommitOnRemove(t *testing.T, _, chainID1 ids.ID, sm0, _ SharedMemory, db database.Database) {
   254  	require := require.New(t)
   255  
   256  	require.NoError(db.Put([]byte{1}, []byte{2}))
   257  
   258  	batch := db.NewBatch()
   259  
   260  	require.NoError(batch.Put([]byte{0}, []byte{1}))
   261  
   262  	require.NoError(batch.Delete([]byte{1}))
   263  
   264  	require.NoError(sm0.Apply(
   265  		map[ids.ID]*Requests{chainID1: {RemoveRequests: [][]byte{{0}}}},
   266  		batch,
   267  	))
   268  
   269  	val, err := db.Get([]byte{0})
   270  	require.NoError(err)
   271  	require.Equal([]byte{1}, val)
   272  
   273  	has, err := db.Has([]byte{1})
   274  	require.NoError(err)
   275  	require.False(has)
   276  }
   277  
   278  // TestPutAndRemoveBatch tests to make sure multiple put and remove requests work properly
   279  func TestPutAndRemoveBatch(t *testing.T, chainID0, _ ids.ID, _, sm1 SharedMemory, db database.Database) {
   280  	require := require.New(t)
   281  
   282  	batch := db.NewBatch()
   283  
   284  	require.NoError(batch.Put([]byte{0}, []byte{1}))
   285  
   286  	batchChainsAndInputs := make(map[ids.ID]*Requests)
   287  
   288  	byteArr := [][]byte{{0}, {1}, {5}}
   289  
   290  	batchChainsAndInputs[chainID0] = &Requests{
   291  		PutRequests: []*Element{{
   292  			Key:   []byte{2},
   293  			Value: []byte{9},
   294  		}},
   295  		RemoveRequests: byteArr,
   296  	}
   297  
   298  	require.NoError(sm1.Apply(batchChainsAndInputs, batch))
   299  
   300  	val, err := db.Get([]byte{0})
   301  	require.NoError(err)
   302  	require.Equal([]byte{1}, val)
   303  }
   304  
   305  // TestSharedMemoryLargeBatchSize tests to make sure that the interface can
   306  // support large batches.
   307  func TestSharedMemoryLargeBatchSize(t *testing.T, _, chainID1 ids.ID, sm0, _ SharedMemory, db database.Database) {
   308  	require := require.New(t)
   309  	rand.Seed(0)
   310  
   311  	totalSize := 8 * units.MiB   // 8 MiB
   312  	elementSize := 4 * units.KiB // 4 KiB
   313  	pairSize := 2 * elementSize  // 8 KiB
   314  
   315  	bytes := make([]byte, totalSize)
   316  	_, err := rand.Read(bytes) // #nosec G404
   317  	require.NoError(err)
   318  
   319  	batch := db.NewBatch()
   320  	require.NotNil(batch)
   321  
   322  	initialBytes := bytes
   323  	for len(bytes) > pairSize {
   324  		key := bytes[:elementSize]
   325  		bytes = bytes[elementSize:]
   326  
   327  		value := bytes[:elementSize]
   328  		bytes = bytes[elementSize:]
   329  
   330  		require.NoError(batch.Put(key, value))
   331  	}
   332  
   333  	require.NoError(db.Put([]byte{1}, []byte{2}))
   334  
   335  	require.NoError(batch.Put([]byte{0}, []byte{1}))
   336  
   337  	require.NoError(batch.Delete([]byte{1}))
   338  
   339  	require.NoError(sm0.Apply(
   340  		map[ids.ID]*Requests{chainID1: {RemoveRequests: [][]byte{{0}}}},
   341  		batch,
   342  	))
   343  
   344  	val, err := db.Get([]byte{0})
   345  	require.NoError(err)
   346  	require.Equal([]byte{1}, val)
   347  
   348  	has, err := db.Has([]byte{1})
   349  	require.NoError(err)
   350  	require.False(has)
   351  
   352  	batch.Reset()
   353  
   354  	bytes = initialBytes
   355  	for len(bytes) > pairSize {
   356  		key := bytes[:elementSize]
   357  		bytes = bytes[pairSize:]
   358  
   359  		require.NoError(batch.Delete(key))
   360  	}
   361  
   362  	require.NoError(sm0.Apply(
   363  		map[ids.ID]*Requests{chainID1: {RemoveRequests: [][]byte{{1}}}},
   364  		batch,
   365  	))
   366  
   367  	batch.Reset()
   368  
   369  	bytes = initialBytes
   370  	for len(bytes) > pairSize {
   371  		key := bytes[:elementSize]
   372  		bytes = bytes[pairSize:]
   373  
   374  		require.NoError(batch.Delete(key))
   375  	}
   376  
   377  	batchChainsAndInputs := make(map[ids.ID]*Requests)
   378  
   379  	byteArr := [][]byte{{30}, {40}, {50}}
   380  
   381  	batchChainsAndInputs[chainID1] = &Requests{
   382  		PutRequests: []*Element{{
   383  			Key:   []byte{2},
   384  			Value: []byte{9},
   385  		}},
   386  		RemoveRequests: byteArr,
   387  	}
   388  
   389  	require.NoError(sm0.Apply(
   390  		batchChainsAndInputs,
   391  		batch,
   392  	))
   393  }