github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/transactions_test.go (about)

     1  package routes
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"testing"
    11  
    12  	mocks "github.com/stretchr/testify/mock"
    13  	"golang.org/x/text/cases"
    14  	"golang.org/x/text/language"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  
    18  	"github.com/onflow/flow/protobuf/go/flow/entities"
    19  
    20  	"github.com/onflow/flow-go/access"
    21  	"github.com/onflow/flow-go/access/mock"
    22  	"github.com/onflow/flow-go/engine/access/rest/models"
    23  	"github.com/onflow/flow-go/engine/access/rest/util"
    24  	"github.com/onflow/flow-go/model/flow"
    25  	"github.com/onflow/flow-go/utils/unittest"
    26  )
    27  
    28  func getTransactionReq(id string, expandResult bool, blockIdQuery string, collectionIdQuery string) *http.Request {
    29  	u, _ := url.Parse(fmt.Sprintf("/v1/transactions/%s", id))
    30  	q := u.Query()
    31  
    32  	if expandResult {
    33  		// by default expand all since we test expanding with converters
    34  		q.Add("expand", "result")
    35  	}
    36  
    37  	if blockIdQuery != "" {
    38  		q.Add("block_id", blockIdQuery)
    39  	}
    40  
    41  	if collectionIdQuery != "" {
    42  		q.Add("collection_id", collectionIdQuery)
    43  	}
    44  
    45  	u.RawQuery = q.Encode()
    46  
    47  	req, _ := http.NewRequest("GET", u.String(), nil)
    48  	return req
    49  }
    50  
    51  func getTransactionResultReq(id string, blockIdQuery string, collectionIdQuery string) *http.Request {
    52  	u, _ := url.Parse(fmt.Sprintf("/v1/transaction_results/%s", id))
    53  	q := u.Query()
    54  	if blockIdQuery != "" {
    55  		q.Add("block_id", blockIdQuery)
    56  	}
    57  
    58  	if collectionIdQuery != "" {
    59  		q.Add("collection_id", collectionIdQuery)
    60  	}
    61  
    62  	u.RawQuery = q.Encode()
    63  
    64  	req, _ := http.NewRequest("GET", u.String(), nil)
    65  	return req
    66  }
    67  
    68  func createTransactionReq(body interface{}) *http.Request {
    69  	jsonBody, _ := json.Marshal(body)
    70  	req, _ := http.NewRequest("POST", "/v1/transactions", bytes.NewBuffer(jsonBody))
    71  	return req
    72  }
    73  
    74  func TestGetTransactions(t *testing.T) {
    75  	t.Run("get by ID without results", func(t *testing.T) {
    76  		backend := &mock.API{}
    77  		tx := unittest.TransactionFixture()
    78  		req := getTransactionReq(tx.ID().String(), false, "", "")
    79  
    80  		backend.Mock.
    81  			On("GetTransaction", mocks.Anything, tx.ID()).
    82  			Return(&tx.TransactionBody, nil)
    83  
    84  		expected := fmt.Sprintf(`
    85  			{
    86  			   "id":"%s",
    87  			   "script":"YWNjZXNzKGFsbCkgZnVuIG1haW4oKSB7fQ==",
    88                 "arguments": [],
    89  			   "reference_block_id":"%s",
    90  			   "gas_limit":"10",
    91  			   "payer":"8c5303eaa26202d6",
    92  			   "proposal_key":{
    93  				  "address":"8c5303eaa26202d6",
    94  				  "key_index":"1",
    95  				  "sequence_number":"0"
    96  			   },
    97  			   "authorizers":[
    98  				  "8c5303eaa26202d6"
    99  			   ],
   100                 "payload_signatures": [],
   101  			   "envelope_signatures":[
   102  				  {
   103  					 "address":"8c5303eaa26202d6",
   104  					 "key_index":"1",
   105  					 "signature":"%s"
   106  				  }
   107  			   ],
   108  			   "_links":{
   109  				  "_self":"/v1/transactions/%s"
   110  			   },
   111  				"_expandable": {
   112  					"result": "/v1/transaction_results/%s"
   113  				}
   114  			}`,
   115  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID())
   116  
   117  		assertOKResponse(t, req, expected, backend)
   118  	})
   119  
   120  	t.Run("Get by ID with results", func(t *testing.T) {
   121  		backend := &mock.API{}
   122  
   123  		tx := unittest.TransactionFixture()
   124  		txr := transactionResultFixture(tx)
   125  
   126  		backend.Mock.
   127  			On("GetTransaction", mocks.Anything, tx.ID()).
   128  			Return(&tx.TransactionBody, nil)
   129  
   130  		backend.Mock.
   131  			On("GetTransactionResult", mocks.Anything, tx.ID(), flow.ZeroID, flow.ZeroID, entities.EventEncodingVersion_JSON_CDC_V0).
   132  			Return(txr, nil)
   133  
   134  		req := getTransactionReq(tx.ID().String(), true, "", "")
   135  
   136  		expected := fmt.Sprintf(`
   137  			{
   138  			   "id":"%s",
   139  			   "script":"YWNjZXNzKGFsbCkgZnVuIG1haW4oKSB7fQ==",
   140                 "arguments": [],
   141  			   "reference_block_id":"%s",
   142  			   "gas_limit":"10",
   143  			   "payer":"8c5303eaa26202d6",
   144  			   "proposal_key":{
   145  				  "address":"8c5303eaa26202d6",
   146  				  "key_index":"1",
   147  				  "sequence_number":"0"
   148  			   },
   149  			   "authorizers":[
   150  				  "8c5303eaa26202d6"
   151  			   ],
   152                 "payload_signatures": [],
   153  			   "envelope_signatures":[
   154  				  {
   155  					 "address":"8c5303eaa26202d6",
   156  					 "key_index":"1",
   157  					 "signature":"%s"
   158  				  }
   159  			   ],
   160  				"result": {
   161  					"block_id": "%s",
   162  					"collection_id": "%s",
   163  					"execution": "Success",
   164  					"status": "Sealed",
   165  					"status_code": 1,
   166  					"error_message": "",
   167  					"computation_used": "0",
   168  					"events": [
   169  						{
   170  							"type": "flow.AccountCreated",
   171  							"transaction_id": "%s",
   172  							"transaction_index": "0",
   173  							"event_index": "0",
   174  							"payload": ""
   175  						}
   176  					],
   177  					"_links": {
   178  						"_self": "/v1/transaction_results/%s"
   179  					}
   180  				},
   181                 "_expandable": {},
   182  			   "_links":{
   183  				  "_self":"/v1/transactions/%s"
   184  			   }
   185  			}`,
   186  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ReferenceBlockID, txr.CollectionID, tx.ID(), tx.ID(), tx.ID())
   187  		assertOKResponse(t, req, expected, backend)
   188  	})
   189  
   190  	t.Run("get by ID Invalid", func(t *testing.T) {
   191  		backend := &mock.API{}
   192  
   193  		req := getTransactionReq("invalid", false, "", "")
   194  		expected := `{"code":400, "message":"invalid ID format"}`
   195  		assertResponse(t, req, http.StatusBadRequest, expected, backend)
   196  	})
   197  
   198  	t.Run("get by ID non-existing", func(t *testing.T) {
   199  		backend := &mock.API{}
   200  
   201  		tx := unittest.TransactionFixture()
   202  		req := getTransactionReq(tx.ID().String(), false, "", "")
   203  
   204  		backend.Mock.
   205  			On("GetTransaction", mocks.Anything, tx.ID()).
   206  			Return(nil, status.Error(codes.NotFound, "transaction not found"))
   207  
   208  		expected := `{"code":404, "message":"Flow resource not found: transaction not found"}`
   209  		assertResponse(t, req, http.StatusNotFound, expected, backend)
   210  	})
   211  }
   212  
   213  func TestGetTransactionResult(t *testing.T) {
   214  	id := unittest.IdentifierFixture()
   215  	bid := unittest.IdentifierFixture()
   216  	cid := unittest.IdentifierFixture()
   217  	txr := &access.TransactionResult{
   218  		Status:     flow.TransactionStatusSealed,
   219  		StatusCode: 10,
   220  		Events: []flow.Event{
   221  			unittest.EventFixture(flow.EventAccountCreated, 1, 0, id, 200),
   222  		},
   223  		ErrorMessage: "",
   224  		BlockID:      bid,
   225  		CollectionID: cid,
   226  	}
   227  	txr.Events[0].Payload = []byte(`test payload`)
   228  	expected := fmt.Sprintf(`{
   229  			"block_id": "%s",
   230  			"collection_id": "%s",
   231  			"execution": "Success",
   232  			"status": "Sealed",
   233  			"status_code": 10,
   234  			"error_message": "",
   235  			"computation_used": "0",
   236  			"events": [
   237  				{
   238  					"type": "flow.AccountCreated",
   239  					"transaction_id": "%s",
   240  					"transaction_index": "1",
   241  					"event_index": "0",
   242  					"payload": "%s"
   243  				}
   244  			],
   245  			"_links": {
   246  				"_self": "/v1/transaction_results/%s"
   247  			}
   248  		}`, bid.String(), cid.String(), id.String(), util.ToBase64(txr.Events[0].Payload), id.String())
   249  
   250  	t.Run("get by transaction ID", func(t *testing.T) {
   251  		backend := &mock.API{}
   252  		req := getTransactionResultReq(id.String(), "", "")
   253  
   254  		backend.Mock.
   255  			On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, flow.ZeroID, entities.EventEncodingVersion_JSON_CDC_V0).
   256  			Return(txr, nil)
   257  
   258  		assertOKResponse(t, req, expected, backend)
   259  	})
   260  
   261  	t.Run("get by block ID", func(t *testing.T) {
   262  		backend := &mock.API{}
   263  
   264  		req := getTransactionResultReq(id.String(), bid.String(), "")
   265  
   266  		backend.Mock.
   267  			On("GetTransactionResult", mocks.Anything, id, bid, flow.ZeroID, entities.EventEncodingVersion_JSON_CDC_V0).
   268  			Return(txr, nil)
   269  
   270  		assertOKResponse(t, req, expected, backend)
   271  	})
   272  
   273  	t.Run("get by collection ID", func(t *testing.T) {
   274  		backend := &mock.API{}
   275  		req := getTransactionResultReq(id.String(), "", cid.String())
   276  
   277  		backend.Mock.
   278  			On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, cid, entities.EventEncodingVersion_JSON_CDC_V0).
   279  			Return(txr, nil)
   280  
   281  		assertOKResponse(t, req, expected, backend)
   282  	})
   283  
   284  	t.Run("get execution statuses", func(t *testing.T) {
   285  		backend := &mock.API{}
   286  
   287  		testVectors := map[*access.TransactionResult]string{{
   288  			Status:       flow.TransactionStatusExpired,
   289  			ErrorMessage: "",
   290  		}: string(models.FAILURE_RESULT), {
   291  			Status:       flow.TransactionStatusSealed,
   292  			ErrorMessage: "cadence runtime exception",
   293  		}: string(models.FAILURE_RESULT), {
   294  			Status:       flow.TransactionStatusFinalized,
   295  			ErrorMessage: "",
   296  		}: string(models.PENDING_RESULT), {
   297  			Status:       flow.TransactionStatusPending,
   298  			ErrorMessage: "",
   299  		}: string(models.PENDING_RESULT), {
   300  			Status:       flow.TransactionStatusExecuted,
   301  			ErrorMessage: "",
   302  		}: string(models.PENDING_RESULT), {
   303  			Status:       flow.TransactionStatusSealed,
   304  			ErrorMessage: "",
   305  		}: string(models.SUCCESS_RESULT)}
   306  
   307  		for txResult, err := range testVectors {
   308  			txResult.BlockID = bid
   309  			txResult.CollectionID = cid
   310  			req := getTransactionResultReq(id.String(), "", "")
   311  			backend.Mock.
   312  				On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, flow.ZeroID, entities.EventEncodingVersion_JSON_CDC_V0).
   313  				Return(txResult, nil).
   314  				Once()
   315  
   316  			expectedResp := fmt.Sprintf(`{
   317  				"block_id": "%s",
   318  				"collection_id": "%s",
   319  				"execution": "%s",
   320  				"status": "%s",
   321  				"status_code": 0,
   322  				"error_message": "%s",
   323  				"computation_used": "0",
   324  				"events": [],
   325  				"_links": {
   326  					"_self": "/v1/transaction_results/%s"
   327  				}
   328  			}`, bid.String(), cid.String(), err, cases.Title(language.English).String(strings.ToLower(txResult.Status.String())), txResult.ErrorMessage, id.String())
   329  			assertOKResponse(t, req, expectedResp, backend)
   330  		}
   331  	})
   332  
   333  	t.Run("get by ID Invalid", func(t *testing.T) {
   334  		backend := &mock.API{}
   335  
   336  		req := getTransactionResultReq("invalid", "", "")
   337  
   338  		expected := `{"code":400, "message":"invalid ID format"}`
   339  		assertResponse(t, req, http.StatusBadRequest, expected, backend)
   340  	})
   341  }
   342  
   343  func TestCreateTransaction(t *testing.T) {
   344  	backend := &mock.API{}
   345  
   346  	t.Run("create", func(t *testing.T) {
   347  		tx := unittest.TransactionBodyFixture()
   348  		tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()}
   349  		tx.Arguments = [][]uint8{}
   350  		req := createTransactionReq(unittest.CreateSendTxHttpPayload(tx))
   351  
   352  		backend.Mock.
   353  			On("SendTransaction", mocks.Anything, &tx).
   354  			Return(nil)
   355  
   356  		expected := fmt.Sprintf(`
   357  			{
   358  			   "id":"%s",
   359  			   "script":"YWNjZXNzKGFsbCkgZnVuIG1haW4oKSB7fQ==",
   360  			   "arguments": [],
   361  			   "reference_block_id":"%s",
   362  			   "gas_limit":"10",
   363  			   "payer":"8c5303eaa26202d6",
   364  			   "proposal_key":{
   365  				  "address":"8c5303eaa26202d6",
   366  				  "key_index":"1",
   367  				  "sequence_number":"0"
   368  			   },
   369  			   "authorizers":[
   370  				  "8c5303eaa26202d6"
   371  			   ],
   372                 "payload_signatures":[
   373  				  {
   374  					 "address":"8c5303eaa26202d6",
   375  					 "key_index":"1",
   376  					 "signature":"%s"
   377  				  }
   378  			   ],
   379  			   "envelope_signatures":[
   380  				  {
   381  					 "address":"8c5303eaa26202d6",
   382  					 "key_index":"1",
   383  					 "signature":"%s"
   384  				  }
   385  			   ],
   386  				"_expandable": {
   387  					"result": "/v1/transaction_results/%s"
   388  				},
   389  			   "_links":{
   390  				  "_self":"/v1/transactions/%s"
   391  			   }
   392  			}`,
   393  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.PayloadSignatures[0].Signature), util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID())
   394  		assertOKResponse(t, req, expected, backend)
   395  	})
   396  
   397  	t.Run("post invalid transaction", func(t *testing.T) {
   398  		tests := []struct {
   399  			inputField string
   400  			inputValue string
   401  			output     string
   402  		}{
   403  			{"reference_block_id", "-1", `{"code":400, "message":"invalid reference block ID: invalid ID format"}`},
   404  			{"reference_block_id", "", `{"code":400, "message":"reference block not provided"}`},
   405  			{"gas_limit", "-1", `{"code":400, "message":"invalid gas limit: value must be an unsigned 64 bit integer"}`},
   406  			{"payer", "yo", `{"code":400, "message":"invalid payer: invalid address"}`},
   407  			{"proposal_key", "yo", `{"code":400, "message":"request body contains an invalid value for the \"proposal_key\" field (at position 461)"}`},
   408  			{"authorizers", "", `{"code":400, "message":"request body contains an invalid value for the \"authorizers\" field (at position 32)"}`},
   409  			{"authorizers", "yo", `{"code":400, "message":"request body contains an invalid value for the \"authorizers\" field (at position 34)"}`},
   410  			{"envelope_signatures", "", `{"code":400, "message":"request body contains an invalid value for the \"envelope_signatures\" field (at position 75)"}`},
   411  			{"payload_signatures", "", `{"code":400, "message":"request body contains an invalid value for the \"payload_signatures\" field (at position 292)"}`},
   412  		}
   413  
   414  		for _, test := range tests {
   415  			tx := unittest.TransactionBodyFixture()
   416  			tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()}
   417  			testTx := unittest.CreateSendTxHttpPayload(tx)
   418  			testTx[test.inputField] = test.inputValue
   419  			req := createTransactionReq(testTx)
   420  
   421  			assertResponse(t, req, http.StatusBadRequest, test.output, backend)
   422  		}
   423  	})
   424  }
   425  
   426  func transactionResultFixture(tx flow.Transaction) *access.TransactionResult {
   427  	cid := unittest.IdentifierFixture()
   428  	return &access.TransactionResult{
   429  		Status:     flow.TransactionStatusSealed,
   430  		StatusCode: 1,
   431  		Events: []flow.Event{
   432  			unittest.EventFixture(flow.EventAccountCreated, 0, 0, tx.ID(), 255),
   433  		},
   434  		ErrorMessage: "",
   435  		BlockID:      tx.ReferenceBlockID,
   436  		CollectionID: cid,
   437  	}
   438  }