github.com/onflow/flow-go@v0.33.17/engine/execution/execution_test.go (about) 1 package execution_test 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/rs/zerolog/log" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/mock" 12 "github.com/stretchr/testify/require" 13 "github.com/vmihailenco/msgpack" 14 "go.uber.org/atomic" 15 16 execTestutil "github.com/onflow/flow-go/engine/execution/testutil" 17 "github.com/onflow/flow-go/engine/testutil" 18 testmock "github.com/onflow/flow-go/engine/testutil/mock" 19 "github.com/onflow/flow-go/model/flow" 20 "github.com/onflow/flow-go/model/messages" 21 "github.com/onflow/flow-go/module/signature" 22 "github.com/onflow/flow-go/network/channels" 23 "github.com/onflow/flow-go/network/mocknetwork" 24 "github.com/onflow/flow-go/network/stub" 25 "github.com/onflow/flow-go/state/cluster" 26 "github.com/onflow/flow-go/utils/unittest" 27 ) 28 29 func sendBlock(exeNode *testmock.ExecutionNode, from flow.Identifier, proposal *messages.BlockProposal) error { 30 return exeNode.FollowerEngine.Process(channels.ReceiveBlocks, from, proposal) 31 } 32 33 // Test when the ingestion engine receives a block, it will 34 // request collections from collection node, and send ER to 35 // verification node and consensus node. 36 // create a block that has two collections: col1 and col2; 37 // col1 has tx1 and tx2, col2 has tx3 and tx4. 38 // create another child block which will trigger the parent 39 // block to be incorporated and be passed to the ingestion engine 40 func TestExecutionFlow(t *testing.T) { 41 hub := stub.NewNetworkHub() 42 43 chainID := flow.Testnet 44 45 colID := unittest.IdentityFixture( 46 unittest.WithRole(flow.RoleCollection), 47 unittest.WithKeys, 48 ) 49 conID := unittest.IdentityFixture( 50 unittest.WithRole(flow.RoleConsensus), 51 unittest.WithKeys, 52 ) 53 exeID := unittest.IdentityFixture( 54 unittest.WithRole(flow.RoleExecution), 55 unittest.WithKeys, 56 ) 57 verID := unittest.IdentityFixture( 58 unittest.WithRole(flow.RoleVerification), 59 unittest.WithKeys, 60 ) 61 62 identities := unittest.CompleteIdentitySet(colID, conID, exeID, verID).Sort(flow.Canonical) 63 64 // create execution node 65 exeNode := testutil.ExecutionNode(t, hub, exeID, identities, 21, chainID) 66 67 ctx, cancel := context.WithCancel(context.Background()) 68 unittest.RequireReturnsBefore(t, func() { 69 exeNode.Ready(ctx) 70 }, 1*time.Second, "could not start execution node on time") 71 defer exeNode.Done(cancel) 72 73 genesis, err := exeNode.State.AtHeight(0).Head() 74 require.NoError(t, err) 75 76 tx1 := flow.TransactionBody{ 77 Script: []byte("transaction { execute { log(1) } }"), 78 } 79 80 tx2 := flow.TransactionBody{ 81 Script: []byte("transaction { execute { log(2) } }"), 82 } 83 84 tx3 := flow.TransactionBody{ 85 Script: []byte("transaction { execute { log(3) } }"), 86 } 87 88 tx4 := flow.TransactionBody{ 89 Script: []byte("transaction { execute { log(4) } }"), 90 } 91 92 col1 := flow.Collection{Transactions: []*flow.TransactionBody{&tx1, &tx2}} 93 col2 := flow.Collection{Transactions: []*flow.TransactionBody{&tx3, &tx4}} 94 95 collections := map[flow.Identifier]*flow.Collection{ 96 col1.ID(): &col1, 97 col2.ID(): &col2, 98 } 99 100 clusterChainID := cluster.CanonicalClusterID(1, flow.IdentityList{colID}.NodeIDs()) 101 102 // signed by the only collector 103 block := unittest.BlockWithParentAndProposerFixture(t, genesis, conID.NodeID) 104 voterIndices, err := signature.EncodeSignersToIndices( 105 []flow.Identifier{conID.NodeID}, []flow.Identifier{conID.NodeID}) 106 require.NoError(t, err) 107 block.Header.ParentVoterIndices = voterIndices 108 signerIndices, err := signature.EncodeSignersToIndices( 109 []flow.Identifier{colID.NodeID}, []flow.Identifier{colID.NodeID}) 110 require.NoError(t, err) 111 block.SetPayload(flow.Payload{ 112 Guarantees: []*flow.CollectionGuarantee{ 113 { 114 CollectionID: col1.ID(), 115 SignerIndices: signerIndices, 116 ChainID: clusterChainID, 117 ReferenceBlockID: genesis.ID(), 118 }, 119 { 120 CollectionID: col2.ID(), 121 SignerIndices: signerIndices, 122 ChainID: clusterChainID, 123 ReferenceBlockID: genesis.ID(), 124 }, 125 }, 126 }) 127 128 child := unittest.BlockWithParentAndProposerFixture(t, block.Header, conID.NodeID) 129 // the default signer indices is 2 bytes, but in this test cases 130 // we need 1 byte 131 child.Header.ParentVoterIndices = voterIndices 132 133 log.Info().Msgf("child block ID: %v, indices: %x", child.Header.ID(), child.Header.ParentVoterIndices) 134 135 collectionNode := testutil.GenericNodeFromParticipants(t, hub, colID, identities, chainID) 136 defer collectionNode.Done() 137 verificationNode := testutil.GenericNodeFromParticipants(t, hub, verID, identities, chainID) 138 defer verificationNode.Done() 139 consensusNode := testutil.GenericNodeFromParticipants(t, hub, conID, identities, chainID) 140 defer consensusNode.Done() 141 142 // create collection node that can respond collections to execution node 143 // check collection node received the collection request from execution node 144 providerEngine := new(mocknetwork.Engine) 145 provConduit, _ := collectionNode.Net.Register(channels.ProvideCollections, providerEngine) 146 providerEngine.On("Process", mock.AnythingOfType("channels.Channel"), exeID.NodeID, mock.Anything). 147 Run(func(args mock.Arguments) { 148 originID := args.Get(1).(flow.Identifier) 149 req := args.Get(2).(*messages.EntityRequest) 150 151 var entities []flow.Entity 152 for _, entityID := range req.EntityIDs { 153 coll, exists := collections[entityID] 154 require.True(t, exists) 155 entities = append(entities, coll) 156 } 157 158 var blobs [][]byte 159 for _, entity := range entities { 160 blob, _ := msgpack.Marshal(entity) 161 blobs = append(blobs, blob) 162 } 163 164 res := &messages.EntityResponse{ 165 Nonce: req.Nonce, 166 EntityIDs: req.EntityIDs, 167 Blobs: blobs, 168 } 169 170 err := provConduit.Publish(res, originID) 171 assert.NoError(t, err) 172 }). 173 Once(). 174 Return(nil) 175 176 var lock sync.Mutex 177 var receipt *flow.ExecutionReceipt 178 179 // create verification engine that can create approvals and send to consensus nodes 180 // check the verification engine received the ER from execution node 181 verificationEngine := new(mocknetwork.Engine) 182 _, _ = verificationNode.Net.Register(channels.ReceiveReceipts, verificationEngine) 183 verificationEngine.On("Process", mock.AnythingOfType("channels.Channel"), exeID.NodeID, mock.Anything). 184 Run(func(args mock.Arguments) { 185 lock.Lock() 186 defer lock.Unlock() 187 receipt, _ = args[2].(*flow.ExecutionReceipt) 188 189 assert.Equal(t, block.ID(), receipt.ExecutionResult.BlockID) 190 }). 191 Return(nil). 192 Once() 193 194 // create consensus engine that accepts the result 195 // check the consensus engine has received the result from execution node 196 consensusEngine := new(mocknetwork.Engine) 197 _, _ = consensusNode.Net.Register(channels.ReceiveReceipts, consensusEngine) 198 consensusEngine.On("Process", mock.AnythingOfType("channels.Channel"), exeID.NodeID, mock.Anything). 199 Run(func(args mock.Arguments) { 200 lock.Lock() 201 defer lock.Unlock() 202 203 receipt, _ = args[2].(*flow.ExecutionReceipt) 204 205 assert.Equal(t, block.ID(), receipt.ExecutionResult.BlockID) 206 assert.Equal(t, len(collections), len(receipt.ExecutionResult.Chunks)-1) // don't count system chunk 207 208 for i, chunk := range receipt.ExecutionResult.Chunks { 209 assert.EqualValues(t, i, chunk.CollectionIndex) 210 } 211 }). 212 Return(nil). 213 Once() 214 215 // submit block from consensus node 216 err = sendBlock(&exeNode, conID.NodeID, unittest.ProposalFromBlock(&block)) 217 require.NoError(t, err) 218 219 // submit the child block from consensus node, which trigger the parent block 220 // to be passed to BlockProcessable 221 err = sendBlock(&exeNode, conID.NodeID, unittest.ProposalFromBlock(&child)) 222 require.NoError(t, err) 223 224 require.Eventually(t, func() bool { 225 // when sendBlock returned, ingestion engine might not have processed 226 // the block yet, because the process is async. we have to wait 227 hub.DeliverAll() 228 229 lock.Lock() 230 defer lock.Unlock() 231 return receipt != nil 232 }, time.Second*10, time.Millisecond*500) 233 234 // check that the block has been executed. 235 exeNode.AssertBlockIsExecuted(t, block.Header) 236 237 if exeNode.StorehouseEnabled { 238 exeNode.AssertHighestExecutedBlock(t, genesis) 239 } else { 240 exeNode.AssertHighestExecutedBlock(t, block.Header) 241 } 242 243 myReceipt, err := exeNode.MyExecutionReceipts.MyReceipt(block.ID()) 244 require.NoError(t, err) 245 require.NotNil(t, myReceipt) 246 require.Equal(t, exeNode.Me.NodeID(), myReceipt.ExecutorID) 247 248 providerEngine.AssertExpectations(t) 249 verificationEngine.AssertExpectations(t) 250 consensusEngine.AssertExpectations(t) 251 } 252 253 func deployContractBlock(t *testing.T, conID *flow.Identity, colID *flow.Identity, chain flow.Chain, seq uint64, parent *flow.Header, ref *flow.Header) ( 254 *flow.TransactionBody, *flow.Collection, flow.Block, *messages.BlockProposal, uint64) { 255 // make tx 256 tx := execTestutil.DeployCounterContractTransaction(chain.ServiceAddress(), chain) 257 err := execTestutil.SignTransactionAsServiceAccount(tx, seq, chain) 258 require.NoError(t, err) 259 260 // make collection 261 col := &flow.Collection{Transactions: []*flow.TransactionBody{tx}} 262 263 signerIndices, err := signature.EncodeSignersToIndices( 264 []flow.Identifier{colID.NodeID}, []flow.Identifier{colID.NodeID}) 265 require.NoError(t, err) 266 267 clusterChainID := cluster.CanonicalClusterID(1, flow.IdentityList{colID}.NodeIDs()) 268 269 // make block 270 block := unittest.BlockWithParentAndProposerFixture(t, parent, conID.NodeID) 271 voterIndices, err := signature.EncodeSignersToIndices( 272 []flow.Identifier{conID.NodeID}, []flow.Identifier{conID.NodeID}) 273 require.NoError(t, err) 274 block.Header.ParentVoterIndices = voterIndices 275 block.SetPayload(flow.Payload{ 276 Guarantees: []*flow.CollectionGuarantee{ 277 { 278 CollectionID: col.ID(), 279 SignerIndices: signerIndices, 280 ChainID: clusterChainID, 281 ReferenceBlockID: ref.ID(), 282 }, 283 }, 284 }) 285 286 // make proposal 287 proposal := unittest.ProposalFromBlock(&block) 288 289 return tx, col, block, proposal, seq + 1 290 } 291 292 func makePanicBlock(t *testing.T, conID *flow.Identity, colID *flow.Identity, chain flow.Chain, seq uint64, parent *flow.Header, ref *flow.Header) ( 293 *flow.TransactionBody, *flow.Collection, flow.Block, *messages.BlockProposal, uint64) { 294 // make tx 295 tx := execTestutil.CreateCounterPanicTransaction(chain.ServiceAddress(), chain.ServiceAddress()) 296 err := execTestutil.SignTransactionAsServiceAccount(tx, seq, chain) 297 require.NoError(t, err) 298 299 // make collection 300 col := &flow.Collection{Transactions: []*flow.TransactionBody{tx}} 301 302 clusterChainID := cluster.CanonicalClusterID(1, flow.IdentityList{colID}.NodeIDs()) 303 // make block 304 block := unittest.BlockWithParentAndProposerFixture(t, parent, conID.NodeID) 305 voterIndices, err := signature.EncodeSignersToIndices( 306 []flow.Identifier{conID.NodeID}, []flow.Identifier{conID.NodeID}) 307 require.NoError(t, err) 308 block.Header.ParentVoterIndices = voterIndices 309 310 signerIndices, err := signature.EncodeSignersToIndices( 311 []flow.Identifier{colID.NodeID}, []flow.Identifier{colID.NodeID}) 312 require.NoError(t, err) 313 314 block.SetPayload(flow.Payload{ 315 Guarantees: []*flow.CollectionGuarantee{ 316 {CollectionID: col.ID(), SignerIndices: signerIndices, ChainID: clusterChainID, ReferenceBlockID: ref.ID()}, 317 }, 318 }) 319 320 proposal := unittest.ProposalFromBlock(&block) 321 322 return tx, col, block, proposal, seq + 1 323 } 324 325 func makeSuccessBlock(t *testing.T, conID *flow.Identity, colID *flow.Identity, chain flow.Chain, seq uint64, parent *flow.Header, ref *flow.Header) ( 326 *flow.TransactionBody, *flow.Collection, flow.Block, *messages.BlockProposal, uint64) { 327 tx := execTestutil.AddToCounterTransaction(chain.ServiceAddress(), chain.ServiceAddress()) 328 err := execTestutil.SignTransactionAsServiceAccount(tx, seq, chain) 329 require.NoError(t, err) 330 331 signerIndices, err := signature.EncodeSignersToIndices( 332 []flow.Identifier{colID.NodeID}, []flow.Identifier{colID.NodeID}) 333 require.NoError(t, err) 334 clusterChainID := cluster.CanonicalClusterID(1, flow.IdentityList{colID}.NodeIDs()) 335 336 col := &flow.Collection{Transactions: []*flow.TransactionBody{tx}} 337 block := unittest.BlockWithParentAndProposerFixture(t, parent, conID.NodeID) 338 voterIndices, err := signature.EncodeSignersToIndices( 339 []flow.Identifier{conID.NodeID}, []flow.Identifier{conID.NodeID}) 340 require.NoError(t, err) 341 block.Header.ParentVoterIndices = voterIndices 342 block.SetPayload(flow.Payload{ 343 Guarantees: []*flow.CollectionGuarantee{ 344 {CollectionID: col.ID(), SignerIndices: signerIndices, ChainID: clusterChainID, ReferenceBlockID: ref.ID()}, 345 }, 346 }) 347 348 proposal := unittest.ProposalFromBlock(&block) 349 350 return tx, col, block, proposal, seq + 1 351 } 352 353 // Test a successful tx should change the statecommitment, 354 // but a failed Tx should not change the statecommitment. 355 func TestFailedTxWillNotChangeStateCommitment(t *testing.T) { 356 hub := stub.NewNetworkHub() 357 358 chainID := flow.Emulator 359 360 colID := unittest.IdentityFixture( 361 unittest.WithRole(flow.RoleCollection), 362 unittest.WithKeys, 363 ) 364 conID := unittest.IdentityFixture( 365 unittest.WithRole(flow.RoleConsensus), 366 unittest.WithKeys, 367 ) 368 exe1ID := unittest.IdentityFixture( 369 unittest.WithRole(flow.RoleExecution), 370 unittest.WithKeys, 371 ) 372 373 identities := unittest.CompleteIdentitySet(colID, conID, exe1ID) 374 key := unittest.NetworkingPrivKeyFixture() 375 identities[3].NetworkPubKey = key.PublicKey() 376 377 collectionNode := testutil.GenericNodeFromParticipants(t, hub, colID, identities, chainID) 378 defer collectionNode.Done() 379 consensusNode := testutil.GenericNodeFromParticipants(t, hub, conID, identities, chainID) 380 defer consensusNode.Done() 381 exe1Node := testutil.ExecutionNode(t, hub, exe1ID, identities, 27, chainID) 382 383 ctx, cancel := context.WithCancel(context.Background()) 384 unittest.RequireReturnsBefore(t, func() { 385 exe1Node.Ready(ctx) 386 }, 1*time.Second, "could not start execution node on time") 387 defer exe1Node.Done(cancel) 388 389 genesis, err := exe1Node.State.AtHeight(0).Head() 390 require.NoError(t, err) 391 392 seq := uint64(0) 393 394 chain := exe1Node.ChainID.Chain() 395 396 // transaction that will change state and succeed, used to test that state commitment changes 397 // genesis <- block1 [tx1] <- block2 [tx2] <- block3 [tx3] <- child 398 _, col1, block1, proposal1, seq := deployContractBlock(t, conID, colID, chain, seq, genesis, genesis) 399 400 // we don't set the proper sequence number of this one 401 _, col2, block2, proposal2, _ := makePanicBlock(t, conID, colID, chain, uint64(0), block1.Header, genesis) 402 403 _, col3, block3, proposal3, seq := makeSuccessBlock(t, conID, colID, chain, seq, block2.Header, genesis) 404 405 _, _, _, proposal4, _ := makeSuccessBlock(t, conID, colID, chain, seq, block3.Header, genesis) 406 // seq++ 407 408 // setup mocks and assertions 409 collectionEngine := mockCollectionEngineToReturnCollections( 410 t, 411 &collectionNode, 412 []*flow.Collection{col1, col2, col3}, 413 ) 414 415 receiptsReceived := atomic.Uint64{} 416 417 consensusEngine := new(mocknetwork.Engine) 418 _, _ = consensusNode.Net.Register(channels.ReceiveReceipts, consensusEngine) 419 consensusEngine.On("Process", mock.AnythingOfType("channels.Channel"), mock.Anything, mock.Anything). 420 Run(func(args mock.Arguments) { 421 receiptsReceived.Inc() 422 originID := args[1].(flow.Identifier) 423 receipt := args[2].(*flow.ExecutionReceipt) 424 finalState, _ := receipt.ExecutionResult.FinalStateCommitment() 425 consensusNode.Log.Debug(). 426 Hex("origin", originID[:]). 427 Hex("block", receipt.ExecutionResult.BlockID[:]). 428 Hex("final_state_commit", finalState[:]). 429 Msg("execution receipt delivered") 430 }).Return(nil) 431 432 // submit block2 from consensus node to execution node 1 433 err = sendBlock(&exe1Node, conID.NodeID, proposal1) 434 require.NoError(t, err) 435 436 err = sendBlock(&exe1Node, conID.NodeID, proposal2) 437 assert.NoError(t, err) 438 439 // ensure block 1 has been executed 440 hub.DeliverAllEventually(t, func() bool { 441 return receiptsReceived.Load() == 1 442 }) 443 444 if exe1Node.StorehouseEnabled { 445 exe1Node.AssertHighestExecutedBlock(t, genesis) 446 } else { 447 exe1Node.AssertHighestExecutedBlock(t, block1.Header) 448 } 449 450 exe1Node.AssertBlockIsExecuted(t, block1.Header) 451 exe1Node.AssertBlockNotExecuted(t, block2.Header) 452 453 scExe1Genesis, err := exe1Node.ExecutionState.StateCommitmentByBlockID(genesis.ID()) 454 assert.NoError(t, err) 455 456 scExe1Block1, err := exe1Node.ExecutionState.StateCommitmentByBlockID(block1.ID()) 457 assert.NoError(t, err) 458 assert.NotEqual(t, scExe1Genesis, scExe1Block1) 459 460 // submit block 3 and block 4 from consensus node to execution node 1 (who have block1), 461 err = sendBlock(&exe1Node, conID.NodeID, proposal3) 462 assert.NoError(t, err) 463 464 err = sendBlock(&exe1Node, conID.NodeID, proposal4) 465 assert.NoError(t, err) 466 467 // ensure block 1, 2 and 3 have been executed 468 hub.DeliverAllEventually(t, func() bool { 469 return receiptsReceived.Load() == 3 470 }) 471 472 // ensure state has been synced across both nodes 473 exe1Node.AssertBlockIsExecuted(t, block2.Header) 474 exe1Node.AssertBlockIsExecuted(t, block3.Header) 475 476 // verify state commitment of block 2 is the same as block 1, since tx failed on seq number verification 477 scExe1Block2, err := exe1Node.ExecutionState.StateCommitmentByBlockID(block2.ID()) 478 assert.NoError(t, err) 479 // TODO this is no longer valid because the system chunk can change the state 480 //assert.Equal(t, scExe1Block1, scExe1Block2) 481 _ = scExe1Block2 482 483 collectionEngine.AssertExpectations(t) 484 consensusEngine.AssertExpectations(t) 485 } 486 487 func mockCollectionEngineToReturnCollections(t *testing.T, collectionNode *testmock.GenericNode, cols []*flow.Collection) *mocknetwork.Engine { 488 collectionEngine := new(mocknetwork.Engine) 489 colConduit, _ := collectionNode.Net.Register(channels.RequestCollections, collectionEngine) 490 491 // make lookup 492 colMap := make(map[flow.Identifier][]byte) 493 for _, col := range cols { 494 blob, _ := msgpack.Marshal(col) 495 colMap[col.ID()] = blob 496 } 497 collectionEngine.On("Process", mock.AnythingOfType("channels.Channel"), mock.Anything, mock.Anything). 498 Run(func(args mock.Arguments) { 499 originID := args[1].(flow.Identifier) 500 req := args[2].(*messages.EntityRequest) 501 blob, ok := colMap[req.EntityIDs[0]] 502 if !ok { 503 assert.FailNow(t, "requesting unexpected collection", req.EntityIDs[0]) 504 } 505 res := &messages.EntityResponse{Blobs: [][]byte{blob}, EntityIDs: req.EntityIDs[:1]} 506 err := colConduit.Publish(res, originID) 507 assert.NoError(t, err) 508 }). 509 Return(nil). 510 Times(len(cols)) 511 return collectionEngine 512 } 513 514 // Test the receipt will be sent to multiple verification nodes 515 func TestBroadcastToMultipleVerificationNodes(t *testing.T) { 516 hub := stub.NewNetworkHub() 517 518 chainID := flow.Emulator 519 520 colID := unittest.IdentityFixture( 521 unittest.WithRole(flow.RoleCollection), 522 unittest.WithKeys, 523 ) 524 conID := unittest.IdentityFixture( 525 unittest.WithRole(flow.RoleConsensus), 526 unittest.WithKeys, 527 ) 528 exeID := unittest.IdentityFixture( 529 unittest.WithRole(flow.RoleExecution), 530 unittest.WithKeys, 531 ) 532 ver1ID := unittest.IdentityFixture( 533 unittest.WithRole(flow.RoleVerification), 534 unittest.WithKeys, 535 ) 536 ver2ID := unittest.IdentityFixture( 537 unittest.WithRole(flow.RoleVerification), 538 unittest.WithKeys, 539 ) 540 541 identities := unittest.CompleteIdentitySet(colID, conID, exeID, ver1ID, ver2ID) 542 543 exeNode := testutil.ExecutionNode(t, hub, exeID, identities, 21, chainID) 544 ctx, cancel := context.WithCancel(context.Background()) 545 546 unittest.RequireReturnsBefore(t, func() { 547 exeNode.Ready(ctx) 548 }, 1*time.Second, "could not start execution node on time") 549 defer exeNode.Done(cancel) 550 551 verification1Node := testutil.GenericNodeFromParticipants(t, hub, ver1ID, identities, chainID) 552 defer verification1Node.Done() 553 verification2Node := testutil.GenericNodeFromParticipants(t, hub, ver2ID, identities, chainID) 554 defer verification2Node.Done() 555 556 genesis, err := exeNode.State.AtHeight(0).Head() 557 require.NoError(t, err) 558 559 block := unittest.BlockWithParentAndProposerFixture(t, genesis, conID.NodeID) 560 voterIndices, err := signature.EncodeSignersToIndices([]flow.Identifier{conID.NodeID}, []flow.Identifier{conID.NodeID}) 561 require.NoError(t, err) 562 block.Header.ParentVoterIndices = voterIndices 563 block.SetPayload(flow.Payload{}) 564 proposal := unittest.ProposalFromBlock(&block) 565 566 child := unittest.BlockWithParentAndProposerFixture(t, block.Header, conID.NodeID) 567 child.Header.ParentVoterIndices = voterIndices 568 569 actualCalls := atomic.Uint64{} 570 571 verificationEngine := new(mocknetwork.Engine) 572 _, _ = verification1Node.Net.Register(channels.ReceiveReceipts, verificationEngine) 573 _, _ = verification2Node.Net.Register(channels.ReceiveReceipts, verificationEngine) 574 verificationEngine.On("Process", mock.AnythingOfType("channels.Channel"), exeID.NodeID, mock.Anything). 575 Run(func(args mock.Arguments) { 576 actualCalls.Inc() 577 578 var receipt *flow.ExecutionReceipt 579 receipt, _ = args[2].(*flow.ExecutionReceipt) 580 581 assert.Equal(t, block.ID(), receipt.ExecutionResult.BlockID) 582 for i, chunk := range receipt.ExecutionResult.Chunks { 583 assert.EqualValues(t, i, chunk.CollectionIndex) 584 assert.Greater(t, chunk.TotalComputationUsed, uint64(0)) 585 } 586 }). 587 Return(nil) 588 589 err = sendBlock(&exeNode, exeID.NodeID, proposal) 590 require.NoError(t, err) 591 592 err = sendBlock(&exeNode, conID.NodeID, unittest.ProposalFromBlock(&child)) 593 require.NoError(t, err) 594 595 hub.DeliverAllEventually(t, func() bool { 596 return actualCalls.Load() == 2 597 }) 598 599 verificationEngine.AssertExpectations(t) 600 }