github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunk_assigner_test.go (about)

     1  package chunks
     2  
     3  import (
     4  	"math/rand"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/mock"
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/stretchr/testify/suite"
    10  
    11  	chmodels "github.com/koko1123/flow-go-1/model/chunks"
    12  	"github.com/koko1123/flow-go-1/model/flow"
    13  	protocolMock "github.com/koko1123/flow-go-1/state/protocol/mock"
    14  	"github.com/koko1123/flow-go-1/state/protocol/seed"
    15  	"github.com/koko1123/flow-go-1/utils/unittest"
    16  )
    17  
    18  // PublicAssignmentTestSuite contains tests against methods of the ChunkAssigner scheme
    19  type PublicAssignmentTestSuite struct {
    20  	suite.Suite
    21  }
    22  
    23  // Setup test with n verification nodes
    24  func (a *PublicAssignmentTestSuite) SetupTest(n int) (*flow.Header, *protocolMock.Snapshot, *protocolMock.State) {
    25  	nodes := make([]flow.Role, 0)
    26  	for i := 1; i < n; i++ {
    27  		nodes = append(nodes, flow.RoleVerification)
    28  	}
    29  	participants, _, _ := unittest.CreateNParticipantsWithMyRole(flow.RoleVerification, nodes...)
    30  
    31  	// setup protocol state
    32  	block, snapshot, state, _ := unittest.FinalizedProtocolStateWithParticipants(participants)
    33  	head := block.Header
    34  
    35  	return head, snapshot, state
    36  }
    37  
    38  // TestAssignment invokes all the tests in this test suite
    39  func TestAssignment(t *testing.T) {
    40  	suite.Run(t, new(PublicAssignmentTestSuite))
    41  }
    42  
    43  // TestByNodeID evaluates the correctness of ByNodeID method of ChunkAssigner
    44  func (a *PublicAssignmentTestSuite) TestByNodeID() {
    45  	size := 5
    46  	// creates ids and twice chunks of the ids
    47  	ids := unittest.IdentityListFixture(size)
    48  	chunks := a.CreateChunks(2*size, a.T())
    49  	assignment := chmodels.NewAssignment()
    50  
    51  	// assigns two chunks to each verifier node
    52  	// j keeps track of chunks
    53  	j := 0
    54  	for i := 0; i < size; i++ {
    55  		c, ok := chunks.ByIndex(uint64(j))
    56  		require.True(a.T(), ok, "chunk out of range requested")
    57  		assignment.Add(c, append(assignment.Verifiers(c), ids[i].NodeID))
    58  		j++
    59  		c, ok = chunks.ByIndex(uint64(j))
    60  		require.True(a.T(), ok, "chunk out of range requested")
    61  		assignment.Add(c, append(assignment.Verifiers(c), ids[i].NodeID))
    62  	}
    63  
    64  	// evaluating the chunk assignment
    65  	// each verifier should have two certain chunks based on the assignment
    66  	// j keeps track of chunks
    67  	j = 0
    68  	for i := 0; i < size; i++ {
    69  		assignedChunks := assignment.ByNodeID(ids[i].NodeID)
    70  		require.Len(a.T(), assignedChunks, 2)
    71  		c, ok := chunks.ByIndex(uint64(j))
    72  		require.True(a.T(), ok, "chunk out of range requested")
    73  		require.Contains(a.T(), assignedChunks, c.Index)
    74  		j++
    75  		c, ok = chunks.ByIndex(uint64(j))
    76  		require.True(a.T(), ok, "chunk out of range requested")
    77  		require.Contains(a.T(), assignedChunks, c.Index)
    78  	}
    79  
    80  }
    81  
    82  // TestAssignDuplicate tests assign Add duplicate verifiers
    83  func (a *PublicAssignmentTestSuite) TestAssignDuplicate() {
    84  	size := 5
    85  	// creates ids and twice chunks of the ids
    86  	var ids flow.IdentityList = unittest.IdentityListFixture(size)
    87  	chunks := a.CreateChunks(2, a.T())
    88  	assignment := chmodels.NewAssignment()
    89  
    90  	// assigns first chunk to non-duplicate list of verifiers
    91  	c, ok := chunks.ByIndex(uint64(0))
    92  	require.True(a.T(), ok, "chunk out of range requested")
    93  	assignment.Add(c, ids.NodeIDs())
    94  	require.Len(a.T(), assignment.Verifiers(c), size)
    95  
    96  	// duplicates first verifier, hence size increases by 1
    97  	ids = append(ids, ids[0])
    98  	require.Len(a.T(), ids, size+1)
    99  	// assigns second chunk to a duplicate list of verifiers
   100  	c, ok = chunks.ByIndex(uint64(1))
   101  	require.True(a.T(), ok, "chunk out of range requested")
   102  	assignment.Add(c, ids.NodeIDs())
   103  	// should be size not size + 1
   104  	require.Len(a.T(), assignment.Verifiers(c), size)
   105  }
   106  
   107  // TestPermuteEntirely tests permuting an entire IdentityList against
   108  // randomness and deterministicity
   109  func (a *PublicAssignmentTestSuite) TestPermuteEntirely() {
   110  	_, snapshot, state := a.SetupTest(10)
   111  
   112  	// create a assigner object with alpha = 10
   113  	assigner, err := NewChunkAssigner(10, state)
   114  	require.NoError(a.T(), err)
   115  
   116  	// create seed
   117  	seed := a.GetSeed(a.T())
   118  
   119  	snapshot.On("RandomSource").Return(seed, nil)
   120  
   121  	// creates random ids
   122  	count := 10
   123  	var idList flow.IdentityList = unittest.IdentityListFixture(count)
   124  	var ids flow.IdentifierList = idList.NodeIDs()
   125  	original := make(flow.IdentifierList, count)
   126  	copy(original, ids)
   127  
   128  	// Randomness:
   129  	rng1, err := assigner.rngByBlockID(snapshot)
   130  	require.NoError(a.T(), err)
   131  	err = rng1.Shuffle(len(ids), ids.Swap)
   132  	require.NoError(a.T(), err)
   133  
   134  	// permutation should not change length of the list
   135  	require.Len(a.T(), ids, count)
   136  
   137  	// list should be permuted
   138  	require.NotEqual(a.T(), ids, original)
   139  
   140  	// Deterministiciy:
   141  	// shuffling same list with the same seed should generate the same permutation
   142  	rng2, err := assigner.rngByBlockID(snapshot)
   143  	require.NoError(a.T(), err)
   144  	// permutes original list with the same seed
   145  	err = rng2.Shuffle(len(original), original.Swap)
   146  	require.NoError(a.T(), err)
   147  	require.Equal(a.T(), ids, original)
   148  }
   149  
   150  // TestPermuteSublist tests permuting an a sublist of an
   151  // IdentityList against randomness and deterministicity
   152  func (a *PublicAssignmentTestSuite) TestPermuteSublist() {
   153  	_, snapshot, state := a.SetupTest(10)
   154  
   155  	// create a assigner object with alpha = 10
   156  	assigner, err := NewChunkAssigner(10, state)
   157  	require.NoError(a.T(), err)
   158  
   159  	// create seed
   160  	seed := a.GetSeed(a.T())
   161  	snapshot.On("RandomSource").Return(seed, nil)
   162  
   163  	// creates random ids
   164  	count := 10
   165  	subset := 4
   166  
   167  	var idList flow.IdentityList = unittest.IdentityListFixture(count)
   168  	var ids flow.IdentifierList = idList.NodeIDs()
   169  	original := make([]flow.Identifier, count)
   170  	copy(original, ids)
   171  
   172  	// Randomness:
   173  	rng1, err := assigner.rngByBlockID(snapshot)
   174  	require.NoError(a.T(), err)
   175  	err = rng1.Samples(len(ids), subset, ids.Swap)
   176  	require.NoError(a.T(), err)
   177  
   178  	// permutation should not change length of the list
   179  	require.Len(a.T(), ids, count)
   180  
   181  	// the initial subset of the list that is permuted should
   182  	// be different than the original
   183  	require.NotEqual(a.T(), ids[:subset], original[:subset])
   184  }
   185  
   186  // TestDeterministicy evaluates deterministic behavior of chunk assignment when
   187  // chunks, random generator, and nodes are the same
   188  func (a *PublicAssignmentTestSuite) TestDeterministicy() {
   189  	head, snapshot, state := a.SetupTest(10)
   190  
   191  	c := 10          // keeps number of chunks
   192  	n := 10          // keeps number of verifier nodes
   193  	alpha := uint(1) // each chunk requires alpha verifiers
   194  
   195  	// create seed
   196  	result := a.CreateResult(head, c, a.T())
   197  	seed := a.GetSeed(a.T())
   198  	snapshot.On("RandomSource").Return(seed, nil)
   199  
   200  	// creates two set of the same nodes
   201  	nodes1 := unittest.IdentityListFixture(n)
   202  	nodes2 := make([]*flow.Identity, n)
   203  	require.Equal(a.T(), copy(nodes2, nodes1), n)
   204  
   205  	// chunk assignment of the first set
   206  	snapshot.On("Identities", mock.Anything).Return(nodes1, nil).Once()
   207  	a1, err := NewChunkAssigner(alpha, state)
   208  	require.NoError(a.T(), err)
   209  	p1, err := a1.Assign(result, head.ID())
   210  	require.NoError(a.T(), err)
   211  
   212  	// chunk assignment of the second set
   213  	snapshot.On("Identities", mock.Anything).Return(nodes2, nil).Once()
   214  	a2, err := NewChunkAssigner(alpha, state)
   215  	require.NoError(a.T(), err)
   216  	p2, err := a2.Assign(result, head.ID())
   217  	require.NoError(a.T(), err)
   218  
   219  	// list of nodes should get shuffled after public assignment
   220  	// but it should contain same elements
   221  	require.Equal(a.T(), p1, p2)
   222  }
   223  
   224  // TestChunkAssignmentOneToOne evaluates chunk assignment against
   225  // several single chunk to single node assignment
   226  func (a *PublicAssignmentTestSuite) TestChunkAssignmentOneToOne() {
   227  	// covers an edge case assigning 1 chunk to a single verifier node
   228  	a.ChunkAssignmentScenario(1, 1, 1)
   229  
   230  	// assigning 10 chunks to one node
   231  	a.ChunkAssignmentScenario(10, 1, 1)
   232  	// assigning 10 chunks to 2 nodes
   233  	// each chunk to one verifier
   234  	a.ChunkAssignmentScenario(10, 2, 1)
   235  	// each chunk to 2 verifiers
   236  	a.ChunkAssignmentScenario(10, 2, 2)
   237  
   238  	// assigning 10 chunks to 10 nodes
   239  	// each chunk to one verifier
   240  	a.ChunkAssignmentScenario(10, 10, 1)
   241  	// each chunk to 6 verifiers
   242  	a.ChunkAssignmentScenario(10, 10, 6)
   243  	// each chunk to 9 verifiers
   244  	a.ChunkAssignmentScenario(10, 10, 9)
   245  }
   246  
   247  // TestChunkAssignmentOneToMay evaluates chunk assignment
   248  func (a *PublicAssignmentTestSuite) TestChunkAssignmentOneToMany() {
   249  	//  against assigning 52 chunks to 7 nodes
   250  	//  each chunk to 5 verifiers
   251  	a.ChunkAssignmentScenario(52, 7, 5)
   252  	//  against assigning 49 chunks to 9 nodes
   253  	//  each chunk to 8 verifiers
   254  	a.ChunkAssignmentScenario(52, 9, 8)
   255  }
   256  
   257  // ChunkAssignmentScenario is a test helper that creates chunkNum chunks, verNum verifiers
   258  // and then assign each chunk to alpha randomly chosen verifiers
   259  // it also evaluates that each chuck is assigned to alpha many unique verifier nodes
   260  func (a *PublicAssignmentTestSuite) ChunkAssignmentScenario(chunkNum, verNum, alpha int) {
   261  	head, snapshot, state := a.SetupTest(alpha)
   262  
   263  	result := a.CreateResult(head, chunkNum, a.T())
   264  	seed := a.GetSeed(a.T())
   265  	snapshot.On("RandomSource").Return(seed, nil)
   266  
   267  	// creates nodes and keeps a copy of them
   268  	nodes := unittest.IdentityListFixture(verNum)
   269  	original := make([]*flow.Identity, verNum)
   270  	require.Equal(a.T(), copy(original, nodes), verNum)
   271  
   272  	snapshot.On("Identities", mock.Anything).Return(nodes, nil).Once()
   273  	a1, err := NewChunkAssigner(uint(alpha), state)
   274  	require.NoError(a.T(), err)
   275  	p1, err := a1.Assign(result, head.ID())
   276  	require.NoError(a.T(), err)
   277  
   278  	// list of nodes should get shuffled after public assignment
   279  	require.ElementsMatch(a.T(), nodes, original)
   280  
   281  	for _, chunk := range result.Chunks {
   282  		// each chunk should be assigned to alpha verifiers
   283  		require.Equal(a.T(), p1.Verifiers(chunk).Len(), alpha)
   284  	}
   285  }
   286  
   287  func (a *PublicAssignmentTestSuite) TestCacheAssignment() {
   288  	head, snapshot, state := a.SetupTest(3)
   289  
   290  	result := a.CreateResult(head, 20, a.T())
   291  	seed := a.GetSeed(a.T())
   292  	snapshot.On("RandomSource").Return(seed, nil)
   293  
   294  	// creates nodes and keeps a copy of them
   295  	nodes := unittest.IdentityListFixture(5)
   296  	assigner, err := NewChunkAssigner(3, state)
   297  	require.NoError(a.T(), err)
   298  
   299  	// initially cache should be empty
   300  	require.Equal(a.T(), assigner.Size(), uint(0))
   301  
   302  	// new assignment should be cached
   303  	// random generators are stateful and we need to
   304  	// generate a new one if we want to have the same
   305  	// state
   306  	snapshot.On("Identities", mock.Anything).Return(nodes, nil).Once()
   307  	_, err = assigner.Assign(result, head.ID())
   308  	require.NoError(a.T(), err)
   309  	require.Equal(a.T(), assigner.Size(), uint(1))
   310  
   311  	// repetitive assignment should not be cached
   312  	_, err = assigner.Assign(result, head.ID())
   313  	require.NoError(a.T(), err)
   314  	require.Equal(a.T(), assigner.Size(), uint(1))
   315  
   316  	// performs the assignment using a different seed
   317  	// should results in a different new assignment
   318  	// which should be cached
   319  	otherResult := a.CreateResult(head, 20, a.T())
   320  
   321  	_, err = assigner.Assign(otherResult, head.ID())
   322  	require.NoError(a.T(), err)
   323  	require.Equal(a.T(), assigner.Size(), uint(2))
   324  }
   325  
   326  // CreateChunk creates and returns num chunks. It only fills the Index part of
   327  // chunks to make them distinct from each other.
   328  func (a *PublicAssignmentTestSuite) CreateChunks(num int, t *testing.T) flow.ChunkList {
   329  	list := flow.ChunkList{}
   330  	for i := 0; i < num; i++ {
   331  		// creates random state for each chunk
   332  		// to provide random ordering after sorting
   333  		var state flow.StateCommitment
   334  		_, err := rand.Read(state[:])
   335  		require.NoError(t, err)
   336  
   337  		blockID := unittest.IdentifierFixture()
   338  
   339  		// creates chunk
   340  		c := &flow.Chunk{
   341  			Index: uint64(i),
   342  			ChunkBody: flow.ChunkBody{
   343  				StartState: state,
   344  				BlockID:    blockID,
   345  			},
   346  		}
   347  		list.Insert(c)
   348  	}
   349  	require.Equal(a.T(), num, list.Len())
   350  	return list
   351  }
   352  
   353  func (a *PublicAssignmentTestSuite) CreateResult(head *flow.Header, num int, t *testing.T) *flow.ExecutionResult {
   354  	list := a.CreateChunks(5, a.T())
   355  	result := &flow.ExecutionResult{
   356  		BlockID: head.ID(),
   357  		Chunks:  list,
   358  	}
   359  
   360  	return result
   361  }
   362  
   363  func (a *PublicAssignmentTestSuite) GetSeed(t *testing.T) []byte {
   364  	seed := make([]byte, seed.RandomSourceLength)
   365  	_, err := rand.Read(seed)
   366  	require.NoError(t, err)
   367  	return seed
   368  }