Skip to content

Commit

Permalink
Reflect through submissionRejected JSON body from FFTM/EVMConnect
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Broadhurst <[email protected]>
  • Loading branch information
peterbroadhurst committed Dec 21, 2023
1 parent 4645f28 commit c808a7c
Show file tree
Hide file tree
Showing 57 changed files with 2,360 additions and 130 deletions.
2 changes: 2 additions & 0 deletions internal/blockchain/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type BlockchainReceiptNotification struct {

type BlockchainRESTError struct {
Error string `json:"error,omitempty"`
// See https://github.com/hyperledger/firefly-transaction-manager/blob/main/pkg/ffcapi/submission_error.go
SubmissionRejected bool `json:"submissionRejected,omitempty"`
}

type conflictError struct {
Expand Down
32 changes: 17 additions & 15 deletions internal/blockchain/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,14 @@ func (e *Ethereum) applyOptions(ctx context.Context, body, options map[string]in
return body, nil
}

func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, errors []*abi.Entry, options map[string]interface{}) error {
func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, errors []*abi.Entry, options map[string]interface{}) (bool, error) {
if e.metrics.IsMetricsEnabled() {
e.metrics.BlockchainTransaction(address, abi.Name)
}
messageType := "SendTransaction"
body, err := e.buildEthconnectRequestBody(ctx, messageType, address, signingKey, abi, requestID, input, errors, options)
if err != nil {
return err
return true, err
}
var resErr common.BlockchainRESTError
res, err := e.client.R().
Expand All @@ -623,9 +623,9 @@ func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey
SetError(&resErr).
Post("/")
if err != nil || !res.IsSuccess() {
return common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgEthConnectorRESTErr)
return resErr.SubmissionRejected, common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgEthConnectorRESTErr)
}
return nil
return false, nil
}

func (e *Ethereum) queryContractMethod(ctx context.Context, address, signingKey string, abi *abi.Entry, input []interface{}, errors []*abi.Entry, options map[string]interface{}) (*resty.Response, error) {
Expand Down Expand Up @@ -697,7 +697,8 @@ func (e *Ethereum) SubmitBatchPin(ctx context.Context, nsOpID, networkNamespace,
method, input := e.buildBatchPinInput(ctx, version, networkNamespace, batch)

var emptyErrors []*abi.Entry
return e.invokeContractMethod(ctx, ethLocation.Address, signingKey, method, nsOpID, input, emptyErrors, nil)
_, err = e.invokeContractMethod(ctx, ethLocation.Address, signingKey, method, nsOpID, input, emptyErrors, nil)
return err
}

func (e *Ethereum) SubmitNetworkAction(ctx context.Context, nsOpID string, signingKey string, action core.NetworkActionType, location *fftypes.JSONAny) error {
Expand Down Expand Up @@ -731,10 +732,11 @@ func (e *Ethereum) SubmitNetworkAction(ctx context.Context, nsOpID string, signi
}
}
var emptyErrors []*abi.Entry
return e.invokeContractMethod(ctx, ethLocation.Address, signingKey, method, nsOpID, input, emptyErrors, nil)
_, err = e.invokeContractMethod(ctx, ethLocation.Address, signingKey, method, nsOpID, input, emptyErrors, nil)
return err
}

func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error {
func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) (bool, error) {
if e.metrics.IsMetricsEnabled() {
e.metrics.BlockchainContractDeployment()
}
Expand All @@ -754,7 +756,7 @@ func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string
}
body, err := e.applyOptions(ctx, body, options)
if err != nil {
return err
return true, err
}

var resErr common.BlockchainRESTError
Expand All @@ -767,11 +769,11 @@ func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string
if strings.Contains(string(res.Body()), "FFEC100130") {
// This error is returned by ethconnect because it does not support deploying contracts with this syntax
// Return a more helpful and clear error message
return i18n.NewError(ctx, coremsgs.MsgNotSupportedByBlockchainPlugin)
return true, i18n.NewError(ctx, coremsgs.MsgNotSupportedByBlockchainPlugin)
}
return common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgEthConnectorRESTErr)
return resErr.SubmissionRejected, common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgEthConnectorRESTErr)
}
return nil
return false, nil
}

