github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/chunks/chunk_assigner_test.go (about) 1 package chunks 2 3 import ( 4 "crypto/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/onflow/flow-go/model/chunks" 12 "github.com/onflow/flow-go/model/flow" 13 protocol "github.com/onflow/flow-go/state/protocol/mock" 14 "github.com/onflow/flow-go/state/protocol/prg" 15 "github.com/onflow/flow-go/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, *protocol.Snapshot, *protocol.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, prg.RandomSourceLength) 365 _, err := rand.Read(seed) 366 require.NoError(t, err) 367 return seed 368 }