github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/cache/gossipsub_spam_records_test.go (about)

     1  package cache_test
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  	"github.com/stretchr/testify/require"
    11  	"go.uber.org/atomic"
    12  
    13  	"github.com/onflow/flow-go/module/metrics"
    14  	"github.com/onflow/flow-go/network/p2p"
    15  	netcache "github.com/onflow/flow-go/network/p2p/cache"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  // TestGossipSubSpamRecordCache_Add tests the Add method of the GossipSubSpamRecordCache. It tests
    20  // adding a new record to the cache.
    21  func TestGossipSubSpamRecordCache_Add(t *testing.T) {
    22  	// create a new instance of GossipSubSpamRecordCache.
    23  	cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
    24  		return p2p.GossipSubSpamRecord{
    25  			Decay:               0,
    26  			Penalty:             0,
    27  			LastDecayAdjustment: time.Now(),
    28  		}
    29  	})
    30  
    31  	adjustedEntity, err := cache.Adjust("peer0", func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
    32  		record.Decay = 0.1
    33  		record.Penalty = 0.5
    34  
    35  		return record
    36  	})
    37  	require.NoError(t, err)
    38  	require.Equal(t, 0.1, adjustedEntity.Decay)
    39  	require.Equal(t, 0.5, adjustedEntity.Penalty)
    40  
    41  	// makes the cache full.
    42  	for i := 1; i <= 100; i++ {
    43  		adjustedEntity, err := cache.Adjust(peer.ID(fmt.Sprintf("peer%d", i)), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
    44  			record.Decay = 0.1
    45  			record.Penalty = 0.5
    46  
    47  			return record
    48  		})
    49  
    50  		require.NoError(t, err)
    51  		require.Equal(t, 0.1, adjustedEntity.Decay)
    52  	}
    53  
    54  	// retrieving an existing record should work.
    55  	for i := 1; i <= 100; i++ {
    56  		record, err, ok := cache.Get(peer.ID(fmt.Sprintf("peer%d", i)))
    57  		require.True(t, ok, fmt.Sprintf("record for peer%d should exist", i))
    58  		require.NoError(t, err)
    59  
    60  		require.Equal(t, 0.1, record.Decay)
    61  		require.Equal(t, 0.5, record.Penalty)
    62  	}
    63  
    64  	// since cache is LRU, the first record should be evicted.
    65  	_, err, ok := cache.Get("peer0")
    66  	require.False(t, ok)
    67  	require.NoError(t, err)
    68  }
    69  
    70  // TestGossipSubSpamRecordCache_Concurrent_Adjust tests if the cache can be adjusted and retrieved concurrently.
    71  // It adjusts the cache with a number of records concurrently and then checks if the cache can retrieve all records.
    72  func TestGossipSubSpamRecordCache_Concurrent_Adjust(t *testing.T) {
    73  	cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
    74  		return p2p.GossipSubSpamRecord{
    75  			Decay:               0,
    76  			Penalty:             0,
    77  			LastDecayAdjustment: time.Now(),
    78  		}
    79  	})
    80  
    81  	// defines the number of records to be adjusted.
    82  	numRecords := 100
    83  
    84  	// uses a wait group to wait for all goroutines to finish.
    85  	var wg sync.WaitGroup
    86  	wg.Add(numRecords)
    87  
    88  	// adds the records concurrently.
    89  	for i := 0; i < numRecords; i++ {
    90  		go func(num int) {
    91  			defer wg.Done()
    92  			peerID := fmt.Sprintf("peer%d", num)
    93  			adjustedEntity, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
    94  				record.Decay = 0.1 * float64(num)
    95  				record.Penalty = float64(num)
    96  
    97  				return record
    98  			})
    99  
   100  			require.NoError(t, err)
   101  			require.Equal(t, 0.1*float64(num), adjustedEntity.Decay)
   102  			require.Equal(t, float64(num), adjustedEntity.Penalty)
   103  		}(i)
   104  	}
   105  
   106  	unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not adjust all records concurrently on time")
   107  
   108  	// checks if the cache can retrieve all records.
   109  	for i := 0; i < numRecords; i++ {
   110  		peerID := fmt.Sprintf("peer%d", i)
   111  		record, err, found := cache.Get(peer.ID(peerID))
   112  		require.True(t, found)
   113  		require.NoError(t, err)
   114  
   115  		expectedPenalty := float64(i)
   116  		require.Equal(t, expectedPenalty, record.Penalty,
   117  			"Get() returned incorrect penalty for record %s: expected %f, got %f", peerID, expectedPenalty, record.Penalty)
   118  		expectedDecay := 0.1 * float64(i)
   119  		require.Equal(t, expectedDecay, record.Decay,
   120  			"Get() returned incorrect decay for record %s: expected %f, got %f", peerID, expectedDecay, record.Decay)
   121  	}
   122  }
   123  
   124  // TestGossipSubSpamRecordCache_Adjust tests the Adjust method of the GossipSubSpamRecordCache. It tests if the cache can adjust
   125  // the penalty of an existing record and add a new record.
   126  func TestGossipSubSpamRecordCache_Adjust(t *testing.T) {
   127  	cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
   128  		return p2p.GossipSubSpamRecord{
   129  			Decay:               0,
   130  			Penalty:             0,
   131  			LastDecayAdjustment: time.Now(),
   132  		}
   133  	})
   134  
   135  	peerID := "peer1"
   136  
   137  	// test adjusting a non-existing record.
   138  	record, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   139  		record.Penalty = 0.7
   140  		return record
   141  	})
   142  	require.NoError(t, err)
   143  	require.Equal(t, 0.7, record.Penalty) // checks if the penalty is adjusted correctly.
   144  
   145  	// test adjusting an existing record.
   146  	record, err = cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   147  		record.Penalty = 0.8
   148  		return record
   149  	})
   150  	require.NoError(t, err)
   151  	require.Equal(t, 0.8, record.Penalty) // checks if the penalty is adjusted correctly.
   152  }
   153  
   154  // TestGossipSubSpamRecordCache_Adjust_With_Preprocess tests Adjust method of the GossipSubSpamRecordCache when the cache
   155  // has preprocessor functions.
   156  // It tests when the cache has preprocessor functions, all preprocessor functions are called prior to the adjust function.
   157  // Also, it tests if the pre-processor functions are called in the order they are added.
   158  func TestGossipSubSpamRecordCache_Adjust_With_Preprocess(t *testing.T) {
   159  	cache := netcache.NewGossipSubSpamRecordCache(200,
   160  		unittest.Logger(),
   161  		metrics.NewNoopCollector(),
   162  		func() p2p.GossipSubSpamRecord {
   163  			return p2p.GossipSubSpamRecord{
   164  				Decay:               0,
   165  				Penalty:             0,
   166  				LastDecayAdjustment: time.Now(),
   167  			}
   168  		},
   169  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   170  			record.Penalty += 1.5
   171  			return record, nil
   172  		}, func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   173  			record.Penalty *= 2
   174  			return record, nil
   175  		})
   176  
   177  	peerID := "peer1"
   178  
   179  	// test adjusting a non-existing record.
   180  	record, err := cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   181  		record.Penalty = 0.5
   182  		record.Decay = 0.1
   183  		return record
   184  	})
   185  	require.NoError(t, err)
   186  	require.Equal(t, 0.5, record.Penalty) // checks if the penalty is adjusted correctly.
   187  	require.Equal(t, 0.1, record.Decay)   // checks if the decay is adjusted correctly.
   188  
   189  	// tests updating the penalty of an existing record.
   190  	record, err = cache.Adjust(peer.ID(peerID), func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   191  		record.Penalty += 0.7
   192  		return record
   193  	})
   194  	require.NoError(t, err)
   195  	require.Equal(t, 4.7, record.Penalty) // (0.5+1.5) * 2 + 0.7 = 4.7
   196  	require.Equal(t, 0.1, record.Decay)   // checks if the decay is not changed.
   197  }
   198  
   199  // TestGossipSubSpamRecordCache_Adjust_Preprocess_Error tests the Adjust method of the GossipSubSpamRecordCache.
   200  // It tests if any of the preprocessor functions returns an error, the Adjust function effect
   201  // is reverted, and the error is returned.
   202  func TestGossipSubSpamRecordCache_Adjust_Preprocess_Error(t *testing.T) {
   203  	secondPreprocessorCalled := 0
   204  	cache := netcache.NewGossipSubSpamRecordCache(200,
   205  		unittest.Logger(),
   206  		metrics.NewNoopCollector(),
   207  		func() p2p.GossipSubSpamRecord {
   208  			return p2p.GossipSubSpamRecord{
   209  				Decay:               0,
   210  				Penalty:             0,
   211  				LastDecayAdjustment: time.Now(),
   212  			}
   213  		},
   214  		// the first preprocessor function does not return an error.
   215  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   216  			return record, nil
   217  		},
   218  		// the second preprocessor function returns an error on the second call, and does not return an error on any other call.
   219  		// this means that adjustment should be successful on the first call, and should fail on the second call.
   220  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   221  			secondPreprocessorCalled++
   222  			if secondPreprocessorCalled == 2 {
   223  				return record, fmt.Errorf("some error")
   224  			}
   225  			return record, nil
   226  
   227  		})
   228  
   229  	peerID := unittest.PeerIdFixture(t)
   230  
   231  	// tests adjusting the penalty of a non-existing record; the record should be initiated and the penalty should be adjusted.
   232  	record, err := cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   233  		record.Penalty = 0.5
   234  		record.Decay = 0.1
   235  		return record
   236  	})
   237  	require.NoError(t, err)
   238  	require.NotNil(t, record)
   239  	require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed.
   240  	require.Equal(t, 0.1, record.Decay)   // checks if the decay is not changed.
   241  
   242  	// tests adjusting the penalty of an existing record.
   243  	record, err = cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   244  		record.Penalty = 0.7
   245  		return record
   246  	})
   247  	// since the second preprocessor function returns an error, the adjust function effect should be reverted.
   248  	// the error should be returned.
   249  	require.Error(t, err)
   250  	require.Nil(t, record)
   251  
   252  	// checks if the record is not changed.
   253  	record, err, found := cache.Get(peerID)
   254  	require.True(t, found)
   255  	require.NoError(t, err)
   256  	require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed.
   257  	require.Equal(t, 0.1, record.Decay)   // checks if the decay is not changed.
   258  }
   259  
   260  // TestGossipSubSpamRecordCache_ByValue tests if the cache stores the GossipSubSpamRecord by value.
   261  // It adjusts the cache with a record and then modifies the record externally.
   262  // It then checks if the record in the cache is still the original record.
   263  // This is a desired behavior that is guaranteed by the underlying HeroCache library.
   264  // In other words, we don't desire the records to be externally mutable after they are added to the cache (unless by a subsequent call to Adjust).
   265  func TestGossipSubSpamRecordCache_ByValue(t *testing.T) {
   266  	cache := netcache.NewGossipSubSpamRecordCache(200, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
   267  		return p2p.GossipSubSpamRecord{
   268  			Decay:               0,
   269  			Penalty:             0,
   270  			LastDecayAdjustment: time.Now(),
   271  		}
   272  	})
   273  
   274  	peerID := unittest.PeerIdFixture(t)
   275  	// adjusts a non-existing record, which should initiate the record.
   276  	record, err := cache.Adjust(peerID, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   277  		record.Penalty = 0.5
   278  		record.Decay = 0.1
   279  		return record
   280  	})
   281  	require.NoError(t, err)
   282  	require.NotNil(t, record)
   283  	require.Equal(t, 0.5, record.Penalty) // checks if the penalty is not changed.
   284  	require.Equal(t, 0.1, record.Decay)   // checks if the decay is not changed.
   285  
   286  	// get the record from the cache
   287  	record, err, found := cache.Get(peerID)
   288  	require.True(t, found)
   289  	require.NoError(t, err)
   290  
   291  	// modify the record
   292  	record.Decay = 0.2
   293  	record.Penalty = 0.8
   294  
   295  	// get the record from the cache again
   296  	record, err, found = cache.Get(peerID)
   297  	require.True(t, found)
   298  	require.NoError(t, err)
   299  
   300  	// check if the record is still the same
   301  	require.Equal(t, 0.1, record.Decay)
   302  	require.Equal(t, 0.5, record.Penalty)
   303  }
   304  
   305  // TestGossipSubSpamRecordCache_Get_With_Preprocessors tests if the cache applies the preprocessors to the records before returning them.
   306  func TestGossipSubSpamRecordCache_Get_With_Preprocessors(t *testing.T) {
   307  	cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(),
   308  		func() p2p.GossipSubSpamRecord {
   309  			return p2p.GossipSubSpamRecord{
   310  				Decay:               0,
   311  				Penalty:             0,
   312  				LastDecayAdjustment: time.Now(),
   313  			}
   314  		},
   315  		// first preprocessor: adds 1 to the penalty.
   316  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   317  			record.Penalty++
   318  			return record, nil
   319  		},
   320  		// second preprocessor: multiplies the penalty by 2
   321  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   322  			record.Penalty *= 2
   323  			return record, nil
   324  		},
   325  	)
   326  
   327  	peerId := unittest.PeerIdFixture(t)
   328  	adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   329  		record.Penalty = 1
   330  		record.Decay = 0.5
   331  		return record
   332  	})
   333  	require.NoError(t, err)
   334  	require.Equal(t, 1.0, adjustedRecord.Penalty)
   335  
   336  	// verifies that the preprocessors were called and the record was adjusted accordingly.
   337  	cachedRecord, err, ok := cache.Get(peerId)
   338  	require.NoError(t, err)
   339  	require.True(t, ok)
   340  
   341  	// expected penalty is 4: the first preprocessor adds 1 to the penalty and the second preprocessor multiplies the penalty by 2.
   342  	// (1 + 1) * 2 = 4
   343  	require.Equal(t, 4.0, cachedRecord.Penalty) // penalty should be adjusted
   344  	require.Equal(t, 0.5, cachedRecord.Decay)   // decay should not be modified
   345  }
   346  
   347  // TestGossipSubSpamRecordCache_Get_Preprocessor_Error tests if the cache returns an error if one of the preprocessors returns an error upon a Get.
   348  // It adds a record to the cache and then checks if the cache returns an error upon a Get if one of the preprocessors returns an error.
   349  // It also checks if a preprocessor is failed, the subsequent preprocessors are not called, and the original record is returned.
   350  // In other words, the Get method acts atomically on the record for applying the preprocessors. If one of the preprocessors
   351  // fails, the record is returned without applying the subsequent preprocessors.
   352  func TestGossipSubSpamRecordCache_Get_Preprocessor_Error(t *testing.T) {
   353  	secondPreprocessorCalledCount := 0
   354  	thirdPreprocessorCalledCount := 0
   355  
   356  	cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(),
   357  		func() p2p.GossipSubSpamRecord {
   358  			return p2p.GossipSubSpamRecord{
   359  				Decay:               0,
   360  				Penalty:             0,
   361  				LastDecayAdjustment: time.Now(),
   362  			}
   363  		},
   364  		// first preprocessor: adds 1 to the penalty.
   365  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   366  			record.Penalty++
   367  			return record, nil
   368  		},
   369  		// second preprocessor: multiplies the penalty by 2 (this preprocessor returns an error on the third call and forward)
   370  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   371  			secondPreprocessorCalledCount++
   372  			if secondPreprocessorCalledCount < 3 {
   373  				// on the first call, the preprocessor is successful
   374  				return record, nil
   375  			} else {
   376  				// on the second call, the preprocessor returns an error
   377  				return p2p.GossipSubSpamRecord{}, fmt.Errorf("error in preprocessor")
   378  			}
   379  		},
   380  		// since second preprocessor returns an error on the second call, the third preprocessor should not be called more than once.
   381  		func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) {
   382  			thirdPreprocessorCalledCount++
   383  			require.Less(t, secondPreprocessorCalledCount, 3)
   384  			return record, nil
   385  		},
   386  	)
   387  
   388  	peerId := unittest.PeerIdFixture(t)
   389  	adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   390  		record.Penalty = 1
   391  		record.Decay = 0.5
   392  		return record
   393  	})
   394  	require.NoError(t, err)
   395  	require.Equal(t, 1.0, adjustedRecord.Penalty)
   396  	require.Equal(t, 0.5, adjustedRecord.Decay)
   397  
   398  	// verifies that the preprocessors were called and the penalty was adjusted accordingly.
   399  	cachedRecord, err, ok := cache.Get(peerId)
   400  	require.NoError(t, err)
   401  	require.True(t, ok)
   402  	require.Equal(t, 2.0, cachedRecord.Penalty) // penalty should be adjusted by the first preprocessor (1 + 1 = 2)
   403  	require.Equal(t, 0.5, cachedRecord.Decay)
   404  
   405  	// query the cache again that should trigger the second preprocessor to return an error.
   406  	cachedRecord, err, ok = cache.Get(peerId)
   407  	require.Error(t, err)
   408  	require.False(t, ok)
   409  	require.Nil(t, cachedRecord)
   410  
   411  	// verifies that the third preprocessor was called only twice (two success calls).
   412  	require.Equal(t, 2, thirdPreprocessorCalledCount)
   413  	// verifies that the second preprocessor was called three times (two success calls and one failure call).
   414  	require.Equal(t, 3, secondPreprocessorCalledCount)
   415  }
   416  
   417  // TestGossipSubSpamRecordCache_Get_Without_Preprocessors tests when no preprocessors are provided to the cache constructor
   418  // that the cache returns the original record without any modifications.
   419  func TestGossipSubSpamRecordCache_Get_Without_Preprocessors(t *testing.T) {
   420  	cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
   421  		return p2p.GossipSubSpamRecord{
   422  			Decay:               0,
   423  			Penalty:             0,
   424  			LastDecayAdjustment: time.Now(),
   425  		}
   426  	})
   427  
   428  	peerId := unittest.PeerIdFixture(t)
   429  	adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   430  		record.Penalty = 1
   431  		record.Decay = 0.5
   432  		return record
   433  	})
   434  	require.NoError(t, err)
   435  	require.Equal(t, 1.0, adjustedRecord.Penalty)
   436  	require.Equal(t, 0.5, adjustedRecord.Decay)
   437  
   438  	// verifies that no preprocessors were called and the record was not adjusted.
   439  	cachedRecord, err, ok := cache.Get(peerId)
   440  	require.NoError(t, err)
   441  	require.True(t, ok)
   442  	require.Equal(t, 1.0, cachedRecord.Penalty)
   443  	require.Equal(t, 0.5, cachedRecord.Decay)
   444  }
   445  
   446  // TestGossipSubSpamRecordCache_Duplicate_Adjust_Sequential tests if the cache returns false when a duplicate record is added to the cache.
   447  // This test evaluates that the cache de-duplicates the records based on their peer id and not content, and hence
   448  // each peer id can only be added once to the cache.
   449  func TestGossipSubSpamRecordCache_Duplicate_Adjust_Sequential(t *testing.T) {
   450  	cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
   451  		return p2p.GossipSubSpamRecord{
   452  			Decay:               0,
   453  			Penalty:             0,
   454  			LastDecayAdjustment: time.Now(),
   455  		}
   456  	})
   457  
   458  	peerId := unittest.PeerIdFixture(t)
   459  	adjustedRecord, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   460  		record.Penalty = 1
   461  		record.Decay = 0.5
   462  		return record
   463  	})
   464  	require.NoError(t, err)
   465  	require.Equal(t, 1.0, adjustedRecord.Penalty)
   466  	require.Equal(t, 0.5, adjustedRecord.Decay)
   467  
   468  	// duplicate adjust should return the same record.
   469  	adjustedRecord, err = cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   470  		record.Penalty = 1
   471  		record.Decay = 0.5
   472  		return record
   473  	})
   474  	require.NoError(t, err)
   475  	require.Equal(t, 1.0, adjustedRecord.Penalty)
   476  	require.Equal(t, 0.5, adjustedRecord.Decay)
   477  
   478  	// verifies that the cache deduplicates the records based on their peer id and not content.
   479  	adjustedRecord, err = cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   480  		record.Penalty = 3
   481  		record.Decay = 2
   482  		return record
   483  	})
   484  	require.NoError(t, err)
   485  	require.Equal(t, 3.0, adjustedRecord.Penalty)
   486  	require.Equal(t, 2.0, adjustedRecord.Decay)
   487  }
   488  
   489  // TestGossipSubSpamRecordCache_Duplicate_Adjust_Concurrent tests if the cache returns false when a duplicate record is added to the cache.
   490  // Test is the concurrent version of TestAppScoreCache_Duplicate_Adjust_Sequential.
   491  func TestGossipSubSpamRecordCache_Duplicate_Adjust_Concurrent(t *testing.T) {
   492  	cache := netcache.NewGossipSubSpamRecordCache(10, unittest.Logger(), metrics.NewNoopCollector(), func() p2p.GossipSubSpamRecord {
   493  		return p2p.GossipSubSpamRecord{
   494  			Decay:               0,
   495  			Penalty:             0,
   496  			LastDecayAdjustment: time.Now(),
   497  		}
   498  	})
   499  
   500  	successAdd := atomic.Int32{}
   501  	successAdd.Store(0)
   502  
   503  	record1 := p2p.GossipSubSpamRecord{
   504  		Decay:   1,
   505  		Penalty: 1,
   506  	}
   507  
   508  	record2 := p2p.GossipSubSpamRecord{
   509  		Decay:   1,
   510  		Penalty: 2,
   511  	}
   512  
   513  	wg := sync.WaitGroup{} // wait group to wait for all goroutines to finish.
   514  	wg.Add(2)
   515  	peerId := unittest.PeerIdFixture(t)
   516  	// adds a record to the cache concurrently.
   517  	add := func(newRecord p2p.GossipSubSpamRecord) {
   518  		_, err := cache.Adjust(peerId, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord {
   519  			record.Penalty = newRecord.Penalty
   520  			record.Decay = newRecord.Decay
   521  			record.LastDecayAdjustment = newRecord.LastDecayAdjustment
   522  			return record
   523  		})
   524  		require.NoError(t, err)
   525  		successAdd.Inc()
   526  
   527  		wg.Done()
   528  	}
   529  
   530  	go add(record1)
   531  	go add(record2)
   532  
   533  	unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "could not add records to the cache")
   534  
   535  	// verifies that both of the records was added to the cache.
   536  	require.Equal(t, int32(2), successAdd.Load())
   537  
   538  	// verifies that the record is adjusted to one of the records.
   539  	cachedRecord, err, ok := cache.Get(peerId)
   540  	require.NoError(t, err)
   541  	require.True(t, ok)
   542  	require.True(t, cachedRecord.Penalty == 1 && cachedRecord.Decay == 1 || cachedRecord.Penalty == 2 && cachedRecord.Decay == 1)
   543  }