// Check if a method supports passing extra data via conformance to ERC5750.
Expand All @@ -796,14 +798,14 @@ func (e *Ethereum) ValidateInvokeRequest(ctx context.Context, parsedMethod inter
return err
}

func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, parsedMethod interface{}, input map[string]interface{}, options map[string]interface{}, batch *blockchain.BatchPin) error {
func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, parsedMethod interface{}, input map[string]interface{}, options map[string]interface{}, batch *blockchain.BatchPin) (bool, error) {
ethereumLocation, err := e.parseContractLocation(ctx, location)
if err != nil {
return err
return true, err
}
methodInfo, orderedInput, err := e.prepareRequest(ctx, parsedMethod, input)
if err != nil {
return err
return true, err
}
if batch != nil {
err := e.checkDataSupport(ctx, methodInfo.methodABI)
Expand All @@ -815,7 +817,7 @@ func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey
}
}
if err != nil {
return err
return true, err
}
}
return e.invokeContractMethod(ctx, ethereumLocation.Address, signingKey, methodInfo.methodABI, nsOpID, orderedInput, methodInfo.errorsABI, options)
Expand Down
88 changes: 75 additions & 13 deletions internal/blockchain/ethereum/ethereum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,7 @@ func TestDeployContractOK(t *testing.T) {
assert.Equal(t, body["customOption"].(string), "customValue")
return httpmock.NewJsonResponderOrPanic(200, "")(req)
})
err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
_, err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
assert.NoError(t, err)
}

Expand All @@ -2557,10 +2557,36 @@ func TestDeployContractFFEC100130(t *testing.T) {
assert.NoError(t, err)
httpmock.RegisterResponder("POST", `http://localhost:12345/`,
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponderOrPanic(500, `{"error":"FFEC100130: failure"}`)(req)
return httpmock.NewJsonResponderOrPanic(500, fftypes.JSONAnyPtr(`{"error":"FFEC100130: failure"}`))(req)
})
err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
submissionRejected, err := e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
assert.Regexp(t, "FF10429", err)
assert.True(t, submissionRejected)
}

func TestDeployContractRevert(t *testing.T) {
e, cancel := newTestEthereum()
defer cancel()
httpmock.ActivateNonDefault(e.client.GetClient())
defer httpmock.DeactivateAndReset()
signingKey := ethHexFormatB32(fftypes.NewRandB32())
input := []interface{}{
float64(1),
"1000000000000000000000000",
}
options := map[string]interface{}{
"customOption": "customValue",
}
definitionBytes, err := json.Marshal([]interface{}{})
contractBytes, err := json.Marshal("0x123456")
assert.NoError(t, err)
httpmock.RegisterResponder("POST", `http://localhost:12345/`,
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponderOrPanic(500, fftypes.JSONAnyPtr(`{"error":"FF23021: EVM reverted", "submissionRejected": true}`))(req)
})
submissionRejected, err := e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
assert.Regexp(t, "FF10111.*FF23021", err)
assert.True(t, submissionRejected)
}

func TestDeployContractInvalidOption(t *testing.T) {
Expand Down Expand Up @@ -2591,8 +2617,9 @@ func TestDeployContractInvalidOption(t *testing.T) {
assert.Equal(t, body["customOption"].(string), "customValue")
return httpmock.NewJsonResponderOrPanic(400, "pop")(req)
})
err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
submissionRejected, err := e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
assert.Regexp(t, "FF10398", err)
assert.True(t, submissionRejected)
}

func TestDeployContractError(t *testing.T) {
Expand Down Expand Up @@ -2623,8 +2650,9 @@ func TestDeployContractError(t *testing.T) {
assert.Equal(t, body["customOption"].(string), "customValue")
return httpmock.NewJsonResponderOrPanic(400, "pop")(req)
})
err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
submissionRejected, err := e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options)
assert.Regexp(t, "FF10111", err)
assert.False(t, submissionRejected)
}

