Skip to content

Commit

Permalink
Merge branch 'main' of github.com:hyperledger-labs/firefly-ethconnect…
Browse files Browse the repository at this point in the history
… into mocks
  • Loading branch information
awrichar committed Sep 20, 2021
2 parents bf07afd + ebe0585 commit ed7256e
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 23 deletions.
96 changes: 78 additions & 18 deletions internal/contractgateway/rest2eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand All @@ -446,8 +450,6 @@ func (r *rest2eth) resolveParams(res http.ResponseWriter, req *http.Request, par
}
}

c.blocknumber = getFlyParam("blocknumber", req)

return
}

Expand All @@ -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)
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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
Expand Down
96 changes: 91 additions & 5 deletions internal/contractgateway/rest2eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"strings"
Expand Down Expand Up @@ -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)

Expand All @@ -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: &ethbinding.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: &ethbinding.HexBytes{},
}
}).
Return(nil)

req.Header.Set("X-Firefly-Transaction", "0x9999")
router.ServeHTTP(res, req)

assert.Equal(500, res.Result().StatusCode)
}
5 changes: 5 additions & 0 deletions internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions internal/eth/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit ed7256e

Please sign in to comment.