Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect through submissionRejected JSON body from FFTM/EVMConnect #1436

Merged
merged 10 commits into from
Dec 21, 2023
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:

- stack-type: ethereum
blockchain-connector: ethconnect
test-suite: TestEthereumGatewayE2ESuite
test-suite: TestEthereumGatewayLegacyEthE2ESuite
database-type: sqlite3
token-provider: erc1155
multiparty-enabled: false
Expand Down
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ run:
skip-dirs:
- "mocks"
- "ffconfig"
- "test/e2e"
linters-settings:
golint: {}
gocritic:
Expand Down
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
36 changes: 19 additions & 17 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{}) (submissionRejected bool, err 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{}) (submissionRejected bool, err error) {
if e.metrics.IsMetricsEnabled() {
e.metrics.BlockchainContractDeployment()
}
Expand All @@ -752,9 +754,9 @@ func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string
if signingKey != "" {
body["from"] = signingKey
}
body, err := e.applyOptions(ctx, body, options)
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 Expand Up @@ -998,7 +1000,7 @@ func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *fftypes.F
if err != nil {
return nil, i18n.WrapError(ctx, err, coremsgs.MsgFFIGenerationFailed, "unable to deserialize JSON as ABI")
}
if len(*input.ABI) == 0 {
if input.ABI == nil || len(*input.ABI) == 0 {
return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "ABI is empty")
}
return ffi2abi.ConvertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
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
Loading
Loading