func TestInvokeContractOK(t *testing.T) {
Expand Down Expand Up @@ -2662,7 +2690,7 @@ func TestInvokeContractOK(t *testing.T) {
})
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
_, err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.NoError(t, err)
}

Expand Down Expand Up @@ -2703,7 +2731,7 @@ func TestInvokeContractWithBatchOK(t *testing.T) {

parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, nil, nil, batch)
_, err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, nil, nil, batch)
assert.NoError(t, err)
}

Expand All @@ -2721,8 +2749,9 @@ func TestInvokeContractWithBatchUnsupported(t *testing.T) {
batch := &blockchain.BatchPin{}
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, nil, nil, batch)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, nil, nil, batch)
assert.Regexp(t, "FF10443", err)
assert.True(t, submissionRejected)
}

func TestInvokeContractInvalidOption(t *testing.T) {
Expand Down Expand Up @@ -2758,8 +2787,9 @@ func TestInvokeContractInvalidOption(t *testing.T) {
})
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.Regexp(t, "FF10398", err)
assert.True(t, submissionRejected)
}

func TestInvokeContractInvalidInput(t *testing.T) {
Expand Down Expand Up @@ -2796,7 +2826,7 @@ func TestInvokeContractInvalidInput(t *testing.T) {
})
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
_, err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.Regexp(t, "unsupported type", err)
}

Expand All @@ -2816,8 +2846,9 @@ func TestInvokeContractAddressNotSet(t *testing.T) {
assert.NoError(t, err)
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.Regexp(t, "'address' not set", err)
assert.True(t, submissionRejected)
}

func TestInvokeContractEthconnectError(t *testing.T) {
Expand All @@ -2844,8 +2875,38 @@ func TestInvokeContractEthconnectError(t *testing.T) {
})
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.Regexp(t, "FF10111", err)
assert.False(t, submissionRejected)
}

func TestInvokeContractEVMConnectRejectErr(t *testing.T) {
e, cancel := newTestEthereum()
defer cancel()
httpmock.ActivateNonDefault(e.client.GetClient())
defer httpmock.DeactivateAndReset()
signingKey := ethHexFormatB32(fftypes.NewRandB32())
location := &Location{
Address: "0x12345",
}
method := testFFIMethod()
errors := testFFIErrors()
params := map[string]interface{}{
"x": float64(1),
"y": float64(2),
}
options := map[string]interface{}{}
locationBytes, err := json.Marshal(location)
assert.NoError(t, err)
httpmock.RegisterResponder("POST", `http://localhost:12345/`,
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponderOrPanic(400, fftypes.JSONAnyPtr(`{"error":"FF23021: EVM reverted", "submissionRejected": true}`))(req)
})
parsedMethod, err := e.ParseInterface(context.Background(), method, errors)
assert.NoError(t, err)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), parsedMethod, params, options, nil)
assert.Regexp(t, "FF10111", err)
assert.True(t, submissionRejected)
}

func TestInvokeContractPrepareFail(t *testing.T) {
Expand All @@ -2864,8 +2925,9 @@ func TestInvokeContractPrepareFail(t *testing.T) {
options := map[string]interface{}{}
locationBytes, err := json.Marshal(location)
assert.NoError(t, err)
err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), "wrong", params, options, nil)
submissionRejected, err := e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), "wrong", params, options, nil)
assert.Regexp(t, "FF10457", err)
assert.True(t, submissionRejected)
}

