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