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  }