github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/approvals/request_tracker_test.go (about)

     1  package approvals
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/stretchr/testify/suite"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/module/mempool"
    13  	"github.com/onflow/flow-go/storage"
    14  	mockstorage "github.com/onflow/flow-go/storage/mock"
    15  	"github.com/onflow/flow-go/utils/unittest"
    16  )
    17  
    18  // TestRequestTracker performs isolated testing of RequestTracker.
    19  // RequestTracker has to lazy initialize items.
    20  // RequestTracker has to properly update items when they are in non-blackout period.
    21  // RequestTracker has to properly prune outdated items by height
    22  func TestRequestTracker(t *testing.T) {
    23  	suite.Run(t, new(RequestTrackerTestSuite))
    24  }
    25  
    26  type RequestTrackerTestSuite struct {
    27  	suite.Suite
    28  	headers *mockstorage.Headers
    29  
    30  	tracker *RequestTracker
    31  }
    32  
    33  func (s *RequestTrackerTestSuite) SetupTest() {
    34  	s.headers = &mockstorage.Headers{}
    35  	s.tracker = NewRequestTracker(s.headers, 1, 3)
    36  }
    37  
    38  // TestTryUpdate_CreateAndUpdate tests that tracker item is lazy initialized and successfully
    39  // updated when blackout period has passed.
    40  func (s *RequestTrackerTestSuite) TestTryUpdate_CreateAndUpdate() {
    41  	executedBlock := unittest.BlockFixture()
    42  	s.headers.On("ByBlockID", executedBlock.ID()).Return(executedBlock.Header, nil)
    43  	result := unittest.ExecutionResultFixture(unittest.WithBlock(&executedBlock))
    44  	chunks := 5
    45  	for i := 0; i < chunks; i++ {
    46  		_, updated, err := s.tracker.TryUpdate(result, executedBlock.ID(), uint64(i))
    47  		require.NoError(s.T(), err)
    48  		require.False(s.T(), updated)
    49  	}
    50  
    51  	// wait for maximum blackout period
    52  	time.Sleep(time.Second * 3)
    53  
    54  	for i := 0; i < chunks; i++ {
    55  		item, updated, err := s.tracker.TryUpdate(result, executedBlock.ID(), uint64(i))
    56  		require.NoError(s.T(), err)
    57  		require.True(s.T(), updated)
    58  		require.Equal(s.T(), uint(1), item.Requests)
    59  	}
    60  }
    61  
    62  // TestTryUpdate_ConcurrentTracking tests that TryUpdate behaves correctly under concurrent updates
    63  func (s *RequestTrackerTestSuite) TestTryUpdate_ConcurrentTracking() {
    64  	s.tracker.blackoutPeriodMax = 0
    65  	s.tracker.blackoutPeriodMin = 0
    66  
    67  	executedBlock := unittest.BlockFixture()
    68  	s.headers.On("ByBlockID", executedBlock.ID()).Return(executedBlock.Header, nil)
    69  	result := unittest.ExecutionResultFixture(unittest.WithBlock(&executedBlock))
    70  	chunks := 5
    71  	var wg sync.WaitGroup
    72  	for times := 0; times < 10; times++ {
    73  		wg.Add(1)
    74  		go func() {
    75  			for i := 0; i < chunks; i++ {
    76  				_, updated, err := s.tracker.TryUpdate(result, executedBlock.ID(), uint64(i))
    77  				require.NoError(s.T(), err)
    78  				require.True(s.T(), updated)
    79  			}
    80  			wg.Done()
    81  		}()
    82  	}
    83  
    84  	wg.Wait()
    85  
    86  	for i := 0; i < chunks; i++ {
    87  		tracker, ok := s.tracker.index[result.ID()][executedBlock.ID()][uint64(i)]
    88  		require.True(s.T(), ok)
    89  		require.Equal(s.T(), uint(10), tracker.Requests)
    90  	}
    91  }
    92  
    93  // TestTryUpdate_UpdateForInvalidResult tests that submitting ER which is referencing invalid block
    94  // results in error.
    95  func (s *RequestTrackerTestSuite) TestTryUpdate_UpdateForInvalidResult() {
    96  	executedBlock := unittest.BlockFixture()
    97  	s.headers.On("ByBlockID", executedBlock.ID()).Return(nil, storage.ErrNotFound)
    98  	result := unittest.ExecutionResultFixture(unittest.WithBlock(&executedBlock))
    99  	_, updated, err := s.tracker.TryUpdate(result, executedBlock.ID(), uint64(0))
   100  	require.Error(s.T(), err)
   101  	require.False(s.T(), updated)
   102  }
   103  
   104  // TestTryUpdate_UpdateForPrunedHeight tests that request tracker doesn't accept items for execution results
   105  // that are lower than our lowest height.
   106  func (s *RequestTrackerTestSuite) TestTryUpdate_UpdateForPrunedHeight() {
   107  	executedBlock := unittest.BlockFixture()
   108  	s.headers.On("ByBlockID", executedBlock.ID()).Return(executedBlock.Header, nil)
   109  	err := s.tracker.PruneUpToHeight(executedBlock.Header.Height + 1)
   110  	require.NoError(s.T(), err)
   111  	result := unittest.ExecutionResultFixture(unittest.WithBlock(&executedBlock))
   112  	_, updated, err := s.tracker.TryUpdate(result, executedBlock.ID(), uint64(0))
   113  	require.Error(s.T(), err)
   114  	require.True(s.T(), mempool.IsBelowPrunedThresholdError(err))
   115  	require.False(s.T(), updated)
   116  }
   117  
   118  // TestPruneUpToHeight_Pruning tests that pruning up to height some height correctly removes needed items
   119  func (s *RequestTrackerTestSuite) TestPruneUpToHeight_Pruning() {
   120  	executedBlock := unittest.BlockFixture()
   121  	nextExecutedBlock := unittest.BlockWithParentFixture(executedBlock.Header)
   122  	s.headers.On("ByBlockID", executedBlock.ID()).Return(executedBlock.Header, nil)
   123  	s.headers.On("ByBlockID", nextExecutedBlock.ID()).Return(nextExecutedBlock.Header, nil)
   124  
   125  	result := unittest.ExecutionResultFixture(unittest.WithBlock(&executedBlock))
   126  	nextResult := unittest.ExecutionResultFixture(unittest.WithBlock(nextExecutedBlock))
   127  
   128  	for _, r := range []*flow.ExecutionResult{result, nextResult} {
   129  		_, updated, err := s.tracker.TryUpdate(r, executedBlock.ID(), uint64(0))
   130  		require.NoError(s.T(), err)
   131  		require.False(s.T(), updated)
   132  	}
   133  
   134  	err := s.tracker.PruneUpToHeight(nextExecutedBlock.Header.Height)
   135  	require.NoError(s.T(), err)
   136  
   137  	_, ok := s.tracker.index[result.ID()]
   138  	require.False(s.T(), ok)
   139  	_, ok = s.tracker.index[nextResult.ID()]
   140  	require.True(s.T(), ok)
   141  }
   142  
   143  // TestPruneUpToHeight_PruningWithHeightBelowPrunedThreshold tests that pruning with height below pruned threshold results in error
   144  func (s *RequestTrackerTestSuite) TestPruneUpToHeight_PruningWithHeightBelowPrunedThreshold() {
   145  	height := uint64(100)
   146  	err := s.tracker.PruneUpToHeight(height + 1)
   147  	require.NoError(s.T(), err)
   148  	err = s.tracker.PruneUpToHeight(height)
   149  	require.Error(s.T(), err)
   150  	require.True(s.T(), mempool.IsBelowPrunedThresholdError(err))
   151  }