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

Separate "define" and "publish" for contract interfaces, and allow deleting unpublished interfaces #1279

Merged
merged 4 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions db/migrations/postgres/000112_add_ffi_networkname.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN;
DROP INDEX ffi_networkname;
ALTER TABLE ffi DROP COLUMN published;
ALTER TABLE ffi DROP COLUMN network_name;
COMMIT;
7 changes: 7 additions & 0 deletions db/migrations/postgres/000112_add_ffi_networkname.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN;
ALTER TABLE ffi ADD COLUMN published BOOLEAN DEFAULT false;
UPDATE ffi SET published = true WHERE message_id IS NOT NULL;
ALTER TABLE ffi ADD COLUMN network_name VARCHAR(64);
UPDATE ffi SET network_name = name WHERE message_id IS NOT NULL;
CREATE UNIQUE INDEX ffi_networkname ON ffi(namespace,network_name,version);
COMMIT;
3 changes: 3 additions & 0 deletions db/migrations/sqlite/000112_add_ffi_networkname.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DROP INDEX ffi_networkname;
ALTER TABLE ffi DROP COLUMN published;
ALTER TABLE ffi DROP COLUMN network_name;
5 changes: 5 additions & 0 deletions db/migrations/sqlite/000112_add_ffi_networkname.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE ffi ADD COLUMN published BOOLEAN DEFAULT false;
UPDATE ffi SET published = true WHERE message_id IS NOT NULL;
ALTER TABLE ffi ADD COLUMN network_name VARCHAR(64);
UPDATE ffi SET network_name = name WHERE message_id IS NOT NULL;
CREATE UNIQUE INDEX ffi_networkname ON ffi(namespace,network_name,version);
4 changes: 2 additions & 2 deletions docs/reference/types/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,13 @@ nav_order: 9
| `message` | The UUID of the broadcast message that was used to publish this FFI to the network | [`UUID`](simpletypes#uuid) |
| `namespace` | The namespace of the FFI | `string` |
| `name` | The name of the FFI - usually matching the smart contract name | `string` |
| `networkName` | The shared interface name within the multiparty network | `string` |
| `networkName` | The published name of the FFI within the multiparty network | `string` |
| `description` | A description of the smart contract this FFI represents | `string` |
| `version` | A version for the FFI - use of semantic versioning such as 'v1.0.1' is encouraged | `string` |
| `methods` | An array of smart contract method definitions | [`FFIMethod[]`](#ffimethod) |
| `events` | An array of smart contract event definitions | [`FFIEvent[]`](#ffievent) |
| `errors` | An array of smart contract error definitions | [`FFIError[]`](#ffierror) |
| `published` | True if the interface has been published to a multiparty network | `bool` |
| `published` | Indicates if the FFI is published to other members of the multiparty network | `bool` |

## FFIMethod

Expand Down
9,708 changes: 5,406 additions & 4,302 deletions docs/swagger/swagger.yaml

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions internal/apiserver/route_delete_contract_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"net/http"

"github.com/hyperledger/firefly-common/pkg/ffapi"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/internal/coremsgs"
)

var deleteContractInterface = &ffapi.Route{
Name: "deleteContractInterface",
Path: "contracts/interfaces/{interfaceId}",
Method: http.MethodDelete,
PathParams: []*ffapi.PathParam{
{Name: "interfaceId", Description: coremsgs.APIParamsContractInterfaceID},
},
QueryParams: nil,
Description: coremsgs.APIEndpointsDeleteContractInterface,
JSONInputValue: nil,
JSONOutputValue: nil,
JSONOutputCodes: []int{http.StatusNoContent}, // Sync operation, no output
Extensions: &coreExtensions{
CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
interfaceID, err := fftypes.ParseUUID(cr.ctx, r.PP["interfaceId"])
if err != nil {
return nil, err
}
return nil, cr.or.Contracts().DeleteFFI(cr.ctx, interfaceID)
},
},
}
56 changes: 56 additions & 0 deletions internal/apiserver/route_delete_contract_interface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright © 2021 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"fmt"
"net/http/httptest"
"testing"

"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/mocks/contractmocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestDeleteContractInterface(t *testing.T) {
o, r := newTestAPIServer()
o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
mcm := &contractmocks.Manager{}
o.On("Contracts").Return(mcm)
u := fftypes.NewUUID()
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/namespaces/ns1/contracts/interfaces/%s", u), nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

mcm.On("DeleteFFI", mock.Anything, u).Return(nil)
r.ServeHTTP(res, req)

assert.Equal(t, 204, res.Result().StatusCode)
}

func TestDeleteContractInterfaceBadID(t *testing.T) {
o, r := newTestAPIServer()
o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/v1/namespaces/ns1/contracts/interfaces/bad"), nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

r.ServeHTTP(res, req)

assert.Equal(t, 400, res.Result().StatusCode)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down
52 changes: 52 additions & 0 deletions internal/apiserver/route_post_contract_interface_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"net/http"
"strings"

"github.com/hyperledger/firefly-common/pkg/ffapi"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/internal/coremsgs"
"github.com/hyperledger/firefly/pkg/core"
)

var postContractInterfacePublish = &ffapi.Route{
Name: "postContractInterfacePublish",
Path: "contracts/interfaces/{name}/{version}/publish",
Method: http.MethodPost,
PathParams: []*ffapi.PathParam{
{Name: "name", Description: coremsgs.APIParamsContractInterfaceName},
{Name: "version", Description: coremsgs.APIParamsContractInterfaceVersion},
},
QueryParams: []*ffapi.QueryParam{
{Name: "confirm", Description: coremsgs.APIConfirmQueryParam, IsBool: true},
},
Description: coremsgs.APIEndpointsPostContractInterfacePublish,
JSONInputValue: func() interface{} { return &core.DefinitionPublish{} },
JSONOutputValue: func() interface{} { return &fftypes.FFI{} },
JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK},
Extensions: &coreExtensions{
CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) {
waitConfirm := strings.EqualFold(r.QP["confirm"], "true")
r.SuccessStatus = syncRetcode(waitConfirm)
input := r.Input.(*core.DefinitionPublish)
return cr.or.DefinitionSender().PublishFFI(cr.ctx, r.PP["name"], r.PP["version"], input.NetworkName, waitConfirm)
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright © 2021 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"

"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/mocks/definitionsmocks"
"github.com/hyperledger/firefly/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestPostContractInterfacePublish(t *testing.T) {
o, r := newTestAPIServer()
o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
mds := &definitionsmocks.Sender{}
o.On("DefinitionSender").Return(mds)
input := core.TokenPool{}
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(&input)
req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces/ffi1/1.0/publish", &buf)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()
ffi := &fftypes.FFI{}

mds.On("PublishFFI", mock.Anything, "ffi1", "1.0", "", false).Return(ffi, nil)
r.ServeHTTP(res, req)

assert.Equal(t, 202, res.Result().StatusCode)
}
4 changes: 3 additions & 1 deletion internal/apiserver/route_post_new_contract_interface.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -33,6 +33,7 @@ var postNewContractInterface = &ffapi.Route{
PathParams: nil,
QueryParams: []*ffapi.QueryParam{
{Name: "confirm", Description: coremsgs.APIConfirmQueryParam, IsBool: true, Example: "true"},
{Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true},
},
Description: coremsgs.APIEndpointsPostNewContractInterface,
JSONInputValue: func() interface{} { return &fftypes.FFI{} },
Expand All @@ -46,6 +47,7 @@ var postNewContractInterface = &ffapi.Route{
waitConfirm := strings.EqualFold(r.QP["confirm"], "true")
r.SuccessStatus = syncRetcode(waitConfirm)
ffi := r.Input.(*fftypes.FFI)
ffi.Published = strings.EqualFold(r.QP["publish"], "true")
err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm)
return ffi, err
},
Expand Down
4 changes: 0 additions & 4 deletions internal/apiserver/route_post_token_pool_publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"net/http/httptest"
"testing"

"github.com/hyperledger/firefly/mocks/assetmocks"
"github.com/hyperledger/firefly/mocks/definitionsmocks"
"github.com/hyperledger/firefly/pkg/core"
"github.com/stretchr/testify/assert"
Expand All @@ -32,9 +31,7 @@ import (
func TestPostTokenPoolPublish(t *testing.T) {
o, r := newTestAPIServer()
o.On("Authorize", mock.Anything, mock.Anything).Return(nil)
mam := &assetmocks.Manager{}
mds := &definitionsmocks.Sender{}
o.On("Assets").Return(mam)
o.On("DefinitionSender").Return(mds)
input := core.TokenPool{}
var buf bytes.Buffer
Expand All @@ -44,7 +41,6 @@ func TestPostTokenPoolPublish(t *testing.T) {
res := httptest.NewRecorder()
pool := &core.TokenPool{}

mam.On("GetTokenPoolByNameOrID", mock.Anything, "pool1").Return(pool, nil)
mds.On("PublishTokenPool", mock.Anything, "pool1", "", false).Return(pool, nil)
r.ServeHTTP(res, req)

Expand Down
2 changes: 2 additions & 0 deletions internal/apiserver/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var routes = append(
getWebSockets,
}),
namespacedRoutes([]*ffapi.Route{
deleteContractInterface,
deleteContractListener,
deleteData,
deleteSubscription,
Expand Down Expand Up @@ -128,6 +129,7 @@ var routes = append(
postContractAPIQuery,
postContractAPIListeners,
postContractInterfaceGenerate,
postContractInterfacePublish,
postContractDeploy,
postContractInvoke,
postContractQuery,
Expand Down
30 changes: 24 additions & 6 deletions internal/contracts/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Manager interface {
GetFFIs(ctx context.Context, filter ffapi.AndFilter) ([]*fftypes.FFI, *ffapi.FilterResult, error)
ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error
ResolveFFIReference(ctx context.Context, ref *fftypes.FFIReference) error
DeleteFFI(ctx context.Context, id *fftypes.UUID) error

DeployContract(ctx context.Context, req *core.ContractDeployRequest, waitConfirm bool) (interface{}, error)
InvokeContract(ctx context.Context, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error)
Expand Down Expand Up @@ -141,7 +142,13 @@ func (cm *contractManager) newFFISchemaCompiler() *jsonschema.Compiler {
}

func (cm *contractManager) GetFFI(ctx context.Context, name, version string) (*fftypes.FFI, error) {
return cm.database.GetFFI(ctx, cm.namespace, name, version)
ffi, err := cm.database.GetFFI(ctx, cm.namespace, name, version)
if err != nil {
return nil, err
} else if ffi == nil {
return nil, i18n.NewError(ctx, coremsgs.Msg404NotFound)
}
return ffi, nil
}

func (cm *contractManager) GetFFIWithChildren(ctx context.Context, name, version string) (*fftypes.FFI, error) {
Expand Down Expand Up @@ -566,11 +573,6 @@ func (cm *contractManager) ResolveFFI(ctx context.Context, ffi *fftypes.FFI) err
return err
}

existing, err := cm.database.GetFFI(ctx, cm.namespace, ffi.Name, ffi.Version)
if existing != nil && err == nil {
return i18n.NewError(ctx, coremsgs.MsgContractInterfaceExists, ffi.Namespace, ffi.Name, ffi.Version)
}

methodPathNames := map[string]bool{}
for _, method := range ffi.Methods {
method.Interface = ffi.ID
Expand Down Expand Up @@ -948,3 +950,19 @@ func (cm *contractManager) buildInvokeMessage(ctx context.Context, in *core.Mess
return nil, i18n.NewError(ctx, coremsgs.MsgInvalidMessageType, allowedTypes)
}
}

func (cm *contractManager) DeleteFFI(ctx context.Context, id *fftypes.UUID) error {
return cm.database.RunAsGroup(ctx, func(ctx context.Context) error {
ffi, err := cm.GetFFIByID(ctx, id)
if err != nil {
return err
}
if ffi == nil {
return i18n.NewError(ctx, coremsgs.Msg404NotFound)
}
if ffi.Published {
return i18n.NewError(ctx, coremsgs.MsgCannotDeletePublished)
}
return cm.database.DeleteFFI(ctx, cm.namespace, id)
})
}
Loading