func TestParseInterfaceFailFFIMethod(t *testing.T) {
Expand Down
26 changes: 14 additions & 12 deletions internal/blockchain/fabric/fabric.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,10 +598,10 @@ func (f *Fabric) ResolveSigningKey(ctx context.Context, signingKeyInput string,
return signingKeyInput, nil
}

func (f *Fabric) invokeContractMethod(ctx context.Context, channel, chaincode, methodName, signingKey, requestID string, prefixItems []*PrefixItem, input map[string]interface{}, options map[string]interface{}) error {
func (f *Fabric) invokeContractMethod(ctx context.Context, channel, chaincode, methodName, signingKey, requestID string, prefixItems []*PrefixItem, input map[string]interface{}, options map[string]interface{}) (bool, error) {
body, err := f.buildFabconnectRequestBody(ctx, channel, chaincode, methodName, signingKey, requestID, prefixItems, input, options)
if err != nil {
return err
return true, err
}
var resErr common.BlockchainRESTError
res, err := f.client.R().
Expand All @@ -611,9 +611,9 @@ func (f *Fabric) invokeContractMethod(ctx context.Context, channel, chaincode, m
SetError(&resErr).
Post("/transactions")
if err != nil || !res.IsSuccess() {
return common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgFabconnectRESTErr)
return resErr.SubmissionRejected, common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgFabconnectRESTErr)
}
return nil
return false, nil
}

func (f *Fabric) queryContractMethod(ctx context.Context, channel, chaincode, methodName, signingKey, requestID string, prefixItems []*PrefixItem, input map[string]interface{}, options map[string]interface{}) (*resty.Response, error) {
Expand Down Expand Up @@ -696,7 +696,8 @@ func (f *Fabric) SubmitBatchPin(ctx context.Context, nsOpID, networkNamespace, s
prefixItems, pinInput := f.buildBatchPinInput(ctx, version, networkNamespace, batch)

input, _ := jsonEncodeInput(pinInput)
return f.invokeContractMethod(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode, batchPinMethodName, signingKey, nsOpID, prefixItems, input, nil)
_, err = f.invokeContractMethod(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode, batchPinMethodName, signingKey, nsOpID, prefixItems, input, nil)
return err
}

func (f *Fabric) SubmitNetworkAction(ctx context.Context, nsOpID string, signingKey string, action core.NetworkActionType, location *fftypes.JSONAny) error {
Expand Down Expand Up @@ -734,7 +735,8 @@ func (f *Fabric) SubmitNetworkAction(ctx context.Context, nsOpID string, signing
}

input, _ := jsonEncodeInput(pinInput)
return f.invokeContractMethod(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode, methodName, signingKey, nsOpID, prefixItems, input, nil)
_, err = f.invokeContractMethod(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode, methodName, signingKey, nsOpID, prefixItems, input, nil)
return err
}

func (f *Fabric) buildFabconnectRequestBody(ctx context.Context, channel, chaincode, methodName, signingKey, requestID string, prefixItems []*PrefixItem, input map[string]interface{}, options map[string]interface{}) (map[string]interface{}, error) {
Expand Down Expand Up @@ -768,8 +770,8 @@ func (f *Fabric) buildFabconnectRequestBody(ctx context.Context, channel, chainc
return body, nil
}

func (f *Fabric) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error {
return i18n.NewError(ctx, coremsgs.MsgNotSupportedByBlockchainPlugin)
func (f *Fabric) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) (bool, error) {
return true, i18n.NewError(ctx, coremsgs.MsgNotSupportedByBlockchainPlugin)
}

func (f *Fabric) ValidateInvokeRequest(ctx context.Context, parsedMethod interface{}, input map[string]interface{}, hasMessage bool) error {
Expand All @@ -778,24 +780,24 @@ func (f *Fabric) ValidateInvokeRequest(ctx context.Context, parsedMethod interfa
return err
}

func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, parsedMethod interface{}, input map[string]interface{}, options map[string]interface{}, batch *blockchain.BatchPin) error {
func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, parsedMethod interface{}, input map[string]interface{}, options map[string]interface{}, batch *blockchain.BatchPin) (bool, error) {

method, _, err := f.recoverFFI(ctx, parsedMethod)
if err != nil {
return err
return true, err
}

fabricOnChainLocation, err := parseContractLocation(ctx, location)
if err != nil {
return err
return true, err
}

// Build the payload schema for the method parameters
prefixItems := make([]*PrefixItem, len(method.Params))
for i, param := range method.Params {
var paramSchema ffiParamSchema
if err := json.Unmarshal(param.Schema.Bytes(), &paramSchema); err != nil {
return i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, fmt.Sprintf("%s.schema", param.Name))
return true, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, fmt.Sprintf("%s.schema", param.Name))
}

prefixItems[i] = &PrefixItem{
Expand Down
Loading

0 comments on commit c808a7c

Please sign in to comment.