diff --git a/internal/contractgateway/rest2eth.go b/internal/contractgateway/rest2eth.go index f9826574..399d3d43 100644 --- a/internal/contractgateway/rest2eth.go +++ b/internal/contractgateway/rest2eth.go @@ -186,18 +186,19 @@ func (r *rest2eth) addRoutes(router *httprouter.Router) { } type restCmd struct { - from string - addr string - value json.Number - abiMethod *ethbinding.ABIMethod - abiMethodElem *ethbinding.ABIElementMarshaling - abiEvent *ethbinding.ABIEvent - abiEventElem *ethbinding.ABIElementMarshaling - isDeploy bool - deployMsg *messages.DeployContract - body map[string]interface{} - msgParams []interface{} - blocknumber string + from string + addr string + value json.Number + abiMethod *ethbinding.ABIMethod + abiMethodElem *ethbinding.ABIElementMarshaling + abiEvent *ethbinding.ABIEvent + abiEventElem *ethbinding.ABIElementMarshaling + isDeploy bool + deployMsg *messages.DeployContract + body map[string]interface{} + msgParams []interface{} + blocknumber string + transactionHash string } func (r *rest2eth) resolveABI(res http.ResponseWriter, req *http.Request, params httprouter.Params, c *restCmd, addrParam string, refresh bool) (a ethbinding.ABIMarshaling, validAddress bool, err error) { @@ -419,7 +420,10 @@ func (r *rest2eth) resolveParams(res http.ResponseWriter, req *http.Request, par return } - if c.abiEvent != nil { + c.blocknumber = getFlyParam("blocknumber", req) + c.transactionHash = getFlyParam("transaction", req) + + if c.abiEvent != nil || c.transactionHash != "" { return } @@ -446,8 +450,6 @@ func (r *rest2eth) resolveParams(res http.ResponseWriter, req *http.Request, par } } - c.blocknumber = getFlyParam("blocknumber", req) - return } @@ -461,7 +463,11 @@ func (r *rest2eth) restHandler(res http.ResponseWriter, req *http.Request, param if c.abiEvent != nil { r.subscribeEvent(res, req, c.addr, c.abiEventElem, c.body) - } else if (req.Method == http.MethodPost && !c.abiMethod.IsConstant()) && !getFlyParamBool("call", req) { + } else if c.transactionHash != "" { + r.lookupTransaction(res, req, c.transactionHash, c.abiMethod) + } else if req.Method != http.MethodPost || c.abiMethod.IsConstant() || getFlyParamBool("call", req) { + r.callContract(res, req, c.from, c.addr, c.value, c.abiMethod, c.msgParams, c.blocknumber) + } else { if c.from == "" { err = ethconnecterrors.Errorf(ethconnecterrors.RESTGatewayMissingFromAddress, utils.GetenvOrDefaultLowerCase("PREFIX_SHORT", "fly"), utils.GetenvOrDefaultLowerCase("PREFIX_LONG", "firefly")) r.restErrReply(res, req, err, 400) @@ -470,8 +476,6 @@ func (r *rest2eth) restHandler(res http.ResponseWriter, req *http.Request, param } else { r.sendTransaction(res, req, c.from, c.addr, c.value, c.abiMethodElem, c.msgParams) } - } else { - r.callContract(res, req, c.from, c.addr, c.value, c.abiMethod, c.msgParams, c.blocknumber) } } @@ -665,6 +669,62 @@ func (r *rest2eth) callContract(res http.ResponseWriter, req *http.Request, from return } +func (r *rest2eth) lookupTransaction(res http.ResponseWriter, req *http.Request, txHash string, abiMethod *ethbinding.ABIMethod) { + info, err := eth.GetTransactionInfo(req.Context(), r.rpc, txHash) + if err != nil { + r.restErrReply(res, req, err, 500) + return + } + inputArgs, err := eth.DecodeInputs(abiMethod, info.Input) + if err != nil { + r.restErrReply(res, req, err, 500) + return + } + + resBody := messages.TransactionInfo{ + BlockHash: info.BlockHash, + BlockNumberHex: info.BlockNumber, + From: info.From, + To: info.To, + GasHex: info.Gas, + GasPriceHex: info.GasPrice, + Hash: info.Hash, + NonceHex: info.Nonce, + TransactionIndexHex: info.TransactionIndex, + ValueHex: info.Value, + Input: info.Input, + InputArgs: inputArgs, + } + + if info.BlockNumber != nil { + resBody.BlockNumberStr = info.BlockNumber.ToInt().Text(10) + } + if info.Gas != nil { + resBody.GasStr = strconv.FormatUint(uint64(*info.Gas), 10) + } + if info.GasPrice != nil { + resBody.GasPriceStr = info.GasPrice.ToInt().Text(10) + } + if info.Nonce != nil { + resBody.NonceStr = strconv.FormatUint(uint64(*info.Nonce), 10) + } + if info.TransactionIndex != nil { + resBody.TransactionIndexStr = strconv.FormatUint(uint64(*info.TransactionIndex), 10) + } + if info.Value != nil { + resBody.ValueStr = info.Value.ToInt().Text(10) + } + + resBytes, _ := json.MarshalIndent(&resBody, "", " ") + status := 200 + log.Infof("<-- %s %s [%d]", req.Method, req.URL, status) + log.Debugf("<-- %s", resBytes) + res.Header().Set("Content-Type", "application/json") + res.WriteHeader(status) + res.Write(resBytes) + return +} + func (r *rest2eth) restAsyncReply(res http.ResponseWriter, req *http.Request, asyncResponse *messages.AsyncSentMsg) { resBytes, _ := json.Marshal(asyncResponse) status := 202 // accepted diff --git a/internal/contractgateway/rest2eth_test.go b/internal/contractgateway/rest2eth_test.go index 0c0267f8..67cd18b0 100644 --- a/internal/contractgateway/rest2eth_test.go +++ b/internal/contractgateway/rest2eth_test.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "fmt" + "math/big" "net/http" "net/http/httptest" "strings" @@ -1839,8 +1840,6 @@ func TestSendTransactionWithIDAsyncSuccess(t *testing.T) { mcr := r.cr.(*contractregistrymocks.ContractStore) expectContractSuccess(t, mcr, to) - req.Header.Set("X-Firefly-PrivateFrom", "0xdC416B907857Fa8c0e0d55ec21766Ee3546D5f90") - req.Header.Set("X-Firefly-PrivateFor", "0xE7E32f0d5A2D55B2aD27E0C2d663807F28f7c745,0xB92F8CebA52fFb5F08f870bd355B1d32f0fd9f7C") req.Header.Set("X-Firefly-ID", "my-id") router.ServeHTTP(res, req) @@ -1854,10 +1853,97 @@ func TestSendTransactionWithIDAsyncSuccess(t *testing.T) { assert.Equal(true, dispatcher.asyncDispatchAck) assert.Equal(from, dispatcher.asyncDispatchMsg["from"]) assert.Equal(to, dispatcher.asyncDispatchMsg["to"]) - assert.Equal("0xdC416B907857Fa8c0e0d55ec21766Ee3546D5f90", dispatcher.asyncDispatchMsg["privateFrom"]) - assert.Equal("0xE7E32f0d5A2D55B2aD27E0C2d663807F28f7c745", dispatcher.asyncDispatchMsg["privateFor"].([]interface{})[0]) - assert.Equal("0xB92F8CebA52fFb5F08f870bd355B1d32f0fd9f7C", dispatcher.asyncDispatchMsg["privateFor"].([]interface{})[1]) assert.Equal("my-id", dispatcher.asyncDispatchMsg["headers"].(map[string]interface{})["id"]) mcr.AssertExpectations(t) } + +func TestGetTransactionInfoSuccess(t *testing.T) { + assert := assert.New(t) + dir := tempdir() + defer cleanup(dir) + + gas := uint64(5000) + nonce := uint64(12) + txindex := uint64(0) + + dispatcher := &mockREST2EthDispatcher{} + r, router, res, req := newTestREST2EthAndMsg(dispatcher, "", "contract-name", nil) + mcr := r.cr.(*contractregistrymocks.ContractStore) + mcr.On("ResolveContractAddress", "contract-name").Return("contract-address", nil) + expectContractSuccess(t, mcr, "contract-address") + mockRPC := r.rpc.(*ethmocks.RPCClient) + mockRPC.On("CallContext", context.Background(), mock.Anything, "eth_getTransactionByHash", "0x9999"). + Run(func(args mock.Arguments) { + result := args[1].(*eth.TxnInfo) + *result = eth.TxnInfo{ + BlockNumber: (*ethbinding.HexBigInt)(big.NewInt(15)), + Gas: (*ethbinding.HexUint64)(&gas), + GasPrice: (*ethbinding.HexBigInt)(big.NewInt(0)), + Nonce: (*ethbinding.HexUint64)(&nonce), + TransactionIndex: (*ethbinding.HexUint64)(&txindex), + Value: (*ethbinding.HexBigInt)(big.NewInt(10)), + Input: ðbinding.HexBytes{ + 0x09, 0x23, 0xf7, 0x0f, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + } + }). + Return(nil) + + req.Header.Set("X-Firefly-Transaction", "0x9999") + router.ServeHTTP(res, req) + + assert.Equal(200, res.Result().StatusCode) + var resultBody map[string]interface{} + err := json.NewDecoder(res.Result().Body).Decode(&resultBody) + assert.NoError(err) + assert.Equal(map[string]interface{}{"i": "0", "s": ""}, resultBody["inputArgs"]) +} + +func TestGetTransactionInfoFail(t *testing.T) { + assert := assert.New(t) + dir := tempdir() + defer cleanup(dir) + + dispatcher := &mockREST2EthDispatcher{} + r, router, res, req := newTestREST2EthAndMsg(dispatcher, "", "contract-name", nil) + mcr := r.cr.(*contractregistrymocks.ContractStore) + mcr.On("ResolveContractAddress", "contract-name").Return("contract-address", nil) + expectContractSuccess(t, mcr, "contract-address") + mockRPC := r.rpc.(*ethmocks.RPCClient) + mockRPC.On("CallContext", context.Background(), mock.Anything, "eth_getTransactionByHash", "0x9999"). + Return(fmt.Errorf("pop")) + + req.Header.Set("X-Firefly-Transaction", "0x9999") + router.ServeHTTP(res, req) + + assert.Equal(500, res.Result().StatusCode) +} + +func TestGetTransactionInfoDecodeFail(t *testing.T) { + assert := assert.New(t) + dir := tempdir() + defer cleanup(dir) + + dispatcher := &mockREST2EthDispatcher{} + r, router, res, req := newTestREST2EthAndMsg(dispatcher, "", "contract-name", nil) + mcr := r.cr.(*contractregistrymocks.ContractStore) + mcr.On("ResolveContractAddress", "contract-name").Return("contract-address", nil) + expectContractSuccess(t, mcr, "contract-address") + mockRPC := r.rpc.(*ethmocks.RPCClient) + mockRPC.On("CallContext", context.Background(), mock.Anything, "eth_getTransactionByHash", "0x9999"). + Run(func(args mock.Arguments) { + result := args[1].(*eth.TxnInfo) + *result = eth.TxnInfo{ + Input: ðbinding.HexBytes{}, + } + }). + Return(nil) + + req.Header.Set("X-Firefly-Transaction", "0x9999") + router.ServeHTTP(res, req) + + assert.Equal(500, res.Result().StatusCode) +} diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 352cda19..c58e4da9 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -320,6 +320,11 @@ const ( // SecurityModuleNoAuthContext missing auth context in context object at point security module is invoked SecurityModuleNoAuthContext = "No auth context" + // TransactionQueryFailed transaction lookup failed + TransactionQueryFailed = "Failed to query transaction: %s" + // TransactionQueryMethodMismatch transaction input did not match the method queried + TransactionQueryMethodMismatch = "Method signature did not match: %s != %s" + // TransactionSendConstructorPackArgs RLP encoding failure for a constructor TransactionSendConstructorPackArgs = "Packing arguments for constructor: %s" // TransactionSendMethodPackArgs RLP encoding failure for a method diff --git a/internal/eth/txn.go b/internal/eth/txn.go index 02313a47..ff1ae301 100644 --- a/internal/eth/txn.go +++ b/internal/eth/txn.go @@ -63,6 +63,21 @@ type TxnReceipt struct { TransactionIndex *ethbinding.HexUint `json:"transactionIndex"` } +// TxnInfo is the detailed transaction info returned by eth_getTransactionByXXXXX +type TxnInfo struct { + BlockHash *ethbinding.Hash `json:"blockHash,omitempty"` + BlockNumber *ethbinding.HexBigInt `json:"blockNumber,omitempty"` + From *ethbinding.Address `json:"from,omitempty"` + To *ethbinding.Address `json:"to,omitempty"` + Gas *ethbinding.HexUint64 `json:"gas"` + GasPrice *ethbinding.HexBigInt `json:"gasPrice"` + Hash *ethbinding.Hash `json:"hash"` + Nonce *ethbinding.HexUint64 `json:"nonce"` + TransactionIndex *ethbinding.HexUint64 `json:"transactionIndex"` + Value *ethbinding.HexBigInt `json:"value"` + Input *ethbinding.HexBytes `json:"input"` +} + // NewContractDeployTxn builds a new ethereum transaction from the supplied // SendTranasction message func NewContractDeployTxn(msg *messages.DeployContract, signer TXSigner) (tx *Txn, err error) { @@ -155,6 +170,33 @@ func CallMethod(ctx context.Context, rpc RPCClient, signer TXSigner, from, addr return ProcessRLPBytes(methodABI.Outputs, retBytes), nil } +// Decode the "input" bytes from a transaction, which are composed of a method ID + encoded arguments +func DecodeInputs(method *ethbinding.ABIMethod, inputs *ethbinding.HexBytes) (map[string]interface{}, error) { + methodIDLen := len(method.ID) + expectedMethod := hex.EncodeToString(method.ID) + if len(*inputs) < methodIDLen { + return nil, fmt.Errorf(errors.TransactionQueryMethodMismatch, "unknown", expectedMethod) + } + inputMethod := hex.EncodeToString((*inputs)[:methodIDLen]) + if inputMethod != expectedMethod { + log.Infof("Method did not match: %s != %s", inputMethod, expectedMethod) + return nil, fmt.Errorf(errors.TransactionQueryMethodMismatch, inputMethod, expectedMethod) + } + return ProcessRLPBytes(method.Inputs, (*inputs)[methodIDLen:]), nil +} + +func GetTransactionInfo(ctx context.Context, rpc RPCClient, txHash string) (*TxnInfo, error) { + log.Debugf("Retrieving transaction %s", txHash) + var txn TxnInfo + if err := rpc.CallContext(ctx, &txn, "eth_getTransactionByHash", txHash); err != nil { + return nil, fmt.Errorf(errors.RPCCallReturnedError, "eth_getTransactionByHash", err) + } + if txn.Input == nil { + return nil, fmt.Errorf(errors.TransactionQueryFailed, txHash) + } + return &txn, nil +} + func addErrorToRetval(retval map[string]interface{}, retBytes []byte, rawRetval interface{}, err error) { log.Warnf(err.Error()) retval["rlp"] = hex.EncodeToString(retBytes) diff --git a/internal/eth/txn_test.go b/internal/eth/txn_test.go index 078b8ce4..56fcd1d3 100644 --- a/internal/eth/txn_test.go +++ b/internal/eth/txn_test.go @@ -1828,3 +1828,83 @@ func TestProcessOutputsBadArgs(t *testing.T) { err := processOutputs(methodABI.Outputs, []interface{}{"arg1"}, make(map[string]interface{})) assert.EqualError(err, "Expected slice type in JSON/RPC response for retval1 (int32[]). Received string") } + +func TestGetTransactionInfoFail(t *testing.T) { + assert := assert.New(t) + rpc := testRPCClient{} + + info, err := GetTransactionInfo(context.Background(), &rpc, "0x12345") + assert.Regexp("Failed to query transaction: 0x12345", err) + assert.Nil(info) +} + +func TestGetTransactionInfoError(t *testing.T) { + assert := assert.New(t) + rpc := testRPCClient{ + mockError: fmt.Errorf("pop"), + } + + info, err := GetTransactionInfo(context.Background(), &rpc, "0x12345") + assert.Regexp("pop", err) + assert.Nil(info) +} + +func TestGetTransactionInfo(t *testing.T) { + assert := assert.New(t) + rpc := testRPCClient{ + resultWrangler: func(txn interface{}) { + json.Unmarshal([]byte(`{"input":"0x01"}`), &txn) + }, + } + + info, err := GetTransactionInfo(context.Background(), &rpc, "0x12345") + assert.NoError(err) + assert.NotNil(info) + assert.Equal(ethbinding.HexBytes{1}, *info.Input) +} + +func TestDecodeInputsBadSignature(t *testing.T) { + assert := assert.New(t) + method := ethbinding.ABIMethod{ + ID: []byte{1, 2, 3, 4}, + Inputs: ethbinding.ABIArguments{}, + } + inputs := ethbinding.HexBytes{1} + + args, err := DecodeInputs(&method, &inputs) + assert.Regexp("Method signature did not match", err) + assert.Nil(args) +} + +func TestDecodeInputsNoMatch(t *testing.T) { + assert := assert.New(t) + method := ethbinding.ABIMethod{ + ID: []byte{1, 2, 3, 4}, + Inputs: ethbinding.ABIArguments{}, + } + inputs := ethbinding.HexBytes{1, 2, 3, 5} + + args, err := DecodeInputs(&method, &inputs) + assert.Regexp("Method signature did not match", err) + assert.Nil(args) +} + +func TestDecodeInputs(t *testing.T) { + assert := assert.New(t) + method := ethbinding.ABIMethod{ + ID: []byte{1, 2, 3, 4}, + Inputs: ethbinding.ABIArguments{ + { + Name: "arg1", + Type: ethbinding.ABIType{}, + }, + }, + } + inputs := ethbinding.HexBytes{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + expectedArgs := make(map[string]interface{}, 0) + expectedArgs["arg1"] = "1" + + args, err := DecodeInputs(&method, &inputs) + assert.NoError(err) + assert.Equal(expectedArgs, args) +} diff --git a/internal/messages/messages.go b/internal/messages/messages.go index ed670cdc..2443e335 100644 --- a/internal/messages/messages.go +++ b/internal/messages/messages.go @@ -162,6 +162,30 @@ type TransactionReceipt struct { RegisterAs string `json:"registerAs,omitempty"` } +// TransactionInfo is the detailed transaction info returned by eth_getTransactionByXXXXX +// For the big numbers, we pass a simple string as well as a full +// ethereum hex encoding version +type TransactionInfo struct { + BlockHash *ethbinding.Hash `json:"blockHash,omitempty"` + BlockNumberStr string `json:"blockNumber,omitempty"` + BlockNumberHex *ethbinding.HexBigInt `json:"blockNumberHex,omitempty"` + From *ethbinding.Address `json:"from,omitempty"` + To *ethbinding.Address `json:"to,omitempty"` + GasStr string `json:"gas"` + GasHex *ethbinding.HexUint64 `json:"gasHex"` + GasPriceStr string `json:"gasPrice"` + GasPriceHex *ethbinding.HexBigInt `json:"gasPriceHex"` + Hash *ethbinding.Hash `json:"hash"` + NonceStr string `json:"nonce"` + NonceHex *ethbinding.HexUint64 `json:"nonceHex"` + TransactionIndexStr string `json:"transactionIndex"` + TransactionIndexHex *ethbinding.HexUint64 `json:"transactionIndexHex"` + ValueStr string `json:"value"` + ValueHex *ethbinding.HexBigInt `json:"valueHex"` + Input *ethbinding.HexBytes `json:"input"` + InputArgs map[string]interface{} `json:"inputArgs"` +} + // ErrorReply is type ErrorReply struct { ReplyCommon diff --git a/internal/openapi/abi2swagger.go b/internal/openapi/abi2swagger.go index b2f717d4..b25dd113 100644 --- a/internal/openapi/abi2swagger.go +++ b/internal/openapi/abi2swagger.go @@ -395,6 +395,18 @@ func (c *ABI2Swagger) getCommonParameters() map[string]spec.Parameter { Type: "string", }, } + params["transactionParam"] = spec.Parameter{ + ParamProps: spec.ParamProps{ + Description: fmt.Sprintf("Query the details for the provided transaction hash (header: x-%s-transaction)", utils.GetenvOrDefaultLowerCase("PREFIX_LONG", "firefly")), + Name: fmt.Sprintf("%s-transaction", utils.GetenvOrDefaultLowerCase("PREFIX_SHORT", "fly")), + In: "query", + Required: false, + AllowEmptyValue: true, + }, + SimpleSchema: spec.SimpleSchema{ + Type: "string", + }, + } return params } @@ -416,6 +428,7 @@ func (c *ABI2Swagger) addCommonParams(op *spec.Operation, isPOST bool, isConstru privacyGroupIDParam, _ := spec.NewRef("#/parameters/privacyGroupIdParam") registerParam, _ := spec.NewRef("#/parameters/registerParam") blocknumberParam, _ := spec.NewRef("#/parameters/blocknumberParam") + transactionParam, _ := spec.NewRef("#/parameters/transactionParam") op.Parameters = append(op.Parameters, spec.Parameter{ Refable: spec.Refable{ Ref: idParam, @@ -474,6 +487,12 @@ func (c *ABI2Swagger) addCommonParams(op *spec.Operation, isPOST bool, isConstru }, }) } + } else { + op.Parameters = append(op.Parameters, spec.Parameter{ + Refable: spec.Refable{ + Ref: transactionParam, + }, + }) } if isConstructor { op.Parameters = append(op.Parameters, spec.Parameter{ diff --git a/test/abicoderv2_example.swagger.json b/test/abicoderv2_example.swagger.json index 6d9b34d0..948cec47 100644 --- a/test/abicoderv2_example.swagger.json +++ b/test/abicoderv2_example.swagger.json @@ -41,6 +41,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -225,6 +228,13 @@ "in": "query", "allowEmptyValue": true }, + "transactionParam": { + "type": "string", + "description": "Query the details for the provided transaction hash (header: x-firefly-transaction)", + "name": "fly-transaction", + "in": "query", + "allowEmptyValue": true + }, "valueParam": { "type": "integer", "description": "Ether value to send with the transaction (header: x-firefly-ethvalue)", diff --git a/test/erc20.swagger.json b/test/erc20.swagger.json index 74d69332..dbf87f1e 100644 --- a/test/erc20.swagger.json +++ b/test/erc20.swagger.json @@ -384,6 +384,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -532,6 +535,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -673,6 +679,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -821,6 +830,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -969,6 +981,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -1103,6 +1118,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -1251,6 +1269,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -1406,6 +1427,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -1817,6 +1841,13 @@ "in": "query", "allowEmptyValue": true }, + "transactionParam": { + "type": "string", + "description": "Query the details for the provided transaction hash (header: x-firefly-transaction)", + "name": "fly-transaction", + "in": "query", + "allowEmptyValue": true + }, "valueParam": { "type": "integer", "description": "Ether value to send with the transaction (header: x-firefly-ethvalue)", diff --git a/test/lotsoftypes.swagger.json b/test/lotsoftypes.swagger.json index 5e751312..f306e79d 100644 --- a/test/lotsoftypes.swagger.json +++ b/test/lotsoftypes.swagger.json @@ -89,6 +89,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -258,6 +261,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -419,6 +425,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -845,6 +854,13 @@ "in": "query", "allowEmptyValue": true }, + "transactionParam": { + "type": "string", + "description": "Query the details for the provided transaction hash (header: x-firefly-transaction)", + "name": "fly-transaction", + "in": "query", + "allowEmptyValue": true + }, "valueParam": { "type": "integer", "description": "Ether value to send with the transaction (header: x-firefly-ethvalue)", diff --git a/test/unnamedinput.swagger.json b/test/unnamedinput.swagger.json index c4e79fa6..0e51f087 100644 --- a/test/unnamedinput.swagger.json +++ b/test/unnamedinput.swagger.json @@ -176,6 +176,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -315,6 +318,9 @@ }, { "$ref": "#/parameters/gaspriceParam" + }, + { + "$ref": "#/parameters/transactionParam" } ], "responses": { @@ -540,6 +546,13 @@ "in": "query", "allowEmptyValue": true }, + "transactionParam": { + "type": "string", + "description": "Query the details for the provided transaction hash (header: x-firefly-transaction)", + "name": "fly-transaction", + "in": "query", + "allowEmptyValue": true + }, "valueParam": { "type": "integer", "description": "Ether value to send with the transaction (header: x-firefly-ethvalue)",