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

     1  package approvals
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/gammazero/workerpool"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/stretchr/testify/suite"
    13  
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/utils/unittest"
    16  )
    17  
    18  // AssignmentCollectorStateMachineTestSuite is a test suite for testing AssignmentCollectorStateMachine. Contains a minimal set of
    19  // helper mocks to test the behavior.
    20  type AssignmentCollectorStateMachineTestSuite struct {
    21  	BaseAssignmentCollectorTestSuite
    22  	collector *AssignmentCollectorStateMachine
    23  }
    24  
    25  func TestAssignmentCollectorStateMachine(t *testing.T) {
    26  	suite.Run(t, new(AssignmentCollectorStateMachineTestSuite))
    27  }
    28  
    29  func (s *AssignmentCollectorStateMachineTestSuite) SetupTest() {
    30  	s.BaseAssignmentCollectorTestSuite.SetupTest()
    31  
    32  	s.collector = NewAssignmentCollectorStateMachine(AssignmentCollectorBase{
    33  		workerPool:                           workerpool.New(4),
    34  		assigner:                             s.Assigner,
    35  		state:                                s.State,
    36  		headers:                              s.Headers,
    37  		sigHasher:                            s.SigHasher,
    38  		seals:                                s.SealsPL,
    39  		approvalConduit:                      s.Conduit,
    40  		requestTracker:                       s.RequestTracker,
    41  		requiredApprovalsForSealConstruction: 5,
    42  		executedBlock:                        s.Block,
    43  		result:                               s.IncorporatedResult.Result,
    44  		resultID:                             s.IncorporatedResult.Result.ID(),
    45  	})
    46  }
    47  
    48  // TestChangeProcessingStatus_CachingToVerifying tests that state machine correctly performs transition from CachingApprovals to
    49  // VerifyingApprovals state. After transition all caches approvals and results need to be applied to new state.
    50  func (s *AssignmentCollectorStateMachineTestSuite) TestChangeProcessingStatus_CachingToVerifying() {
    51  	require.Equal(s.T(), CachingApprovals, s.collector.ProcessingStatus())
    52  	results := make([]*flow.IncorporatedResult, 3)
    53  
    54  	s.PublicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil)
    55  
    56  	for i := range results {
    57  		block := unittest.BlockHeaderWithParentFixture(s.Block)
    58  		s.Blocks[block.ID()] = block
    59  		result := unittest.IncorporatedResult.Fixture(
    60  			unittest.IncorporatedResult.WithIncorporatedBlockID(block.ID()),
    61  			unittest.IncorporatedResult.WithResult(s.IncorporatedResult.Result),
    62  		)
    63  		results[i] = result
    64  	}
    65  
    66  	approvals := make([]*flow.ResultApproval, s.Chunks.Len())
    67  
    68  	for i := range approvals {
    69  		approval := unittest.ResultApprovalFixture(
    70  			unittest.WithExecutionResultID(s.IncorporatedResult.Result.ID()),
    71  			unittest.WithChunk(uint64(i)),
    72  			unittest.WithApproverID(s.VerID),
    73  			unittest.WithBlockID(s.Block.ID()),
    74  		)
    75  		approvals[i] = approval
    76  	}
    77  
    78  	var wg sync.WaitGroup
    79  	wg.Add(1)
    80  	go func() {
    81  		defer wg.Done()
    82  		for _, result := range results {
    83  			require.NoError(s.T(), s.collector.ProcessIncorporatedResult(result))
    84  		}
    85  	}()
    86  
    87  	wg.Add(1)
    88  	go func() {
    89  		defer wg.Done()
    90  		for _, approval := range approvals {
    91  			require.NoError(s.T(), s.collector.ProcessApproval(approval))
    92  		}
    93  	}()
    94  
    95  	err := s.collector.ChangeProcessingStatus(CachingApprovals, VerifyingApprovals)
    96  	require.NoError(s.T(), err)
    97  	require.Equal(s.T(), VerifyingApprovals, s.collector.ProcessingStatus())
    98  
    99  	wg.Wait()
   100  
   101  	// give some time to process on worker pool
   102  	time.Sleep(1 * time.Second)
   103  	// need to check if collector has processed cached items
   104  	verifyingCollector, ok := s.collector.atomicLoadCollector().(*VerifyingAssignmentCollector)
   105  	require.True(s.T(), ok)
   106  
   107  	for _, ir := range results {
   108  		verifyingCollector.lock.Lock()
   109  		collector, ok := verifyingCollector.collectors[ir.IncorporatedBlockID]
   110  		verifyingCollector.lock.Unlock()
   111  		require.True(s.T(), ok)
   112  
   113  		for _, approval := range approvals {
   114  			chunkCollector := collector.chunkCollectors[approval.Body.ChunkIndex]
   115  			chunkCollector.lock.Lock()
   116  			signed := chunkCollector.chunkApprovals.HasSigned(approval.Body.ApproverID)
   117  			chunkCollector.lock.Unlock()
   118  			require.True(s.T(), signed)
   119  		}
   120  	}
   121  }
   122  
   123  // TestChangeProcessingStatus_InvalidTransition tries to perform transition from caching to verifying status
   124  // but with underlying orphan status. This should result in sentinel error ErrInvalidCollectorStateTransition.
   125  func (s *AssignmentCollectorStateMachineTestSuite) TestChangeProcessingStatus_InvalidTransition() {
   126  	// first change status to orphan
   127  	err := s.collector.ChangeProcessingStatus(CachingApprovals, Orphaned)
   128  	require.NoError(s.T(), err)
   129  	require.Equal(s.T(), Orphaned, s.collector.ProcessingStatus())
   130  	// then try to perform transition from caching to verifying
   131  	err = s.collector.ChangeProcessingStatus(CachingApprovals, VerifyingApprovals)
   132  	require.Error(s.T(), err)
   133  	require.True(s.T(), errors.Is(err, ErrDifferentCollectorState))
   134  }