github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/retry_test.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/stretchr/testify/mock"
     7  	"google.golang.org/grpc/codes"
     8  	"google.golang.org/grpc/status"
     9  
    10  	"github.com/onflow/flow/protobuf/go/flow/access"
    11  	"github.com/onflow/flow/protobuf/go/flow/execution"
    12  
    13  	"github.com/koko1123/flow-go-1/model/flow"
    14  	"github.com/koko1123/flow-go-1/module/metrics"
    15  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    16  	realstorage "github.com/koko1123/flow-go-1/storage"
    17  	"github.com/koko1123/flow-go-1/utils/unittest"
    18  )
    19  
    20  // TestTransactionRetry tests that the retry mechanism will send retries at specific times
    21  func (suite *Suite) TestTransactionRetry() {
    22  
    23  	// ctx := context.Background()
    24  	collection := unittest.CollectionFixture(1)
    25  	transactionBody := collection.Transactions[0]
    26  	block := unittest.BlockFixture()
    27  	// Height needs to be at least DefaultTransactionExpiry before we start doing retries
    28  	block.Header.Height = flow.DefaultTransactionExpiry + 1
    29  	transactionBody.SetReferenceBlockID(block.ID())
    30  	headBlock := unittest.BlockFixture()
    31  	headBlock.Header.Height = block.Header.Height - 1 // head is behind the current block
    32  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
    33  
    34  	suite.snapshot.On("Head").Return(headBlock.Header, nil)
    35  	snapshotAtBlock := new(protocol.Snapshot)
    36  	snapshotAtBlock.On("Head").Return(block.Header, nil)
    37  	suite.state.On("AtBlockID", block.ID()).Return(snapshotAtBlock, nil)
    38  
    39  	// collection storage returns a not found error
    40  	suite.collections.On("LightByTransactionID", transactionBody.ID()).Return(nil, realstorage.ErrNotFound)
    41  
    42  	// txID := transactionBody.ID()
    43  	// blockID := block.ID()
    44  	// Setup Handler + Retry
    45  	backend := New(suite.state,
    46  		suite.colClient,
    47  		nil,
    48  		suite.blocks,
    49  		suite.headers,
    50  		suite.collections,
    51  		suite.transactions,
    52  		suite.receipts,
    53  		suite.results,
    54  		suite.chainID,
    55  		metrics.NewNoopCollector(),
    56  		nil,
    57  		false,
    58  		DefaultMaxHeightRange,
    59  		nil,
    60  		nil,
    61  		suite.log,
    62  		DefaultSnapshotHistoryLimit,
    63  	)
    64  	retry := newRetry().SetBackend(backend).Activate()
    65  	backend.retry = retry
    66  
    67  	retry.RegisterTransaction(block.Header.Height, transactionBody)
    68  
    69  	suite.colClient.On("SendTransaction", mock.Anything, mock.Anything).Return(&access.SendTransactionResponse{}, nil)
    70  
    71  	// Don't retry on every height
    72  	retry.Retry(block.Header.Height + 1)
    73  
    74  	suite.colClient.AssertNotCalled(suite.T(), "SendTransaction", mock.Anything, mock.Anything)
    75  
    76  	// Retry every `retryFrequency`
    77  	retry.Retry(block.Header.Height + retryFrequency)
    78  
    79  	suite.colClient.AssertNumberOfCalls(suite.T(), "SendTransaction", 1)
    80  
    81  	// do not retry if expired
    82  	retry.Retry(block.Header.Height + retryFrequency + flow.DefaultTransactionExpiry)
    83  
    84  	// Should've still only been called once
    85  	suite.colClient.AssertNumberOfCalls(suite.T(), "SendTransaction", 1)
    86  
    87  	suite.assertAllExpectations()
    88  }
    89  
    90  // TestSuccessfulTransactionsDontRetry tests that the retry mechanism will send retries at specific times
    91  func (suite *Suite) TestSuccessfulTransactionsDontRetry() {
    92  
    93  	ctx := context.Background()
    94  	collection := unittest.CollectionFixture(1)
    95  	transactionBody := collection.Transactions[0]
    96  	block := unittest.BlockFixture()
    97  	// Height needs to be at least DefaultTransactionExpiry before we start doing retries
    98  	block.Header.Height = flow.DefaultTransactionExpiry + 1
    99  	transactionBody.SetReferenceBlockID(block.ID())
   100  
   101  	light := collection.Light()
   102  	suite.state.On("Final").Return(suite.snapshot, nil).Maybe()
   103  	// transaction storage returns the corresponding transaction
   104  	suite.transactions.On("ByID", transactionBody.ID()).Return(transactionBody, nil)
   105  	// collection storage returns the corresponding collection
   106  	suite.collections.On("LightByTransactionID", transactionBody.ID()).Return(&light, nil)
   107  	// block storage returns the corresponding block
   108  	suite.blocks.On("ByCollectionID", collection.ID()).Return(&block, nil)
   109  
   110  	txID := transactionBody.ID()
   111  	blockID := block.ID()
   112  	exeEventReq := execution.GetTransactionResultRequest{
   113  		BlockId:       blockID[:],
   114  		TransactionId: txID[:],
   115  	}
   116  	exeEventResp := execution.GetTransactionResultResponse{
   117  		Events: nil,
   118  	}
   119  
   120  	_, enIDs := suite.setupReceipts(&block)
   121  	suite.snapshot.On("Identities", mock.Anything).Return(enIDs, nil)
   122  	connFactory := suite.setupConnectionFactory()
   123  
   124  	// Setup Handler + Retry
   125  	backend := New(suite.state,
   126  		suite.colClient,
   127  		nil,
   128  		suite.blocks,
   129  		suite.headers,
   130  		suite.collections,
   131  		suite.transactions,
   132  		suite.receipts,
   133  		suite.results,
   134  		suite.chainID,
   135  		metrics.NewNoopCollector(),
   136  		connFactory,
   137  		false,
   138  		DefaultMaxHeightRange,
   139  		nil,
   140  		nil,
   141  		suite.log,
   142  		DefaultSnapshotHistoryLimit,
   143  	)
   144  	retry := newRetry().SetBackend(backend).Activate()
   145  	backend.retry = retry
   146  
   147  	retry.RegisterTransaction(block.Header.Height, transactionBody)
   148  
   149  	suite.colClient.On("SendTransaction", mock.Anything, mock.Anything).Return(&access.SendTransactionResponse{}, nil)
   150  
   151  	// return not found to return finalized status
   152  	suite.execClient.On("GetTransactionResult", ctx, &exeEventReq).Return(&exeEventResp, status.Errorf(codes.NotFound, "not found")).Once()
   153  	// first call - when block under test is greater height than the sealed head, but execution node does not know about Tx
   154  	result, err := backend.GetTransactionResult(ctx, txID)
   155  	suite.checkResponse(result, err)
   156  
   157  	// status should be finalized since the sealed blocks is smaller in height
   158  	suite.Assert().Equal(flow.TransactionStatusFinalized, result.Status)
   159  
   160  	// Don't retry now now that block is finalized
   161  	retry.Retry(block.Header.Height + 1)
   162  
   163  	suite.colClient.AssertNotCalled(suite.T(), "SendTransaction", mock.Anything, mock.Anything)
   164  
   165  	// Don't retry now now that block is finalized
   166  	retry.Retry(block.Header.Height + retryFrequency)
   167  
   168  	suite.colClient.AssertNotCalled(suite.T(), "SendTransaction", mock.Anything, mock.Anything)
   169  
   170  	// Don't retry now now that block is finalized
   171  	retry.Retry(block.Header.Height + retryFrequency + flow.DefaultTransactionExpiry)
   172  
   173  	// Should've still should not be called
   174  	suite.colClient.AssertNotCalled(suite.T(), "SendTransaction", mock.Anything, mock.Anything)
   175  
   176  	suite.assertAllExpectations()
   177  }