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