Skip to content

Commit

Permalink
Merge pull request #1436 from hyperledger/submission-reject
Browse files Browse the repository at this point in the history
Reflect through submissionRejected JSON body from FFTM/EVMConnect
  • Loading branch information
peterbroadhurst authored Dec 21, 2023
2 parents 4645f28 + fd554b7 commit 85da316
Show file tree
Hide file tree
Showing 69 changed files with 2,577 additions and 179 deletions.
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

0 comments on commit 85da316

Please sign in to comment.