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 }