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