github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/provider/engine_test.go (about)

     1  package provider
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	_ "github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	state "github.com/onflow/flow-go/engine/execution/state/mock"
    16  	"github.com/onflow/flow-go/model/flow"
    17  	"github.com/onflow/flow-go/model/messages"
    18  	"github.com/onflow/flow-go/module/irrecoverable"
    19  	"github.com/onflow/flow-go/module/mempool/queue"
    20  	"github.com/onflow/flow-go/module/metrics"
    21  	"github.com/onflow/flow-go/module/trace"
    22  	"github.com/onflow/flow-go/network/channels"
    23  	"github.com/onflow/flow-go/network/mocknetwork"
    24  	mockprotocol "github.com/onflow/flow-go/state/protocol/mock"
    25  	"github.com/onflow/flow-go/utils/unittest"
    26  )
    27  
    28  func TestProviderEngine_onChunkDataRequest(t *testing.T) {
    29  
    30  	t.Run("non-existent chunk", func(t *testing.T) {
    31  		net := mocknetwork.NewNetwork(t)
    32  		chunkConduit := mocknetwork.NewConduit(t)
    33  		net.On("Register", channels.PushReceipts, mock.Anything).Return(&mocknetwork.Conduit{}, nil)
    34  		net.On("Register", channels.ProvideChunks, mock.Anything).Return(chunkConduit, nil)
    35  		e, _, es, requestQueue := newTestEngine(t, net, true)
    36  
    37  		es.On("ChunkDataPackByChunkID", mock.Anything).Return(nil, errors.New("not found!"))
    38  
    39  		originIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification))
    40  
    41  		chunkID := unittest.IdentifierFixture()
    42  
    43  		req := &messages.ChunkDataRequest{
    44  			ChunkID: chunkID,
    45  			Nonce:   rand.Uint64(),
    46  		}
    47  
    48  		cancelCtx, cancel := context.WithCancel(context.Background())
    49  		defer cancel()
    50  		ctx, _ := irrecoverable.WithSignaler(cancelCtx)
    51  		e.Start(ctx)
    52  		// submit using non-existing origin ID
    53  		unittest.RequireCloseBefore(t, e.Ready(), 100*time.Millisecond, "could not start engine")
    54  		require.NoError(t, e.Process(channels.RequestChunks, originIdentity.NodeID, req))
    55  
    56  		require.Eventually(t, func() bool {
    57  			_, ok := requestQueue.Get() // ensuring all requests have been picked up from the queue.
    58  			return !ok
    59  		}, 1*time.Second, 10*time.Millisecond)
    60  
    61  		cancel()
    62  		unittest.RequireCloseBefore(t, e.Done(), 100*time.Millisecond, "could not stop engine")
    63  
    64  		// no chunk data pack response should be sent to a request coming from a non-existing origin ID
    65  		chunkConduit.AssertNotCalled(t, "Unicast")
    66  	})
    67  
    68  	t.Run("success", func(t *testing.T) {
    69  		net := mocknetwork.NewNetwork(t)
    70  		chunkConduit := &mocknetwork.Conduit{}
    71  		net.On("Register", channels.PushReceipts, mock.Anything).Return(&mocknetwork.Conduit{}, nil)
    72  		net.On("Register", channels.ProvideChunks, mock.Anything).Return(chunkConduit, nil)
    73  		e, _, es, requestQueue := newTestEngine(t, net, true)
    74  
    75  		originIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification))
    76  
    77  		chunkID := unittest.IdentifierFixture()
    78  		chunkDataPack := unittest.ChunkDataPackFixture(chunkID)
    79  
    80  		chunkConduit.On("Unicast", mock.Anything, originIdentity.NodeID).
    81  			Run(func(args mock.Arguments) {
    82  				res, ok := args[0].(*messages.ChunkDataResponse)
    83  				require.True(t, ok)
    84  
    85  				actualChunkID := res.ChunkDataPack.ChunkID
    86  				assert.Equal(t, chunkID, actualChunkID)
    87  			}).
    88  			Return(nil)
    89  
    90  		es.On("ChunkDataPackByChunkID", chunkID).Return(chunkDataPack, nil)
    91  
    92  		req := &messages.ChunkDataRequest{
    93  			ChunkID: chunkID,
    94  			Nonce:   rand.Uint64(),
    95  		}
    96  
    97  		cancelCtx, cancel := context.WithCancel(context.Background())
    98  		defer cancel()
    99  		ctx, _ := irrecoverable.WithSignaler(cancelCtx)
   100  		e.Start(ctx)
   101  		// submit using non-existing origin ID
   102  		unittest.RequireCloseBefore(t, e.Ready(), 100*time.Millisecond, "could not start engine")
   103  		require.NoError(t, e.Process(channels.RequestChunks, originIdentity.NodeID, req))
   104  
   105  		require.Eventually(t, func() bool {
   106  			_, ok := requestQueue.Get() // ensuring all requests have been picked up from the queue.
   107  			return !ok
   108  		}, 1*time.Second, 10*time.Millisecond)
   109  
   110  		cancel()
   111  		unittest.RequireCloseBefore(t, e.Done(), 100*time.Millisecond, "could not stop engine")
   112  	})
   113  
   114  }
   115  
   116  func TestProviderEngine_BroadcastExecutionReceipt(t *testing.T) {
   117  	// prepare
   118  	net := mocknetwork.NewNetwork(t)
   119  	chunkConduit := mocknetwork.NewConduit(t)
   120  	receiptConduit := mocknetwork.NewConduit(t)
   121  	net.On("Register", channels.PushReceipts, mock.Anything).Return(receiptConduit, nil)
   122  	net.On("Register", channels.ProvideChunks, mock.Anything).Return(chunkConduit, nil)
   123  	e, ps, _, _ := newTestEngine(t, net, true)
   124  
   125  	sealedBlock := unittest.BlockHeaderFixture()
   126  	sealed := new(mockprotocol.Snapshot)
   127  	sealed.On("Head").Return(sealedBlock, nil)
   128  	ps.On("Sealed").Return(sealed)
   129  	sealedHeight := sealedBlock.Height
   130  
   131  	receivers := unittest.IdentityListFixture(1)
   132  	snap := new(mockprotocol.Snapshot)
   133  	snap.On("Identities", mock.Anything).Return(receivers, nil)
   134  	ps.On("Final").Return(snap)
   135  
   136  	// verify that above the sealed height will be broadcasted
   137  	receipt1 := unittest.ExecutionReceiptFixture()
   138  	receiptConduit.On("Publish", receipt1, receivers.NodeIDs()[0]).Return(nil)
   139  
   140  	broadcasted, err := e.BroadcastExecutionReceipt(context.Background(), sealedHeight+1, receipt1)
   141  	require.NoError(t, err)
   142  	require.True(t, broadcasted)
   143  
   144  	// verify that equal the sealed height will NOT be broadcasted
   145  	receipt2 := unittest.ExecutionReceiptFixture()
   146  	broadcasted, err = e.BroadcastExecutionReceipt(context.Background(), sealedHeight, receipt2)
   147  	require.NoError(t, err)
   148  	require.False(t, broadcasted)
   149  
   150  	// verify that below the sealed height will NOT be broadcasted
   151  	broadcasted, err = e.BroadcastExecutionReceipt(context.Background(), sealedHeight-1, receipt2)
   152  	require.NoError(t, err)
   153  	require.False(t, broadcasted)
   154  }
   155  
   156  func TestProviderEngine_BroadcastExecutionUnauthorized(t *testing.T) {
   157  	net := mocknetwork.NewNetwork(t)
   158  	chunkConduit := mocknetwork.NewConduit(t)
   159  	receiptConduit := mocknetwork.NewConduit(t)
   160  	net.On("Register", channels.PushReceipts, mock.Anything).Return(receiptConduit, nil)
   161  	net.On("Register", channels.ProvideChunks, mock.Anything).Return(chunkConduit, nil)
   162  	// make sure the node is not authorized for broadcasting
   163  	authorized := false
   164  	e, ps, _, _ := newTestEngine(t, net, authorized)
   165  
   166  	sealedBlock := unittest.BlockHeaderFixture()
   167  	sealed := mockprotocol.NewSnapshot(t)
   168  	sealed.On("Head").Return(sealedBlock, nil)
   169  	ps.On("Sealed").Return(sealed)
   170  	sealedHeight := sealedBlock.Height
   171  
   172  	// verify that unstaked node will NOT broadcast
   173  	receipt2 := unittest.ExecutionReceiptFixture()
   174  	broadcasted, err := e.BroadcastExecutionReceipt(context.Background(), sealedHeight+1, receipt2)
   175  	require.NoError(t, err)
   176  	require.False(t, broadcasted)
   177  }
   178  
   179  func newTestEngine(t *testing.T, net *mocknetwork.Network, authorized bool) (
   180  	*Engine,
   181  	*mockprotocol.State,
   182  	*state.ExecutionState,
   183  	*queue.HeroStore,
   184  ) {
   185  	ps := mockprotocol.NewState(t)
   186  	execState := state.NewExecutionState(t)
   187  	requestQueue := queue.NewHeroStore(10, unittest.Logger(), metrics.NewNoopCollector())
   188  
   189  	e, err := New(
   190  		unittest.Logger(),
   191  		trace.NewNoopTracer(),
   192  		net,
   193  		ps,
   194  		execState,
   195  		metrics.NewNoopCollector(),
   196  		func(_ flow.Identifier) (bool, error) { return authorized, nil },
   197  		requestQueue,
   198  		DefaultChunkDataPackRequestWorker,
   199  		DefaultChunkDataPackQueryTimeout,
   200  		DefaultChunkDataPackDeliveryTimeout)
   201  	require.NoError(t, err)
   202  	return e, ps, execState, requestQueue
   203  }