diff --git a/Makefile b/Makefile index cce56c32ed..dfee557d9d 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,8 @@ $(eval $(call makemock, internal/broadcast, Manager, broadcast $(eval $(call makemock, internal/privatemessaging, Manager, privatemessagingmocks)) $(eval $(call makemock, internal/shareddownload, Manager, shareddownloadmocks)) $(eval $(call makemock, internal/shareddownload, Callbacks, shareddownloadmocks)) -$(eval $(call makemock, internal/definitions, DefinitionHandler, definitionsmocks)) +$(eval $(call makemock, internal/definitions, Handler, definitionsmocks)) +$(eval $(call makemock, internal/definitions, Sender, definitionsmocks)) $(eval $(call makemock, internal/events, EventManager, eventmocks)) $(eval $(call makemock, internal/namespace, Manager, namespacemocks)) $(eval $(call makemock, internal/networkmap, Manager, networkmapmocks)) diff --git a/db/migrations/postgres/000094_allow_local_definitions.down.sql b/db/migrations/postgres/000094_allow_local_definitions.down.sql new file mode 100644 index 0000000000..b4cd87f28f --- /dev/null +++ b/db/migrations/postgres/000094_allow_local_definitions.down.sql @@ -0,0 +1 @@ +-- No down migration (can't add back NOT NULL constraint) diff --git a/db/migrations/postgres/000094_allow_local_definitions.up.sql b/db/migrations/postgres/000094_allow_local_definitions.up.sql new file mode 100644 index 0000000000..ed135221c3 --- /dev/null +++ b/db/migrations/postgres/000094_allow_local_definitions.up.sql @@ -0,0 +1,16 @@ +BEGIN; +ALTER TABLE identities RENAME COLUMN messages_claim TO messages_claim_old; +ALTER TABLE identities ADD COLUMN messages_claim UUID; +UPDATE identities SET messages_claim = messages_claim_old; +ALTER TABLE identities DROP COLUMN messages_claim_old; + +ALTER TABLE ffi RENAME COLUMN message_id TO message_id_old; +ALTER TABLE ffi ADD COLUMN message_id UUID; +UPDATE ffi SET message_id = message_id_old; +ALTER TABLE ffi DROP COLUMN message_id_old; + +ALTER TABLE contractapis RENAME COLUMN message_id TO message_id_old; +ALTER TABLE contractapis ADD COLUMN message_id UUID; +UPDATE contractapis SET message_id = message_id_old; +ALTER TABLE contractapis DROP COLUMN message_id_old; +COMMIT; diff --git a/db/migrations/sqlite/000094_allow_local_definitions.down.sql b/db/migrations/sqlite/000094_allow_local_definitions.down.sql new file mode 100644 index 0000000000..299c848d84 --- /dev/null +++ b/db/migrations/sqlite/000094_allow_local_definitions.down.sql @@ -0,0 +1 @@ +-- No down migration (can't add back NOT NULL constraint) \ No newline at end of file diff --git a/db/migrations/sqlite/000094_allow_local_definitions.up.sql b/db/migrations/sqlite/000094_allow_local_definitions.up.sql new file mode 100644 index 0000000000..9e53cabf7e --- /dev/null +++ b/db/migrations/sqlite/000094_allow_local_definitions.up.sql @@ -0,0 +1,14 @@ +ALTER TABLE identities RENAME COLUMN messages_claim TO messages_claim_old; +ALTER TABLE identities ADD COLUMN messages_claim UUID; +UPDATE identities SET messages_claim = messages_claim_old; +ALTER TABLE identities DROP COLUMN messages_claim_old; + +ALTER TABLE ffi RENAME COLUMN message_id TO message_id_old; +ALTER TABLE ffi ADD COLUMN message_id UUID; +UPDATE ffi SET message_id = message_id_old; +ALTER TABLE ffi DROP COLUMN message_id_old; + +ALTER TABLE contractapis RENAME COLUMN message_id TO message_id_old; +ALTER TABLE contractapis ADD COLUMN message_id UUID; +UPDATE contractapis SET message_id = message_id_old; +ALTER TABLE contractapis DROP COLUMN message_id_old; diff --git a/docs/reference/config.md b/docs/reference/config.md index b182b10b4d..693d296b00 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -336,7 +336,7 @@ nav_order: 2 |Key|Description|Type|Default Value| |---|-----------|----|-------------| -|type|The Data Exchange plugin to use|`string`|`ffdx` +|type|The Data Exchange plugin to use|`string`|`` ## dataexchange.ffdx @@ -585,11 +585,11 @@ nav_order: 2 |Key|Description|Type|Default Value| |---|-----------|----|-------------| -|defaultKey|A default signing key for blockchain transactions within this namespace|`string`|`` +|defaultkey|A default signing key for blockchain transactions within this namespace|`string`|`` |description|A description for the namespace|`string`|`` |name|The name of the namespace (must be unique)|`string`|`` |plugins|The list of plugins for this namespace|`string`|`` -|remoteName|The namespace name to be sent in plugin calls, if it differs from namespace name|`string`|`` +|remotename|The namespace name to be sent in plugin calls, if it differs from namespace name|`string`|`` ## namespaces.predefined[].multiparty diff --git a/internal/apiserver/route_get_data_blob.go b/internal/apiserver/route_get_data_blob.go index 3cd7388ff1..993db912d3 100644 --- a/internal/apiserver/route_get_data_blob.go +++ b/internal/apiserver/route_get_data_blob.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" ) @@ -40,6 +41,9 @@ var getDataBlob = &ffapi.Route{ JSONOutputCodes: []int{http.StatusOK}, Extensions: &coreExtensions{ FilterFactory: database.MessageQueryFactory, + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { blob, reader, err := cr.or.Data().DownloadBlob(cr.ctx, r.PP["dataid"]) if err == nil { diff --git a/internal/apiserver/route_get_data_blob_test.go b/internal/apiserver/route_get_data_blob_test.go index 9e823a4356..01086c9afc 100644 --- a/internal/apiserver/route_get_data_blob_test.go +++ b/internal/apiserver/route_get_data_blob_test.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/mocks/datamocks" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -33,6 +34,7 @@ func TestGetDataBlob(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} o.On("Data").Return(mdm) + o.On("MultiParty").Return(&multipartymocks.Manager{}) req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/data/abcd1234/blob", nil) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() diff --git a/internal/apiserver/route_get_group_by_id.go b/internal/apiserver/route_get_group_by_id.go index 4ceaeb5265..1af9340812 100644 --- a/internal/apiserver/route_get_group_by_id.go +++ b/internal/apiserver/route_get_group_by_id.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -37,9 +38,11 @@ var getGroupByHash = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Group{} }, JSONOutputCodes: []int{http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.PrivateMessaging() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { - output, err = cr.or.PrivateMessaging().GetGroupByID(cr.ctx, r.PP["hash"]) - return output, err + return cr.or.PrivateMessaging().GetGroupByID(cr.ctx, r.PP["hash"]) }, }, } diff --git a/internal/apiserver/route_get_groups.go b/internal/apiserver/route_get_groups.go index 1972d3e70b..6afc20338b 100644 --- a/internal/apiserver/route_get_groups.go +++ b/internal/apiserver/route_get_groups.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" ) @@ -37,6 +38,9 @@ var getGroups = &ffapi.Route{ JSONOutputCodes: []int{http.StatusOK}, Extensions: &coreExtensions{ FilterFactory: database.GroupQueryFactory, + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.PrivateMessaging() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { return filterResult(cr.or.PrivateMessaging().GetGroups(cr.ctx, cr.filter)) }, diff --git a/internal/apiserver/route_get_status_batchmanager.go b/internal/apiserver/route_get_status_batchmanager.go index 50979fed28..ab1c3fe9fb 100644 --- a/internal/apiserver/route_get_status_batchmanager.go +++ b/internal/apiserver/route_get_status_batchmanager.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/batch" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" ) var getStatusBatchManager = &ffapi.Route{ @@ -35,9 +36,11 @@ var getStatusBatchManager = &ffapi.Route{ JSONOutputValue: func() interface{} { return &batch.ManagerStatus{} }, JSONOutputCodes: []int{http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.BatchManager() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { - output = cr.or.BatchManager().Status() - return output, nil + return cr.or.BatchManager().Status(), nil }, }, } diff --git a/internal/apiserver/route_post_data.go b/internal/apiserver/route_post_data.go index 46e031211d..fe0087ed85 100644 --- a/internal/apiserver/route_post_data.go +++ b/internal/apiserver/route_post_data.go @@ -25,6 +25,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -46,6 +47,9 @@ var postData = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Data{} }, JSONOutputCodes: []int{http.StatusCreated}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { output, err = cr.or.Data().UploadJSON(cr.ctx, r.Input.(*core.DataRefOrValue)) return output, err diff --git a/internal/apiserver/route_post_data_test.go b/internal/apiserver/route_post_data_test.go index 9068f61590..b3b38333aa 100644 --- a/internal/apiserver/route_post_data_test.go +++ b/internal/apiserver/route_post_data_test.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly/mocks/datamocks" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -34,6 +35,7 @@ import ( func TestPostDataJSON(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) input := core.Data{} var buf bytes.Buffer @@ -52,6 +54,7 @@ func TestPostDataJSON(t *testing.T) { func TestPostDataJSONDefaultNS(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) input := core.Data{} var buf bytes.Buffer @@ -72,6 +75,7 @@ func TestPostDataBinary(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer @@ -97,6 +101,7 @@ func TestPostDataBinaryObjAutoMeta(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer @@ -143,6 +148,7 @@ func TestPostDataBinaryStringMetadata(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer @@ -176,6 +182,7 @@ func TestPostDataTrailingMetadata(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer @@ -207,6 +214,7 @@ func TestPostDataBinaryMissing(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer @@ -231,6 +239,7 @@ func TestPostDataBadForm(t *testing.T) { o, r := newTestAPIServer() mdm := &datamocks.Manager{} + o.On("MultiParty").Return(&multipartymocks.Manager{}) o.On("Data").Return(mdm) var b bytes.Buffer diff --git a/internal/apiserver/route_post_network_action.go b/internal/apiserver/route_post_network_action.go index 061ab95413..dcab6ac1ee 100644 --- a/internal/apiserver/route_post_network_action.go +++ b/internal/apiserver/route_post_network_action.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -35,6 +36,9 @@ var postNetworkAction = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.NetworkAction{} }, JSONOutputCodes: []int{http.StatusAccepted}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { err = cr.or.SubmitNetworkAction(cr.ctx, r.Input.(*core.NetworkAction)) return r.Input, err diff --git a/internal/apiserver/route_post_network_action_test.go b/internal/apiserver/route_post_network_action_test.go index fe401d2586..0c56f813eb 100644 --- a/internal/apiserver/route_post_network_action_test.go +++ b/internal/apiserver/route_post_network_action_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -29,6 +30,7 @@ import ( func TestPostNetworkAction(t *testing.T) { o, r := newTestAPIServer() + o.On("MultiParty").Return(&multipartymocks.Manager{}) input := core.NetworkAction{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) diff --git a/internal/apiserver/route_post_new_contract_api.go b/internal/apiserver/route_post_new_contract_api.go index a2d777fa46..5ae7496e2b 100644 --- a/internal/apiserver/route_post_new_contract_api.go +++ b/internal/apiserver/route_post_new_contract_api.go @@ -41,7 +41,9 @@ var postNewContractAPI = &ffapi.Route{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return cr.or.Contracts().BroadcastContractAPI(cr.ctx, cr.apiBaseURL, r.Input.(*core.ContractAPI), waitConfirm) + api := r.Input.(*core.ContractAPI) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm) + return api, err }, }, } diff --git a/internal/apiserver/route_post_new_contract_api_test.go b/internal/apiserver/route_post_new_contract_api_test.go index 127716835f..e287291740 100644 --- a/internal/apiserver/route_post_new_contract_api_test.go +++ b/internal/apiserver/route_post_new_contract_api_test.go @@ -22,7 +22,7 @@ import ( "net/http/httptest" "testing" - "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -30,8 +30,8 @@ import ( func TestPostNewContractAPI(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -39,8 +39,7 @@ func TestPostNewContractAPI(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false). - Return(&core.ContractAPI{}, nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -48,8 +47,8 @@ func TestPostNewContractAPI(t *testing.T) { func TestPostNewContractAPISync(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -57,8 +56,7 @@ func TestPostNewContractAPISync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true). - Return(&core.ContractAPI{}, nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/route_post_new_contract_interface.go b/internal/apiserver/route_post_new_contract_interface.go index fb761eef4c..90f62d7b16 100644 --- a/internal/apiserver/route_post_new_contract_interface.go +++ b/internal/apiserver/route_post_new_contract_interface.go @@ -41,7 +41,9 @@ var postNewContractInterface = &ffapi.Route{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - return cr.or.Contracts().BroadcastFFI(cr.ctx, r.Input.(*core.FFI), waitConfirm) + ffi := r.Input.(*core.FFI) + err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm) + return ffi, err }, }, } diff --git a/internal/apiserver/route_post_new_contract_interface_test.go b/internal/apiserver/route_post_new_contract_interface_test.go index 1b3dfa48bd..3d3d680fc7 100644 --- a/internal/apiserver/route_post_new_contract_interface_test.go +++ b/internal/apiserver/route_post_new_contract_interface_test.go @@ -22,7 +22,7 @@ import ( "net/http/httptest" "testing" - "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -30,8 +30,8 @@ import ( func TestPostNewContractInterface(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -39,8 +39,7 @@ func TestPostNewContractInterface(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), false). - Return(&core.FFI{}, nil) + mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), false).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -48,8 +47,8 @@ func TestPostNewContractInterface(t *testing.T) { func TestPostNewContractInterfaceSync(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -57,8 +56,7 @@ func TestPostNewContractInterfaceSync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), true). - Return(&core.FFI{}, nil) + mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*core.FFI"), true).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/route_post_new_datatype.go b/internal/apiserver/route_post_new_datatype.go index 53e591247d..8a683ae1ee 100644 --- a/internal/apiserver/route_post_new_datatype.go +++ b/internal/apiserver/route_post_new_datatype.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -38,10 +39,13 @@ var postNewDatatype = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Datatype{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - _, err = cr.or.Broadcast().BroadcastDatatype(cr.ctx, r.Input.(*core.Datatype), waitConfirm) + err = cr.or.DefinitionSender().DefineDatatype(cr.ctx, r.Input.(*core.Datatype), waitConfirm) return r.Input, err }, }, diff --git a/internal/apiserver/route_post_new_datatype_test.go b/internal/apiserver/route_post_new_datatype_test.go index c047b42bce..c8753f2ffd 100644 --- a/internal/apiserver/route_post_new_datatype_test.go +++ b/internal/apiserver/route_post_new_datatype_test.go @@ -22,7 +22,8 @@ import ( "net/http/httptest" "testing" - "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -30,8 +31,9 @@ import ( func TestPostNewDatatypes(t *testing.T) { o, r := newTestAPIServer() - mbm := &broadcastmocks.Manager{} - o.On("Broadcast").Return(mbm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) + o.On("MultiParty").Return(&multipartymocks.Manager{}) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -39,8 +41,7 @@ func TestPostNewDatatypes(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mbm.On("BroadcastDatatype", mock.Anything, mock.AnythingOfType("*core.Datatype"), false). - Return(&core.Message{}, nil) + mds.On("DefineDatatype", mock.Anything, mock.AnythingOfType("*core.Datatype"), false).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -48,8 +49,9 @@ func TestPostNewDatatypes(t *testing.T) { func TestPostNewDatatypesSync(t *testing.T) { o, r := newTestAPIServer() - mbm := &broadcastmocks.Manager{} - o.On("Broadcast").Return(mbm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) + o.On("MultiParty").Return(&multipartymocks.Manager{}) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -57,8 +59,7 @@ func TestPostNewDatatypesSync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mbm.On("BroadcastDatatype", mock.Anything, mock.AnythingOfType("*core.Datatype"), true). - Return(&core.Message{}, nil) + mds.On("DefineDatatype", mock.Anything, mock.AnythingOfType("*core.Datatype"), true).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/route_post_new_message_broadcast.go b/internal/apiserver/route_post_new_message_broadcast.go index bf587cd0e3..bdc6c8bc06 100644 --- a/internal/apiserver/route_post_new_message_broadcast.go +++ b/internal/apiserver/route_post_new_message_broadcast.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -38,6 +39,9 @@ var postNewMessageBroadcast = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Message{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.Broadcast() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) diff --git a/internal/apiserver/route_post_new_message_private.go b/internal/apiserver/route_post_new_message_private.go index 181c03988f..caab671313 100644 --- a/internal/apiserver/route_post_new_message_private.go +++ b/internal/apiserver/route_post_new_message_private.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -38,11 +39,13 @@ var postNewMessagePrivate = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Message{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.PrivateMessaging() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - output, err = cr.or.PrivateMessaging().SendMessage(cr.ctx, r.Input.(*core.MessageInOut), waitConfirm) - return output, err + return cr.or.PrivateMessaging().SendMessage(cr.ctx, r.Input.(*core.MessageInOut), waitConfirm) }, }, } diff --git a/internal/apiserver/route_post_new_message_requestreply.go b/internal/apiserver/route_post_new_message_requestreply.go index dc5e981307..202c0a3bcc 100644 --- a/internal/apiserver/route_post_new_message_requestreply.go +++ b/internal/apiserver/route_post_new_message_requestreply.go @@ -21,6 +21,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -35,6 +36,9 @@ var postNewMessageRequestReply = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.MessageInOut{} }, JSONOutputCodes: []int{http.StatusOK}, // Sync operation Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.PrivateMessaging() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { output, err = cr.or.RequestReply(cr.ctx, r.Input.(*core.MessageInOut)) return output, err diff --git a/internal/apiserver/route_post_new_message_requestreply_test.go b/internal/apiserver/route_post_new_message_requestreply_test.go index d6424013a2..d8e43c7b13 100644 --- a/internal/apiserver/route_post_new_message_requestreply_test.go +++ b/internal/apiserver/route_post_new_message_requestreply_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger/firefly/mocks/privatemessagingmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -29,6 +30,7 @@ import ( func TestPostNewMessageRequestReply(t *testing.T) { o, r := newTestAPIServer() + o.On("PrivateMessaging").Return(&privatemessagingmocks.Manager{}) o.On("RequestReply", mock.Anything, mock.Anything).Return(&core.MessageInOut{}, nil) input := &core.MessageInOut{} var buf bytes.Buffer diff --git a/internal/apiserver/route_post_new_node_self.go b/internal/apiserver/route_post_new_node_self.go index 47a24990aa..ef25eb4991 100644 --- a/internal/apiserver/route_post_new_node_self.go +++ b/internal/apiserver/route_post_new_node_self.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -38,6 +39,9 @@ var postNodesSelf = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Identity{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) diff --git a/internal/apiserver/route_post_new_node_self_test.go b/internal/apiserver/route_post_new_node_self_test.go index aa0ab0f69b..66aa6f4c31 100644 --- a/internal/apiserver/route_post_new_node_self_test.go +++ b/internal/apiserver/route_post_new_node_self_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/mocks/networkmapmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" @@ -32,6 +33,7 @@ func TestPostNewNodeSelf(t *testing.T) { o, r := newTestAPIServer() mnm := &networkmapmocks.Manager{} o.On("NetworkMap").Return(mnm) + o.On("MultiParty").Return(&multipartymocks.Manager{}) input := core.EmptyInput{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) diff --git a/internal/apiserver/route_post_new_organization_self.go b/internal/apiserver/route_post_new_organization_self.go index 629aa6bc2e..03ecd7e3b2 100644 --- a/internal/apiserver/route_post_new_organization_self.go +++ b/internal/apiserver/route_post_new_organization_self.go @@ -22,6 +22,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/pkg/core" ) @@ -38,6 +39,9 @@ var postNewOrganizationSelf = &ffapi.Route{ JSONOutputValue: func() interface{} { return &core.Identity{} }, JSONOutputCodes: []int{http.StatusAccepted, http.StatusOK}, Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.MultiParty() != nil + }, CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) diff --git a/internal/apiserver/route_post_new_organization_self_test.go b/internal/apiserver/route_post_new_organization_self_test.go index e9900f1f8b..e97962cb61 100644 --- a/internal/apiserver/route_post_new_organization_self_test.go +++ b/internal/apiserver/route_post_new_organization_self_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/mocks/networkmapmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" @@ -32,6 +33,7 @@ func TestNewOrganizationSelf(t *testing.T) { o, r := newTestAPIServer() mnm := &networkmapmocks.Manager{} o.On("NetworkMap").Return(mnm) + o.On("MultiParty").Return(&multipartymocks.Manager{}) input := core.EmptyInput{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) diff --git a/internal/apiserver/route_put_contract_api.go b/internal/apiserver/route_put_contract_api.go index 90a0707ed5..537571194d 100644 --- a/internal/apiserver/route_put_contract_api.go +++ b/internal/apiserver/route_put_contract_api.go @@ -46,11 +46,10 @@ var putContractAPI = &ffapi.Route{ r.SuccessStatus = syncRetcode(waitConfirm) api := r.Input.(*core.ContractAPI) api.ID, err = fftypes.ParseUUID(cr.ctx, r.PP["id"]) - var res interface{} if err == nil { - res, err = cr.or.Contracts().BroadcastContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm) } - return res, err + return api, err }, }, } diff --git a/internal/apiserver/route_put_contract_api_test.go b/internal/apiserver/route_put_contract_api_test.go index 9b5a495bbd..90754491d0 100644 --- a/internal/apiserver/route_put_contract_api_test.go +++ b/internal/apiserver/route_put_contract_api_test.go @@ -22,7 +22,7 @@ import ( "net/http/httptest" "testing" - "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -30,8 +30,8 @@ import ( func TestPutContractAPI(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -39,8 +39,7 @@ func TestPutContractAPI(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false). - Return(&core.ContractAPI{}, nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -48,8 +47,8 @@ func TestPutContractAPI(t *testing.T) { func TestPutContractAPISync(t *testing.T) { o, r := newTestAPIServer() - mcm := &contractmocks.Manager{} - o.On("Contracts").Return(mcm) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) input := core.Datatype{} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) @@ -57,8 +56,7 @@ func TestPutContractAPISync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mcm.On("BroadcastContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true). - Return(&core.ContractAPI{}, nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 094ab816e5..5379070368 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -37,6 +37,7 @@ type coreRequest struct { type coreExtensions struct { FilterFactory database.QueryFactory + EnabledIf func(or orchestrator.Orchestrator) bool CoreJSONHandler func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) CoreFormUploadHandler func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) } diff --git a/internal/apiserver/server.go b/internal/apiserver/server.go index f9bc9a50ef..58d4995aab 100644 --- a/internal/apiserver/server.go +++ b/internal/apiserver/server.go @@ -245,6 +245,14 @@ func (as *apiServer) routeHandler(hf *ffapi.HandlerFactory, mgr namespace.Manage // We also pass the Orchestrator context through ce := route.Extensions.(*coreExtensions) route.JSONHandler = func(r *ffapi.APIRequest) (output interface{}, err error) { + or, err := getOrchestrator(r.Req.Context(), mgr, route.Tag, r) + if err != nil { + return nil, err + } + if ce.EnabledIf != nil && !ce.EnabledIf(or) { + return nil, i18n.NewError(r.Req.Context(), coremsgs.MsgActionNotSupported) + } + var filter database.AndFilter if ce.FilterFactory != nil { filter, err = as.buildFilter(r.Req, ce.FilterFactory) @@ -253,11 +261,6 @@ func (as *apiServer) routeHandler(hf *ffapi.HandlerFactory, mgr namespace.Manage } } - or, err := getOrchestrator(r.Req.Context(), mgr, route.Tag, r) - if err != nil { - return nil, err - } - cr := &coreRequest{ mgr: mgr, or: or, @@ -273,6 +276,9 @@ func (as *apiServer) routeHandler(hf *ffapi.HandlerFactory, mgr namespace.Manage if err != nil { return nil, err } + if ce.EnabledIf != nil && !ce.EnabledIf(or) { + return nil, i18n.NewError(r.Req.Context(), coremsgs.MsgActionNotSupported) + } cr := &coreRequest{ mgr: mgr, diff --git a/internal/apiserver/server_test.go b/internal/apiserver/server_test.go index 38da684aa6..74c4d73a73 100644 --- a/internal/apiserver/server_test.go +++ b/internal/apiserver/server_test.go @@ -393,3 +393,44 @@ func TestFormDataBadNamespace(t *testing.T) { assert.Equal(t, 404, res.Result().StatusCode) } + +func TestJSONDisabledRoute(t *testing.T) { + mgr, o, as := newTestServer() + r := as.createMuxRouter(context.Background(), mgr) + s := httptest.NewServer(r) + defer s.Close() + + o.On("PrivateMessaging").Return(nil) + + var b bytes.Buffer + req := httptest.NewRequest("GET", "/api/v1/namespaces/ns1/groups", &b) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + + assert.Equal(t, 400, res.Result().StatusCode) +} + +func TestFormDataDisabledRoute(t *testing.T) { + mgr, o, as := newTestServer() + r := as.createMuxRouter(context.Background(), mgr) + s := httptest.NewServer(r) + defer s.Close() + + o.On("MultiParty").Return(nil) + + var b bytes.Buffer + w := multipart.NewWriter(&b) + writer, err := w.CreateFormFile("file", "filename.ext") + assert.NoError(t, err) + writer.Write([]byte(`some data`)) + w.Close() + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/data", &b) + req.Header.Set("Content-Type", w.FormDataContentType()) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + + assert.Equal(t, 400, res.Result().StatusCode) +} diff --git a/internal/assets/manager.go b/internal/assets/manager.go index 6c028c1f8d..2d92284393 100644 --- a/internal/assets/manager.go +++ b/internal/assets/manager.go @@ -76,8 +76,8 @@ type assetManager struct { txHelper txcommon.Helper identity identity.Manager syncasync syncasync.Bridge - broadcast broadcast.Manager - messaging privatemessaging.Manager + broadcast broadcast.Manager // optional + messaging privatemessaging.Manager // optional tokens map[string]tokens.Plugin metrics metrics.Manager operations operations.Manager @@ -85,7 +85,7 @@ type assetManager struct { } func NewAssetManager(ctx context.Context, ns string, di database.Plugin, ti map[string]tokens.Plugin, im identity.Manager, sa syncasync.Bridge, bm broadcast.Manager, pm privatemessaging.Manager, mm metrics.Manager, om operations.Manager, txHelper txcommon.Helper) (Manager, error) { - if di == nil || im == nil || sa == nil || bm == nil || pm == nil || ti == nil || mm == nil || om == nil { + if di == nil || im == nil || sa == nil || ti == nil || mm == nil || om == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "AssetManager") } am := &assetManager{ diff --git a/internal/assets/token_transfer.go b/internal/assets/token_transfer.go index 52ad2e12e5..a0b17d065f 100644 --- a/internal/assets/token_transfer.go +++ b/internal/assets/token_transfer.go @@ -270,8 +270,14 @@ func (s *transferSender) buildTransferMessage(ctx context.Context, in *core.Mess } switch in.Header.Type { case core.MessageTypeTransferBroadcast: + if s.mgr.broadcast == nil { + return nil, i18n.NewError(ctx, coremsgs.MsgMessagesNotSupported) + } return s.mgr.broadcast.NewBroadcast(in), nil case core.MessageTypeTransferPrivate: + if s.mgr.messaging == nil { + return nil, i18n.NewError(ctx, coremsgs.MsgMessagesNotSupported) + } return s.mgr.messaging.NewMessage(in), nil default: return nil, i18n.NewError(ctx, coremsgs.MsgInvalidMessageType, allowedTypes) diff --git a/internal/assets/token_transfer_test.go b/internal/assets/token_transfer_test.go index ac250eb4f8..5f9ae6aa41 100644 --- a/internal/assets/token_transfer_test.go +++ b/internal/assets/token_transfer_test.go @@ -752,6 +752,39 @@ func TestTransferTokensWithBroadcastMessage(t *testing.T) { mom.AssertExpectations(t) } +func TestTransferTokensWithBroadcastMessageDisabled(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + am.broadcast = nil + + msgID := fftypes.NewUUID() + hash := fftypes.NewRandB32() + transfer := &core.TokenTransferInput{ + TokenTransfer: core.TokenTransfer{ + From: "A", + To: "B", + Amount: *fftypes.NewFFBigInt(5), + }, + Pool: "pool1", + Message: &core.MessageInOut{ + Message: core.Message{ + Header: core.MessageHeader{ + ID: msgID, + }, + Hash: hash, + }, + InlineData: core.InlineData{ + { + Value: fftypes.JSONAnyPtr("test data"), + }, + }, + }, + } + + _, err := am.TransferTokens(context.Background(), transfer, false) + assert.Regexp(t, "FF10415", err) +} + func TestTransferTokensWithBroadcastMessageSendFail(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() @@ -907,6 +940,40 @@ func TestTransferTokensWithPrivateMessage(t *testing.T) { mom.AssertExpectations(t) } +func TestTransferTokensWithPrivateMessageDisabled(t *testing.T) { + am, cancel := newTestAssets(t) + defer cancel() + am.messaging = nil + + msgID := fftypes.NewUUID() + hash := fftypes.NewRandB32() + transfer := &core.TokenTransferInput{ + TokenTransfer: core.TokenTransfer{ + From: "A", + To: "B", + Amount: *fftypes.NewFFBigInt(5), + }, + Pool: "pool1", + Message: &core.MessageInOut{ + Message: core.Message{ + Header: core.MessageHeader{ + ID: msgID, + Type: core.MessageTypeTransferPrivate, + }, + Hash: hash, + }, + InlineData: core.InlineData{ + { + Value: fftypes.JSONAnyPtr("test data"), + }, + }, + }, + } + + _, err := am.TransferTokens(context.Background(), transfer, false) + assert.Regexp(t, "FF10415", err) +} + func TestTransferTokensWithInvalidMessage(t *testing.T) { am, cancel := newTestAssets(t) defer cancel() diff --git a/internal/broadcast/definition.go b/internal/broadcast/definition.go deleted file mode 100644 index 361b08784e..0000000000 --- a/internal/broadcast/definition.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2022 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 broadcast - -import ( - "context" - "encoding/json" - - "github.com/hyperledger/firefly-common/pkg/fftypes" - "github.com/hyperledger/firefly-common/pkg/i18n" - "github.com/hyperledger/firefly/internal/coremsgs" - "github.com/hyperledger/firefly/internal/data" - "github.com/hyperledger/firefly/internal/identity" - "github.com/hyperledger/firefly/pkg/core" -) - -func (bm *broadcastManager) BroadcastDefinitionAsNode(ctx context.Context, def core.Definition, tag string, waitConfirm bool) (msg *core.Message, err error) { - return bm.BroadcastDefinition(ctx, def, &core.SignerRef{ /* resolve to node default */ }, tag, waitConfirm) -} - -func (bm *broadcastManager) BroadcastDefinition(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (msg *core.Message, err error) { - - err = bm.identity.ResolveInputSigningIdentity(ctx, signingIdentity) - if err != nil { - return nil, err - } - - return bm.broadcastDefinitionCommon(ctx, def, signingIdentity, tag, waitConfirm) -} - -// BroadcastIdentityClaim is a special form of BroadcastDefinitionAsNode where the signing identity does not need to have been pre-registered -// The blockchain "key" will be normalized, but the "author" will pass through unchecked -func (bm *broadcastManager) BroadcastIdentityClaim(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (msg *core.Message, err error) { - - signingIdentity.Key, err = bm.identity.NormalizeSigningKey(ctx, signingIdentity.Key, identity.KeyNormalizationBlockchainPlugin) - if err != nil { - return nil, err - } - - return bm.broadcastDefinitionCommon(ctx, def, signingIdentity, tag, waitConfirm) -} - -func (bm *broadcastManager) broadcastDefinitionCommon(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (*core.Message, error) { - - // Serialize it into a data object, as a piece of data we can write to a message - d := &core.Data{ - Validator: core.ValidatorTypeSystemDefinition, - ID: fftypes.NewUUID(), - Namespace: bm.namespace, - Created: fftypes.Now(), - } - b, err := json.Marshal(&def) - if err == nil { - d.Value = fftypes.JSONAnyPtrBytes(b) - err = d.Seal(ctx, nil) - } - if err != nil { - return nil, i18n.WrapError(ctx, err, coremsgs.MsgSerializationFailed) - } - - // Create a broadcast message referring to the data - newMsg := &data.NewMessage{ - Message: &core.MessageInOut{ - Message: core.Message{ - Header: core.MessageHeader{ - Namespace: bm.namespace, - Type: core.MessageTypeDefinition, - SignerRef: *signingIdentity, - Topics: core.FFStringArray{def.Topic()}, - Tag: tag, - TxType: core.TransactionTypeBatchPin, - }, - Data: core.DataRefs{ - {ID: d.ID, Hash: d.Hash, ValueSize: d.ValueSize}, - }, - }, - }, - NewData: core.DataArray{d}, - AllData: core.DataArray{d}, - } - - // Broadcast the message - sender := broadcastSender{ - mgr: bm, - msg: newMsg, - resolved: true, - } - sender.setDefaults() - if waitConfirm { - err = sender.SendAndWait(ctx) - } else { - err = sender.Send(ctx) - } - return &newMsg.Message.Message, err -} diff --git a/internal/broadcast/definition_test.go b/internal/broadcast/definition_test.go deleted file mode 100644 index 40f4f2593f..0000000000 --- a/internal/broadcast/definition_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// 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 broadcast - -import ( - "fmt" - "testing" - - "github.com/hyperledger/firefly/internal/identity" - "github.com/hyperledger/firefly/mocks/identitymanagermocks" - "github.com/hyperledger/firefly/mocks/syncasyncmocks" - "github.com/hyperledger/firefly/pkg/core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestBroadcastDefinitionAsNodeConfirm(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - - msa := bm.syncasync.(*syncasyncmocks.Bridge) - mim := bm.identity.(*identitymanagermocks.Manager) - - mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - msa.On("WaitForMessage", bm.ctx, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) - - _, err := bm.BroadcastDefinitionAsNode(bm.ctx, &core.Namespace{}, core.SystemTagDefineNamespace, true) - assert.EqualError(t, err, "pop") - - msa.AssertExpectations(t) - mim.AssertExpectations(t) -} - -func TestBroadcastIdentityClaim(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - - msa := bm.syncasync.(*syncasyncmocks.Bridge) - mim := bm.identity.(*identitymanagermocks.Manager) - - mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", nil) - msa.On("WaitForMessage", bm.ctx, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) - - _, err := bm.BroadcastIdentityClaim(bm.ctx, &core.IdentityClaim{ - Identity: &core.Identity{}, - }, &core.SignerRef{ - Key: "0x1234", - }, core.SystemTagDefineNamespace, true) - assert.EqualError(t, err, "pop") - - msa.AssertExpectations(t) - mim.AssertExpectations(t) -} - -func TestBroadcastIdentityClaimFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - - mim := bm.identity.(*identitymanagermocks.Manager) - - mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", fmt.Errorf("pop")) - - _, err := bm.BroadcastIdentityClaim(bm.ctx, &core.IdentityClaim{ - Identity: &core.Identity{}, - }, &core.SignerRef{ - Key: "0x1234", - }, core.SystemTagDefineNamespace, true) - assert.EqualError(t, err, "pop") - - mim.AssertExpectations(t) -} - -func TestBroadcastDatatypeDefinitionAsNodeConfirm(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - - msa := bm.syncasync.(*syncasyncmocks.Bridge) - mim := bm.identity.(*identitymanagermocks.Manager) - - mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - msa.On("WaitForMessage", bm.ctx, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) - - _, err := bm.BroadcastDefinitionAsNode(bm.ctx, &core.Datatype{}, core.SystemTagDefineNamespace, true) - assert.EqualError(t, err, "pop") - - msa.AssertExpectations(t) - mim.AssertExpectations(t) -} - -func TestBroadcastDefinitionBadIdentity(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - - mim := bm.identity.(*identitymanagermocks.Manager) - mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - _, err := bm.BroadcastDefinition(bm.ctx, &core.Namespace{}, &core.SignerRef{ - Author: "wrong", - Key: "wrong", - }, core.SystemTagDefineNamespace, false) - assert.Regexp(t, "pop", err) -} diff --git a/internal/broadcast/manager.go b/internal/broadcast/manager.go index e71aaf2382..f3a440e705 100644 --- a/internal/broadcast/manager.go +++ b/internal/broadcast/manager.go @@ -46,12 +46,7 @@ type Manager interface { core.Named NewBroadcast(in *core.MessageInOut) sysmessaging.MessageSender - BroadcastDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) (msg *core.Message, err error) BroadcastMessage(ctx context.Context, in *core.MessageInOut, waitConfirm bool) (out *core.Message, err error) - BroadcastDefinitionAsNode(ctx context.Context, def core.Definition, tag string, waitConfirm bool) (msg *core.Message, err error) - BroadcastDefinition(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (msg *core.Message, err error) - BroadcastIdentityClaim(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (msg *core.Message, err error) - BroadcastTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) (msg *core.Message, err error) Start() error WaitStop() diff --git a/internal/broadcast/message_test.go b/internal/broadcast/message_test.go index 6ede2a3a52..954005e98d 100644 --- a/internal/broadcast/message_test.go +++ b/internal/broadcast/message_test.go @@ -63,6 +63,37 @@ func TestBroadcastMessageOk(t *testing.T) { mdm.AssertExpectations(t) } +func TestBroadcastMessageWriteFail(t *testing.T) { + bm, cancel := newTestBroadcastWithMetrics(t) + defer cancel() + mdm := bm.data.(*datamocks.Manager) + mim := bm.identity.(*identitymanagermocks.Manager) + + ctx := context.Background() + mdm.On("ResolveInlineData", ctx, mock.Anything).Return(nil) + mdm.On("WriteNewMessage", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mim.On("ResolveInputSigningIdentity", ctx, mock.Anything).Return(nil) + + msg, err := bm.BroadcastMessage(ctx, &core.MessageInOut{ + Message: core.Message{ + Header: core.MessageHeader{ + SignerRef: core.SignerRef{ + Author: "did:firefly:org/abcd", + Key: "0x12345", + }, + }, + }, + InlineData: core.InlineData{ + {Value: fftypes.JSONAnyPtr(`{"hello": "world"}`)}, + }, + }, false) + assert.EqualError(t, err, "pop") + assert.Equal(t, "ns1", msg.Header.Namespace) + + mim.AssertExpectations(t) + mdm.AssertExpectations(t) +} + func TestBroadcastMessageWaitConfirmOk(t *testing.T) { bm, cancel := newTestBroadcast(t) defer cancel() diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index dd8ba33c6c..394774cc61 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -23,7 +23,6 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-common/pkg/i18n" - "github.com/hyperledger/firefly/internal/broadcast" "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/internal/identity" "github.com/hyperledger/firefly/internal/operations" @@ -38,21 +37,19 @@ import ( type Manager interface { core.Named - BroadcastFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) (output *core.FFI, err error) GetFFI(ctx context.Context, name, version string) (*core.FFI, error) GetFFIWithChildren(ctx context.Context, name, version string) (*core.FFI, error) GetFFIByID(ctx context.Context, id *fftypes.UUID) (*core.FFI, error) GetFFIByIDWithChildren(ctx context.Context, id *fftypes.UUID) (*core.FFI, error) GetFFIs(ctx context.Context, filter database.AndFilter) ([]*core.FFI, *database.FilterResult, error) + ResolveFFI(ctx context.Context, ffi *core.FFI) error InvokeContract(ctx context.Context, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error) InvokeContractAPI(ctx context.Context, apiName, methodPath string, req *core.ContractCallRequest, waitConfirm bool) (interface{}, error) GetContractAPI(ctx context.Context, httpServerURL, apiName string) (*core.ContractAPI, error) GetContractAPIInterface(ctx context.Context, apiName string) (*core.FFI, error) GetContractAPIs(ctx context.Context, httpServerURL string, filter database.AndFilter) ([]*core.ContractAPI, *database.FilterResult, error) - BroadcastContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) (output *core.ContractAPI, err error) - - ValidateFFIAndSetPathnames(ctx context.Context, ffi *core.FFI) error + ResolveContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI) error AddContractListener(ctx context.Context, listener *core.ContractListenerInput) (output *core.ContractListener, err error) AddContractAPIListener(ctx context.Context, apiName, eventPath string, listener *core.ContractListener) (output *core.ContractListener, err error) @@ -71,7 +68,6 @@ type contractManager struct { namespace string database database.Plugin txHelper txcommon.Helper - broadcast broadcast.Manager identity identity.Manager blockchain blockchain.Plugin ffiParamValidator core.FFIParamValidator @@ -79,8 +75,8 @@ type contractManager struct { syncasync syncasync.Bridge } -func NewContractManager(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, bm broadcast.Manager, im identity.Manager, om operations.Manager, txHelper txcommon.Helper, sa syncasync.Bridge) (Manager, error) { - if di == nil || bm == nil || im == nil || bi == nil || om == nil || txHelper == nil || sa == nil { +func NewContractManager(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, im identity.Manager, om operations.Manager, txHelper txcommon.Helper, sa syncasync.Bridge) (Manager, error) { + if di == nil || im == nil || bi == nil || om == nil || txHelper == nil || sa == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "ContractManager") } v, err := bi.GetFFIParamValidator(ctx) @@ -92,7 +88,6 @@ func NewContractManager(ctx context.Context, ns string, di database.Plugin, bi b namespace: ns, database: di, txHelper: txHelper, - broadcast: bm, identity: im, blockchain: bi, ffiParamValidator: v, @@ -119,34 +114,6 @@ func (cm *contractManager) newFFISchemaCompiler() *jsonschema.Compiler { return c } -func (cm *contractManager) BroadcastFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) (output *core.FFI, err error) { - ffi.ID = fftypes.NewUUID() - ffi.Namespace = cm.namespace - - existing, err := cm.database.GetFFI(ctx, cm.namespace, ffi.Name, ffi.Version) - if existing != nil && err == nil { - return nil, i18n.NewError(ctx, coremsgs.MsgContractInterfaceExists, ffi.Namespace, ffi.Name, ffi.Version) - } - - for _, method := range ffi.Methods { - method.ID = fftypes.NewUUID() - } - for _, event := range ffi.Events { - event.ID = fftypes.NewUUID() - } - if err := cm.ValidateFFIAndSetPathnames(ctx, ffi); err != nil { - return nil, err - } - - output = ffi - msg, err := cm.broadcast.BroadcastDefinitionAsNode(ctx, ffi, core.SystemTagDefineFFI, waitConfirm) - if err != nil { - return nil, err - } - output.Message = msg.Header.ID - return ffi, nil -} - func (cm *contractManager) GetFFI(ctx context.Context, name, version string) (*core.FFI, error) { return cm.database.GetFFI(ctx, cm.namespace, name, version) } @@ -317,6 +284,36 @@ func (cm *contractManager) GetContractAPIs(ctx context.Context, httpServerURL st return apis, fr, err } +func (cm *contractManager) ResolveContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI) (err error) { + if api.Location != nil { + if api.Location, err = cm.blockchain.NormalizeContractLocation(ctx, api.Location); err != nil { + return err + } + } + + err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { + existing, err := cm.database.GetContractAPIByName(ctx, api.Namespace, api.Name) + if existing != nil && err == nil { + if !api.LocationAndLedgerEquals(existing) { + return i18n.NewError(ctx, coremsgs.MsgContractLocationExists) + } + } + + if err := cm.resolveFFIReference(ctx, api.Interface); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + + if httpServerURL != "" { + cm.addContractURLs(httpServerURL, api) + } + return nil +} + func (cm *contractManager) resolveFFIReference(ctx context.Context, ref *core.FFIReference) error { switch { case ref == nil: @@ -346,42 +343,6 @@ func (cm *contractManager) resolveFFIReference(ctx context.Context, ref *core.FF } } -func (cm *contractManager) BroadcastContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) (output *core.ContractAPI, err error) { - api.ID = fftypes.NewUUID() - api.Namespace = cm.namespace - - if api.Location != nil { - if api.Location, err = cm.blockchain.NormalizeContractLocation(ctx, api.Location); err != nil { - return nil, err - } - } - - err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { - existing, err := cm.database.GetContractAPIByName(ctx, api.Namespace, api.Name) - if existing != nil && err == nil { - if !api.LocationAndLedgerEquals(existing) { - return i18n.NewError(ctx, coremsgs.MsgContractLocationExists) - } - } - - if err := cm.resolveFFIReference(ctx, api.Interface); err != nil { - return err - } - return nil - }) - if err != nil { - return nil, err - } - - msg, err := cm.broadcast.BroadcastDefinitionAsNode(ctx, api, core.SystemTagDefineContractAPI, waitConfirm) - if err != nil { - return nil, err - } - api.Message = msg.Header.ID - cm.addContractURLs(httpServerURL, api) - return api, nil -} - func (cm *contractManager) uniquePathName(name string, usedNames map[string]bool) string { pathName := name for counter := 1; ; counter++ { @@ -394,11 +355,16 @@ func (cm *contractManager) uniquePathName(name string, usedNames map[string]bool } } -func (cm *contractManager) ValidateFFIAndSetPathnames(ctx context.Context, ffi *core.FFI) error { +func (cm *contractManager) ResolveFFI(ctx context.Context, ffi *core.FFI) error { if err := ffi.Validate(ctx, false); err != nil { 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 diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index 41b62c2108..86037f9316 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -27,7 +27,6 @@ import ( "github.com/hyperledger/firefly/internal/syncasync" "github.com/hyperledger/firefly/internal/txcommon" "github.com/hyperledger/firefly/mocks/blockchainmocks" - "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" @@ -44,7 +43,6 @@ import ( func newTestContractManager() *contractManager { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - mbm := &broadcastmocks.Manager{} mim := &identitymanagermocks.Manager{} mbi := &blockchainmocks.Plugin{} mom := &operationmocks.Manager{} @@ -61,13 +59,13 @@ func newTestContractManager() *contractManager { a[1].(func(context.Context) error)(a[0].(context.Context)), } } - cm, _ := NewContractManager(context.Background(), "ns1", mdi, mbi, mbm, mim, mom, txHelper, msa) + cm, _ := NewContractManager(context.Background(), "ns1", mdi, mbi, mim, mom, txHelper, msa) cm.(*contractManager).txHelper = &txcommonmocks.Helper{} return cm.(*contractManager) } func TestNewContractManagerFail(t *testing.T) { - _, err := NewContractManager(context.Background(), "", nil, nil, nil, nil, nil, nil, nil) + _, err := NewContractManager(context.Background(), "", nil, nil, nil, nil, nil, nil) assert.Regexp(t, "FF10128", err) } @@ -79,21 +77,19 @@ func TestName(t *testing.T) { func TestNewContractManagerFFISchemaLoaderFail(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - mbm := &broadcastmocks.Manager{} mim := &identitymanagermocks.Manager{} mbi := &blockchainmocks.Plugin{} mom := &operationmocks.Manager{} txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm) msa := &syncasyncmocks.Bridge{} mbi.On("GetFFIParamValidator", mock.Anything).Return(nil, fmt.Errorf("pop")) - _, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mbm, mim, mom, txHelper, msa) + _, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mim, mom, txHelper, msa) assert.Regexp(t, "pop", err) } func TestNewContractManagerFFISchemaLoader(t *testing.T) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} - mbm := &broadcastmocks.Manager{} mim := &identitymanagermocks.Manager{} mbi := &blockchainmocks.Plugin{} mom := &operationmocks.Manager{} @@ -101,29 +97,21 @@ func TestNewContractManagerFFISchemaLoader(t *testing.T) { msa := &syncasyncmocks.Bridge{} mbi.On("GetFFIParamValidator", mock.Anything).Return(ðereum.FFIParamValidator{}, nil) mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything) - _, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mbm, mim, mom, txHelper, msa) + _, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mim, mom, txHelper, msa) assert.NoError(t, err) } -func TestBroadcastFFI(t *testing.T) { +func TestResolveFFI(t *testing.T) { cm := newTestContractManager() mdb := cm.database.(*databasemocks.Plugin) - mim := cm.identity.(*identitymanagermocks.Manager) - mbm := cm.broadcast.(*broadcastmocks.Manager) mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(nil, nil) - mim.On("GetOrgKey", mock.Anything).Return("key", nil) - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.FFI"), core.SystemTagDefineFFI, false).Return(msg, nil) ffi := &core.FFI{ - Name: "test", - Version: "1.0.0", - ID: fftypes.NewUUID(), + Namespace: "ns1", + Name: "test", + Version: "1.0.0", + ID: fftypes.NewUUID(), Methods: []*core.FFIMethod{ { Name: "sum", @@ -137,27 +125,22 @@ func TestBroadcastFFI(t *testing.T) { }, }, } - _, err := cm.BroadcastFFI(context.Background(), ffi, false) + + err := cm.ResolveFFI(context.Background(), ffi) assert.NoError(t, err) } func TestBroadcastFFIInvalid(t *testing.T) { cm := newTestContractManager() mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(nil, nil) - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.FFI"), core.SystemTagDefineFFI, false).Return(msg, nil) ffi := &core.FFI{ - Name: "test", - Version: "1.0.0", - ID: fftypes.NewUUID(), + Namespace: "ns1", + Name: "test", + Version: "1.0.0", + ID: fftypes.NewUUID(), Methods: []*core.FFIMethod{ { Name: "sum", @@ -170,54 +153,40 @@ func TestBroadcastFFIInvalid(t *testing.T) { }, }, } - _, err := cm.BroadcastFFI(context.Background(), ffi, false) - assert.Regexp(t, "does not validate", err) -} - -func TestBroadcastFFIExists(t *testing.T) { - cm := newTestContractManager() - mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) - mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(&core.FFI{}, nil) + err := cm.ResolveFFI(context.Background(), ffi) + assert.Regexp(t, "does not validate", err) - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.FFI"), core.SystemTagDefineFFI, false).Return(msg, nil) - ffi := &core.FFI{ - Name: "test", - Version: "1.0.0", - ID: fftypes.NewUUID(), - } - _, err := cm.BroadcastFFI(context.Background(), ffi, false) - assert.Regexp(t, "FF10302", err) + mdb.AssertExpectations(t) } -func TestBroadcastFFIFail(t *testing.T) { +func TestResolveFFIExists(t *testing.T) { cm := newTestContractManager() mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) - mim := cm.identity.(*identitymanagermocks.Manager) - mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(nil, nil) - mim.On("GetOrgKey", mock.Anything).Return("key", nil) + mdb.On("GetFFI", mock.Anything, "ns1", "test", "1.0.0").Return(&core.FFI{}, nil) - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.FFI"), core.SystemTagDefineFFI, false).Return(nil, fmt.Errorf("pop")) ffi := &core.FFI{ - Name: "test", - Version: "1.0.0", - ID: fftypes.NewUUID(), + Namespace: "ns1", + Name: "test", + Version: "1.0.0", + ID: fftypes.NewUUID(), Methods: []*core.FFIMethod{ { Name: "sum", }, }, + Events: []*core.FFIEvent{ + { + FFIEventDefinition: core.FFIEventDefinition{ + Name: "changed", + }, + }, + }, } - _, err := cm.BroadcastFFI(context.Background(), ffi, false) - assert.Regexp(t, "pop", err) + + err := cm.ResolveFFI(context.Background(), ffi) + assert.Regexp(t, "FF10302", err) } func TestValidateInvokeContractRequest(t *testing.T) { @@ -350,6 +319,8 @@ func TestValidateInvokeContractRequestInvalidParam(t *testing.T) { func TestValidateFFI(t *testing.T) { cm := newTestContractManager() + mdi := cm.database.(*databasemocks.Plugin) + ffi := &core.FFI{ Name: "math", Version: "1.0.0", @@ -403,7 +374,9 @@ func TestValidateFFI(t *testing.T) { }, } - err := cm.ValidateFFIAndSetPathnames(context.Background(), ffi) + mdi.On("GetFFI", context.Background(), "ns1", "math", "1.0.0").Return(nil, nil) + + err := cm.ResolveFFI(context.Background(), ffi) assert.NoError(t, err) assert.Equal(t, "sum", ffi.Methods[0].Pathname) @@ -414,6 +387,8 @@ func TestValidateFFI(t *testing.T) { func TestValidateFFIFail(t *testing.T) { cm := newTestContractManager() + mdi := cm.database.(*databasemocks.Plugin) + ffi := &core.FFI{ Namespace: "default", Methods: []*core.FFIMethod{ @@ -465,12 +440,16 @@ func TestValidateFFIFail(t *testing.T) { }, } - err := cm.ValidateFFIAndSetPathnames(context.Background(), ffi) + mdi.On("GetFFI", context.Background(), "ns1", "math", "1.0.0").Return(nil, nil) + + err := cm.ResolveFFI(context.Background(), ffi) assert.Regexp(t, "FF00140", err) } func TestValidateFFIBadMethod(t *testing.T) { cm := newTestContractManager() + mdi := cm.database.(*databasemocks.Plugin) + ffi := &core.FFI{ Name: "math", Version: "1.0.0", @@ -511,12 +490,16 @@ func TestValidateFFIBadMethod(t *testing.T) { }, } - err := cm.ValidateFFIAndSetPathnames(context.Background(), ffi) + mdi.On("GetFFI", context.Background(), "ns1", "math", "1.0.0").Return(nil, nil) + + err := cm.ResolveFFI(context.Background(), ffi) assert.Regexp(t, "FF10320", err) } func TestValidateFFIBadEventParam(t *testing.T) { cm := newTestContractManager() + mdi := cm.database.(*databasemocks.Plugin) + ffi := &core.FFI{ Name: "math", Version: "1.0.0", @@ -557,7 +540,9 @@ func TestValidateFFIBadEventParam(t *testing.T) { }, } - err := cm.ValidateFFIAndSetPathnames(context.Background(), ffi) + mdi.On("GetFFI", context.Background(), "ns1", "math", "1.0.0").Return(nil, nil) + + err := cm.ResolveFFI(context.Background(), ffi) assert.Regexp(t, "FF10319", err) } @@ -1922,17 +1907,11 @@ func TestGetContractAPIInterfaceFail(t *testing.T) { mdb.AssertExpectations(t) } -func TestBroadcastContractAPI(t *testing.T) { +func TestResolveContractAPI(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } api := &core.ContractAPI{ ID: fftypes.NewUUID(), Namespace: "ns1", @@ -1946,21 +1925,15 @@ func TestBroadcastContractAPI(t *testing.T) { mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(&core.FFI{}, nil) - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.ContractAPI"), core.SystemTagDefineContractAPI, false).Return(msg, nil) - - api, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.NoError(t, err) - assert.NotNil(t, api) - assert.NotEmpty(t, api.URLs.OpenAPI) - assert.NotEmpty(t, api.URLs.UI) mbi.AssertExpectations(t) mdb.AssertExpectations(t) - mbm.AssertExpectations(t) } -func TestBroadcastContractAPIBadLocation(t *testing.T) { +func TestResolveContractAPIBadLocation(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) @@ -1974,61 +1947,15 @@ func TestBroadcastContractAPIBadLocation(t *testing.T) { }, } - mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(nil, fmt.Errorf("pop")) - - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) + mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, fmt.Errorf("pop")) + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.EqualError(t, err, "pop") mbi.AssertExpectations(t) } -func TestBroadcastContractAPIExisting(t *testing.T) { - cm := newTestContractManager() - mbi := cm.blockchain.(*blockchainmocks.Plugin) - mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) - - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } - apiID := fftypes.NewUUID() - existing := &core.ContractAPI{ - ID: apiID, - Namespace: "ns1", - Location: fftypes.JSONAnyPtr(""), - Name: "banana", - Interface: &core.FFIReference{ - ID: fftypes.NewUUID(), - }, - } - api := &core.ContractAPI{ - ID: apiID, - Namespace: "ns1", - Location: fftypes.JSONAnyPtr(""), - Name: "banana", - Interface: &core.FFIReference{ - ID: fftypes.NewUUID(), - }, - } - - mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) - mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(existing, nil) - mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(&core.FFI{}, nil) - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.ContractAPI"), core.SystemTagDefineContractAPI, false).Return(msg, nil) - - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - - assert.NoError(t, err) - - mbi.AssertExpectations(t) - mdb.AssertExpectations(t) - mbm.AssertExpectations(t) -} - -func TestBroadcastContractAPICannotChangeLocation(t *testing.T) { +func TestResolveContractAPICannotChangeLocation(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2056,25 +1983,18 @@ func TestBroadcastContractAPICannotChangeLocation(t *testing.T) { mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(existing, nil) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.Regexp(t, "FF10316", err) mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceName(t *testing.T) { +func TestResolveContractAPIInterfaceName(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) - msg := &core.Message{ - Header: core.MessageHeader{ - ID: fftypes.NewUUID(), - }, - } api := &core.ContractAPI{ ID: fftypes.NewUUID(), Namespace: "ns1", @@ -2090,72 +2010,37 @@ func TestBroadcastContractAPIInterfaceName(t *testing.T) { mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFI", mock.Anything, "ns1", "my-ffi", "1").Return(&core.FFI{ID: interfaceID}, nil) - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.ContractAPI"), core.SystemTagDefineContractAPI, false).Return(msg, nil) - - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.NoError(t, err) - assert.Equal(t, *interfaceID, *api.Interface.ID) mbi.AssertExpectations(t) mdb.AssertExpectations(t) - mbm.AssertExpectations(t) } -func TestBroadcastContractAPIFail(t *testing.T) { +func TestResolveContractAPINoInterface(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) - mbm := cm.broadcast.(*broadcastmocks.Manager) api := &core.ContractAPI{ ID: fftypes.NewUUID(), Namespace: "ns1", Location: fftypes.JSONAnyPtr(""), Name: "banana", - Interface: &core.FFIReference{ - ID: fftypes.NewUUID(), - }, } mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) - mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(&core.FFI{}, nil) - mbm.On("BroadcastDefinitionAsNode", mock.Anything, mock.AnythingOfType("*core.ContractAPI"), core.SystemTagDefineContractAPI, false).Return(nil, fmt.Errorf("pop")) - - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - - assert.Regexp(t, "pop", err) - - mbi.AssertExpectations(t) - mdb.AssertExpectations(t) - mbm.AssertExpectations(t) -} - -func TestBroadcastContractAPINoInterface(t *testing.T) { - cm := newTestContractManager() - mbi := cm.blockchain.(*blockchainmocks.Plugin) - mdb := cm.database.(*databasemocks.Plugin) - - api := &core.ContractAPI{ - ID: fftypes.NewUUID(), - Namespace: "ns1", - Location: fftypes.JSONAnyPtr(""), - Name: "banana", - } - - mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) - mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) - - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.Regexp(t, "FF10303", err) mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceIDFail(t *testing.T) { +func TestResolveContractAPIInterfaceIDFail(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2174,15 +2059,14 @@ func TestBroadcastContractAPIInterfaceIDFail(t *testing.T) { mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(nil, fmt.Errorf("pop")) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.EqualError(t, err, "pop") mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceIDNotFound(t *testing.T) { +func TestResolveContractAPIInterfaceIDNotFound(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2201,15 +2085,14 @@ func TestBroadcastContractAPIInterfaceIDNotFound(t *testing.T) { mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFIByID", mock.Anything, "ns1", api.Interface.ID).Return(nil, nil) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.Regexp(t, "FF10303.*"+api.Interface.ID.String(), err) mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceNameFail(t *testing.T) { +func TestResolveContractAPIInterfaceNameFail(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2229,15 +2112,14 @@ func TestBroadcastContractAPIInterfaceNameFail(t *testing.T) { mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFI", mock.Anything, "ns1", "my-ffi", "1").Return(nil, fmt.Errorf("pop")) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.EqualError(t, err, "pop") mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceNameNotFound(t *testing.T) { +func TestResolveContractAPIInterfaceNameNotFound(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2257,15 +2139,14 @@ func TestBroadcastContractAPIInterfaceNameNotFound(t *testing.T) { mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) mdb.On("GetFFI", mock.Anything, "ns1", "my-ffi", "1").Return(nil, nil) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.Regexp(t, "FF10303.*my-ffi", err) mbi.AssertExpectations(t) mdb.AssertExpectations(t) } -func TestBroadcastContractAPIInterfaceNoVersion(t *testing.T) { +func TestResolveContractAPIInterfaceNoVersion(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin) mdb := cm.database.(*databasemocks.Plugin) @@ -2283,8 +2164,7 @@ func TestBroadcastContractAPIInterfaceNoVersion(t *testing.T) { mbi.On("NormalizeContractLocation", context.Background(), api.Location).Return(api.Location, nil) mdb.On("GetContractAPIByName", mock.Anything, api.Namespace, api.Name).Return(nil, nil) - _, err := cm.BroadcastContractAPI(context.Background(), "http://localhost/api", api, false) - + err := cm.ResolveContractAPI(context.Background(), "http://localhost/api", api) assert.Regexp(t, "FF10303.*my-ffi", err) mbi.AssertExpectations(t) @@ -2324,7 +2204,6 @@ func TestCheckParamSchemaCompileFail(t *testing.T) { func TestAddJSONSchemaExtension(t *testing.T) { cm := &contractManager{ database: &databasemocks.Plugin{}, - broadcast: &broadcastmocks.Manager{}, identity: &identitymanagermocks.Manager{}, blockchain: &blockchainmocks.Plugin{}, ffiParamValidator: &MockFFIParamValidator{}, diff --git a/internal/coreconfig/coreconfig.go b/internal/coreconfig/coreconfig.go index 2d78ac2d9a..7b30281103 100644 --- a/internal/coreconfig/coreconfig.go +++ b/internal/coreconfig/coreconfig.go @@ -34,11 +34,11 @@ const ( // NamespaceName is the long description for a pre-defined namespace NamespaceDescription = "description" // NamespaceRemoteName is the namespace name to be sent in plugin calls - NamespaceRemoteName = "remoteName" + NamespaceRemoteName = "remotename" // NamespacePlugins is the list of namespace plugins NamespacePlugins = "plugins" // NamespaceDefaultKey is the default signing key for blockchain transactions within this namespace - NamespaceDefaultKey = "defaultKey" + NamespaceDefaultKey = "defaultkey" // NamespaceMultiparty contains the multiparty configuration for a namespace NamespaceMultiparty = "multiparty" // NamespaceMultipartyEnabled specifies if multi-party mode is enabled for a namespace diff --git a/internal/coremsgs/en_config_descriptions.go b/internal/coremsgs/en_config_descriptions.go index 784862a183..49b7c04349 100644 --- a/internal/coremsgs/en_config_descriptions.go +++ b/internal/coremsgs/en_config_descriptions.go @@ -252,8 +252,8 @@ var ( ConfigNamespacesPredefinedName = ffc("config.namespaces.predefined[].name", "The name of the namespace (must be unique)", i18n.StringType) ConfigNamespacesPredefinedDescription = ffc("config.namespaces.predefined[].description", "A description for the namespace", i18n.StringType) ConfigNamespacesPredefinedPlugins = ffc("config.namespaces.predefined[].plugins", "The list of plugins for this namespace", i18n.StringType) - ConfigNamespacesPredefinedRemoteName = ffc("config.namespaces.predefined[].remoteName", "The namespace name to be sent in plugin calls, if it differs from namespace name", i18n.StringType) - ConfigNamespacesPredefinedDefaultKey = ffc("config.namespaces.predefined[].defaultKey", "A default signing key for blockchain transactions within this namespace", i18n.StringType) + ConfigNamespacesPredefinedRemoteName = ffc("config.namespaces.predefined[].remotename", "The namespace name to be sent in plugin calls, if it differs from namespace name", i18n.StringType) + ConfigNamespacesPredefinedDefaultKey = ffc("config.namespaces.predefined[].defaultkey", "A default signing key for blockchain transactions within this namespace", i18n.StringType) ConfigNamespacesMultipartyEnabled = ffc("config.namespaces.predefined[].multiparty.enabled", "Enables multi-party mode for this namespace (defaults to true if an org name or key is configured, either here or at the root level)", i18n.BooleanType) ConfigNamespacesMultipartyOrgName = ffc("config.namespaces.predefined[].multiparty.org.name", "A short name for the local root organization within this namespace", i18n.StringType) ConfigNamespacesMultipartyOrgDesc = ffc("config.namespaces.predefined[].multiparty.org.description", "A description for the local root organization within this namespace", i18n.StringType) diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go index 64dfd5840a..313846a16a 100644 --- a/internal/coremsgs/en_error_messages.go +++ b/internal/coremsgs/en_error_messages.go @@ -230,10 +230,10 @@ var ( MsgFFSystemReservedName = ffe("FF10388", "Invalid namespace configuration - %s is a reserved name") MsgInvalidNamespaceMode = ffe("FF10389", "Invalid %s namespace configuration - unknown mode") MsgNamespaceUnknownPlugin = ffe("FF10390", "Invalid %s namespace configuration - unknown plugin %s") - MsgNamespaceMultipartyConfiguration = ffe("FF10391", "Invalid %s multiparty namespace configuration - database, blockchain, shared storage, and data exchange plugins are required") - MsgNamespaceGatewayNoDB = ffe("FF10392", "Invalid %s gateway namespace configuration - a database plugin is required") - MsgNamespaceGatewayInvalidPlugins = ffe("FF10393", "Invalid %s gateway namespace configuration - cannot specify dataexchange or shared storage plugins") - MsgNamespaceGatewayMultiplePluginType = ffe("FF10394", "Invalid %s namespace configuration - multiple %s plugins provided") + MsgNamespaceWrongPluginsMultiparty = ffe("FF10391", "Invalid %s namespace configuration - multiparty mode requires database, blockchain, shared storage, and data exchange plugins") + MsgNamespaceNoDatabase = ffe("FF10392", "Invalid %s namespace configuration - a database plugin is required") + MsgNamespaceWrongPluginsNonMultiparty = ffe("FF10393", "Invalid %s namespace configuration - cannot specify data exchange or shared storage plugins when multiparty mode is disabled") + MsgNamespaceMultiplePluginType = ffe("FF10394", "Invalid %s namespace configuration - multiple %s plugins provided") MsgDuplicatePluginName = ffe("FF10395", "Invalid plugin configuration - plugin with name %s already exists") MsgInvalidFireFlyContractIndex = ffe("FF10396", "No configuration found for FireFly contract at %s") MsgUnrecognizedNetworkAction = ffe("FF10397", "Unrecognized network action: %s", 400) @@ -252,4 +252,7 @@ var ( MsgDefRejectedHashMismatch = ffe("FF10410", "Rejected %s '%s' - hash mismatch: %s != %s") MsgInvalidNamespaceUUID = ffe("FF10411", "Expected 'namespace:' prefix on ID '%s'", 400) MsgSubscriptionIDInvalid = ffe("FF10412", "Invalid subscription ID: %s") + MsgDefinitionRejected = ffe("FF10413", "Definition rejected") + MsgActionNotSupported = ffe("FF10414", "This action is not supported in this namespace", 400) + MsgMessagesNotSupported = ffe("FF10415", "Messages are not supported in this namespace", 400) ) diff --git a/internal/data/blobstore.go b/internal/data/blobstore.go index f541de4573..e4e5f64844 100644 --- a/internal/data/blobstore.go +++ b/internal/data/blobstore.go @@ -31,14 +31,12 @@ import ( "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" - "github.com/hyperledger/firefly/pkg/sharedstorage" ) type blobStore struct { - dm *dataManager - sharedstorage sharedstorage.Plugin - database database.Plugin - exchange dataexchange.Plugin + dm *dataManager + database database.Plugin + exchange dataexchange.Plugin // optional } func (bs *blobStore) uploadVerifyBlob(ctx context.Context, id *fftypes.UUID, reader io.Reader) (hash *fftypes.Bytes32, written int64, payloadRef string, err error) { @@ -81,6 +79,10 @@ func (bs *blobStore) uploadVerifyBlob(ctx context.Context, id *fftypes.UUID, rea func (bs *blobStore) UploadBlob(ctx context.Context, inData *core.DataRefOrValue, mpart *ffapi.Multipart, autoMeta bool) (*core.Data, error) { + if bs.exchange == nil { + return nil, i18n.NewError(ctx, coremsgs.MsgActionNotSupported) + } + data := &core.Data{ ID: fftypes.NewUUID(), Namespace: bs.dm.namespace, @@ -144,6 +146,10 @@ func (bs *blobStore) UploadBlob(ctx context.Context, inData *core.DataRefOrValue func (bs *blobStore) DownloadBlob(ctx context.Context, dataID string) (*core.Blob, io.ReadCloser, error) { + if bs.exchange == nil { + return nil, nil, i18n.NewError(ctx, coremsgs.MsgActionNotSupported) + } + id, err := fftypes.ParseUUID(ctx, dataID) if err != nil { return nil, nil, err diff --git a/internal/data/blobstore_test.go b/internal/data/blobstore_test.go index c6246f27f4..fff10348ee 100644 --- a/internal/data/blobstore_test.go +++ b/internal/data/blobstore_test.go @@ -84,6 +84,18 @@ func TestUploadBlobOk(t *testing.T) { } +func TestUploadBlobDisabled(t *testing.T) { + + dm, ctx, cancel := newTestDataManager(t) + defer cancel() + dm.exchange = nil + + b := make([]byte, 10) + _, err := dm.UploadBlob(ctx, &core.DataRefOrValue{}, &ffapi.Multipart{Data: bytes.NewReader(b)}, false) + assert.Regexp(t, "FF10414", err) + +} + func TestUploadBlobAutoMetaOk(t *testing.T) { dm, ctx, cancel := newTestDataManager(t) @@ -283,6 +295,17 @@ func TestDownloadBlobOk(t *testing.T) { } +func TestDownloadBlobDisabled(t *testing.T) { + + dm, ctx, cancel := newTestDataManager(t) + defer cancel() + dm.exchange = nil + + _, _, err := dm.DownloadBlob(ctx, "") + assert.Regexp(t, "FF10414", err) + +} + func TestDownloadBlobNotFound(t *testing.T) { dm, ctx, cancel := newTestDataManager(t) diff --git a/internal/data/data_manager.go b/internal/data/data_manager.go index f432a85571..398c3260b7 100644 --- a/internal/data/data_manager.go +++ b/internal/data/data_manager.go @@ -32,7 +32,6 @@ import ( "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" "github.com/hyperledger/firefly/pkg/dataexchange" - "github.com/hyperledger/firefly/pkg/sharedstorage" "github.com/karlseguin/ccache" ) @@ -59,7 +58,6 @@ type dataManager struct { blobStore namespace string database database.Plugin - exchange dataexchange.Plugin validatorCache *ccache.Cache validatorCacheTTL time.Duration messageCache *ccache.Cache @@ -95,22 +93,20 @@ const ( CRORequireBatchID ) -func NewDataManager(ctx context.Context, ns string, di database.Plugin, pi sharedstorage.Plugin, dx dataexchange.Plugin) (Manager, error) { - if di == nil || pi == nil || dx == nil { +func NewDataManager(ctx context.Context, ns string, di database.Plugin, dx dataexchange.Plugin) (Manager, error) { + if di == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "DataManager") } dm := &dataManager{ namespace: ns, database: di, - exchange: dx, validatorCacheTTL: config.GetDuration(coreconfig.ValidatorCacheTTL), messageCacheTTL: config.GetDuration(coreconfig.MessageCacheTTL), } dm.blobStore = blobStore{ - dm: dm, - database: di, - sharedstorage: pi, - exchange: dx, + dm: dm, + database: di, + exchange: dx, } dm.validatorCache = ccache.New( // We use a LRU cache with a size-aware max diff --git a/internal/data/data_manager_test.go b/internal/data/data_manager_test.go index 78a6ca2c3e..7870cd85a8 100644 --- a/internal/data/data_manager_test.go +++ b/internal/data/data_manager_test.go @@ -26,7 +26,6 @@ import ( "github.com/hyperledger/firefly/internal/coreconfig" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" - "github.com/hyperledger/firefly/mocks/sharedstoragemocks" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" "github.com/stretchr/testify/assert" @@ -42,8 +41,7 @@ func newTestDataManager(t *testing.T) (*dataManager, context.Context, func()) { Concurrency: true, }) mdx := &dataexchangemocks.Plugin{} - mps := &sharedstoragemocks.Plugin{} - dm, err := NewDataManager(ctx, "ns1", mdi, mps, mdx) + dm, err := NewDataManager(ctx, "ns1", mdi, mdx) assert.NoError(t, err) return dm.(*dataManager), ctx, func() { cancel() @@ -213,7 +211,7 @@ func TestWriteNewMessageE2E(t *testing.T) { } func TestInitBadDeps(t *testing.T) { - _, err := NewDataManager(context.Background(), "", nil, nil, nil) + _, err := NewDataManager(context.Background(), "", nil, nil) assert.Regexp(t, "FF10128", err) } diff --git a/internal/dataexchange/dxfactory/factory.go b/internal/dataexchange/dxfactory/factory.go index 13aaff6582..8e995f4f76 100644 --- a/internal/dataexchange/dxfactory/factory.go +++ b/internal/dataexchange/dxfactory/factory.go @@ -44,7 +44,7 @@ func InitConfig(config config.ArraySection) { } func InitConfigDeprecated(config config.Section) { - config.AddKnownKey(coreconfig.PluginConfigType, NewFFDXPluginName) + config.AddKnownKey(coreconfig.PluginConfigType) for name, plugin := range pluginsByName { plugin().InitConfig(config.SubSection(name)) } diff --git a/internal/definitions/definition_handler.go b/internal/definitions/handler.go similarity index 68% rename from internal/definitions/definition_handler.go rename to internal/definitions/handler.go index f5ff854e2d..6c2f150bb1 100644 --- a/internal/definitions/definition_handler.go +++ b/internal/definitions/handler.go @@ -35,8 +35,8 @@ import ( "github.com/hyperledger/firefly/pkg/dataexchange" ) -type DefinitionHandler interface { - HandleDefinitionBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) +type Handler interface { + HandleDefinitionBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) } type HandlerResult struct { @@ -76,41 +76,25 @@ func (dma DefinitionMessageAction) String() string { } } -// DefinitionBatchState tracks the state between definition handlers that run in-line on the pin processing route in the -// aggregator as part of a batch of pins. They might have complex API calls, and interdependencies, that need to be managed via this state. -// The actions to be taken at the end of a definition batch. -// See further notes on "batchState" in the event aggregator -type DefinitionBatchState interface { - // PreFinalize may perform a blocking action (possibly to an external connector) that should execute outside database RunAsGroup - AddPreFinalize(func(ctx context.Context) error) - - // Finalize may perform final, non-idempotent database operations (such as inserting Events) - AddFinalize(func(ctx context.Context) error) - - // GetPendingConfirm returns a map of messages are that pending confirmation after already being processed in this batch - GetPendingConfirm() map[fftypes.UUID]*core.Message - - // Notify of a DID claim locking in, so a rewind gets queued for it to go back and process any dependent child identities/messages - DIDClaimConfirmed(did string) -} - -type definitionHandlers struct { +type definitionHandler struct { namespace string + multiparty bool database database.Plugin blockchain blockchain.Plugin - exchange dataexchange.Plugin + exchange dataexchange.Plugin // optional data data.Manager identity identity.Manager assets assets.Manager contracts contracts.Manager } -func NewDefinitionHandler(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, dx dataexchange.Plugin, dm data.Manager, im identity.Manager, am assets.Manager, cm contracts.Manager) (DefinitionHandler, error) { - if di == nil || bi == nil || dx == nil || dm == nil || im == nil || am == nil || cm == nil { +func newDefinitionHandler(ctx context.Context, ns string, multiparty bool, di database.Plugin, bi blockchain.Plugin, dx dataexchange.Plugin, dm data.Manager, im identity.Manager, am assets.Manager, cm contracts.Manager) (*definitionHandler, error) { + if di == nil || bi == nil || dm == nil || im == nil || am == nil || cm == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "DefinitionHandler") } - return &definitionHandlers{ + return &definitionHandler{ namespace: ns, + multiparty: multiparty, database: di, blockchain: bi, exchange: dx, @@ -121,7 +105,7 @@ func NewDefinitionHandler(ctx context.Context, ns string, di database.Plugin, bi }, nil } -func (dh *definitionHandlers) HandleDefinitionBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (msgAction HandlerResult, err error) { +func (dh *definitionHandler) HandleDefinitionBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (msgAction HandlerResult, err error) { l := log.L(ctx) l.Infof("Processing system definition '%s' [%s]", msg.Header.Tag, msg.Header.ID) switch msg.Header.Tag { @@ -150,7 +134,7 @@ func (dh *definitionHandlers) HandleDefinitionBroadcast(ctx context.Context, sta } } -func (dh *definitionHandlers) getSystemBroadcastPayload(ctx context.Context, msg *core.Message, data core.DataArray, res core.Definition) (valid bool) { +func (dh *definitionHandler) getSystemBroadcastPayload(ctx context.Context, msg *core.Message, data core.DataArray, res core.Definition) (valid bool) { l := log.L(ctx) if len(data) != 1 { l.Warnf("Unable to process system definition %s - expecting 1 attachment, found %d", msg.Header.ID, len(data)) diff --git a/internal/definitions/definition_handler_contracts.go b/internal/definitions/handler_contracts.go similarity index 60% rename from internal/definitions/definition_handler_contracts.go rename to internal/definitions/handler_contracts.go index f6bf550c26..d00de04838 100644 --- a/internal/definitions/definition_handler_contracts.go +++ b/internal/definitions/handler_contracts.go @@ -27,44 +27,35 @@ import ( "github.com/hyperledger/firefly/pkg/database" ) -func (dh *definitionHandlers) persistFFI(ctx context.Context, ffi *core.FFI) (err error) { - if err = dh.contracts.ValidateFFIAndSetPathnames(ctx, ffi); err != nil { - log.L(ctx).Warnf("Unable to process FFI %s - validate failed: %s", ffi.ID, err) - return nil +func (dh *definitionHandler) persistFFI(ctx context.Context, ffi *core.FFI) (retry bool, err error) { + if err = dh.contracts.ResolveFFI(ctx, ffi); err != nil { + return false, i18n.NewError(ctx, coremsgs.MsgDefRejectedValidateFail, "contract interface", ffi.ID, err) } err = dh.database.UpsertFFI(ctx, ffi) if err != nil { - return err + return true, err } for _, method := range ffi.Methods { - err := dh.database.UpsertFFIMethod(ctx, method) - if err != nil { - return err + if err := dh.database.UpsertFFIMethod(ctx, method); err != nil { + return true, err } } - for _, event := range ffi.Events { - err := dh.database.UpsertFFIEvent(ctx, event) - if err != nil { - return err + if err := dh.database.UpsertFFIEvent(ctx, event); err != nil { + return true, err } } - return nil + return false, nil } -func (dh *definitionHandlers) persistContractAPI(ctx context.Context, api *core.ContractAPI) (retry bool, err error) { - existing, err := dh.database.GetContractAPIByName(ctx, api.Namespace, api.Name) - if err != nil { - return true, err - } - if existing != nil { - if !api.LocationAndLedgerEquals(existing) { - return false, i18n.NewError(ctx, coremsgs.MsgDefRejectedLocationMismatch, "contract API", api.ID) - } +func (dh *definitionHandler) persistContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI) (retry bool, err error) { + if err := dh.contracts.ResolveContractAPI(ctx, httpServerURL, api); err != nil { + return false, i18n.NewError(ctx, coremsgs.MsgDefRejectedValidateFail, "contract API", api.ID, err) } + err = dh.database.UpsertContractAPI(ctx, api) if err != nil { if err == database.IDMismatch { @@ -75,8 +66,7 @@ func (dh *definitionHandlers) persistContractAPI(ctx context.Context, api *core. return false, nil } -func (dh *definitionHandlers) handleFFIBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { - l := log.L(ctx) +func (dh *definitionHandler) handleFFIBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { var ffi core.FFI if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &ffi); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "contract interface", msg.Header.ID) @@ -86,11 +76,19 @@ func (dh *definitionHandlers) handleFFIBroadcast(ctx context.Context, state Defi } ffi.Message = msg.Header.ID - if err := dh.persistFFI(ctx, &ffi); err != nil { - return HandlerResult{Action: ActionRetry}, err + return dh.handleFFIDefinition(ctx, state, &ffi, tx) +} + +func (dh *definitionHandler) handleFFIDefinition(ctx context.Context, state *core.BatchState, ffi *core.FFI, tx *fftypes.UUID) (HandlerResult, error) { + l := log.L(ctx) + if retry, err := dh.persistFFI(ctx, ffi); err != nil { + if retry { + return HandlerResult{Action: ActionRetry}, err + } + return HandlerResult{Action: ActionReject}, err } - l.Infof("Contract interface created id=%s author=%s", ffi.ID, msg.Header.Author) + l.Infof("Contract interface created id=%s", ffi.ID) state.AddFinalize(func(ctx context.Context) error { event := core.NewEvent(core.EventTypeContractInterfaceConfirmed, ffi.Namespace, ffi.ID, tx, ffi.Topic()) return dh.database.InsertEvent(ctx, event) @@ -98,8 +96,7 @@ func (dh *definitionHandlers) handleFFIBroadcast(ctx context.Context, state Defi return HandlerResult{Action: ActionConfirm}, nil } -func (dh *definitionHandlers) handleContractAPIBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { - l := log.L(ctx) +func (dh *definitionHandler) handleContractAPIBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { var api core.ContractAPI if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &api); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "contract API", msg.Header.ID) @@ -109,14 +106,19 @@ func (dh *definitionHandlers) handleContractAPIBroadcast(ctx context.Context, st } api.Message = msg.Header.ID - if retry, err := dh.persistContractAPI(ctx, &api); err != nil { + return dh.handleContractAPIDefinition(ctx, state, "", &api, tx) +} + +func (dh *definitionHandler) handleContractAPIDefinition(ctx context.Context, state *core.BatchState, httpServerURL string, api *core.ContractAPI, tx *fftypes.UUID) (HandlerResult, error) { + l := log.L(ctx) + if retry, err := dh.persistContractAPI(ctx, httpServerURL, api); err != nil { if retry { return HandlerResult{Action: ActionRetry}, err } return HandlerResult{Action: ActionReject}, err } - l.Infof("Contract API created id=%s author=%s", api.ID, msg.Header.Author) + l.Infof("Contract API created id=%s", api.ID) state.AddFinalize(func(ctx context.Context) error { event := core.NewEvent(core.EventTypeContractAPIConfirmed, api.Namespace, api.ID, tx, core.SystemTopicDefinitions) return dh.database.InsertEvent(ctx, event) diff --git a/internal/definitions/definition_handler_contracts_test.go b/internal/definitions/handler_contracts_test.go similarity index 64% rename from internal/definitions/definition_handler_contracts_test.go rename to internal/definitions/handler_contracts_test.go index cba38a7834..e72d655dd0 100644 --- a/internal/definitions/definition_handler_contracts_test.go +++ b/internal/definitions/handler_contracts_test.go @@ -96,42 +96,43 @@ func TestHandleFFIBroadcastOk(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) - mbi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(nil) - mbi.On("UpsertFFIEvent", mock.Anything, mock.Anything).Return(nil) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertFFIEvent", mock.Anything, mock.Anything).Return(nil) + mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(nil) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineFFI, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.NoError(t, err) - mbi.AssertExpectations(t) + mdi.AssertExpectations(t) } func TestPersistFFIValidateFFIFail(t *testing.T) { dh, _ := newTestDefinitionHandler(t) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - err := dh.persistFFI(context.Background(), testFFI()) - assert.NoError(t, err) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + retry, err := dh.persistFFI(context.Background(), testFFI()) + assert.Regexp(t, "FF10403", err) + assert.False(t, retry) mcm.AssertExpectations(t) } func TestHandleFFIBroadcastReject(t *testing.T) { dh, bs := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) + mdi := dh.database.(*databasemocks.Plugin) mcm := dh.contracts.(*contractmocks.Manager) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - action, err := dh.handleFFIBroadcast(context.Background(), bs, &core.Message{ + mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + action, err := dh.handleFFIBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineFFI, }, @@ -143,40 +144,43 @@ func TestHandleFFIBroadcastReject(t *testing.T) { func TestPersistFFIUpsertFFIFail(t *testing.T) { dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("UpsertFFI", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertFFI", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(nil) - err := dh.persistFFI(context.Background(), testFFI()) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(nil) + retry, err := dh.persistFFI(context.Background(), testFFI()) assert.Regexp(t, "pop", err) - mbi.AssertExpectations(t) + assert.True(t, retry) + mdi.AssertExpectations(t) mcm.AssertExpectations(t) } func TestPersistFFIUpsertFFIMethodFail(t *testing.T) { dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) - mbi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(nil) - err := dh.persistFFI(context.Background(), testFFI()) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(nil) + retry, err := dh.persistFFI(context.Background(), testFFI()) assert.Regexp(t, "pop", err) - mbi.AssertExpectations(t) + assert.True(t, retry) + mdi.AssertExpectations(t) mcm.AssertExpectations(t) } func TestPersistFFIUpsertFFIEventFail(t *testing.T) { dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) - mbi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(nil) - mbi.On("UpsertFFIEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertFFI", mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertFFIMethod", mock.Anything, mock.Anything).Return(nil) + mdi.On("UpsertFFIEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(nil) - err := dh.persistFFI(context.Background(), testFFI()) + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(nil) + retry, err := dh.persistFFI(context.Background(), testFFI()) assert.Regexp(t, "pop", err) - mbi.AssertExpectations(t) + assert.True(t, retry) + mdi.AssertExpectations(t) mcm.AssertExpectations(t) } @@ -189,9 +193,9 @@ func TestHandleFFIBroadcastValidateFail(t *testing.T) { data := &core.Data{ Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineFFI, }, @@ -213,8 +217,8 @@ func TestHandleFFIBroadcastPersistFail(t *testing.T) { mdi.On("UpsertFFI", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) mcm := dh.contracts.(*contractmocks.Manager) - mcm.On("ValidateFFIAndSetPathnames", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + mcm.On("ResolveFFI", mock.Anything, mock.Anything).Return(nil) + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineFFI, }, @@ -233,20 +237,24 @@ func TestHandleContractAPIBroadcastOk(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(nil) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), "", mock.Anything).Return(nil) + + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineContractAPI, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.NoError(t, err) - mbi.AssertExpectations(t) + + mdi.AssertExpectations(t) + mcm.AssertExpectations(t) } func TestHandleContractAPIBadPayload(t *testing.T) { @@ -255,7 +263,7 @@ func TestHandleContractAPIBadPayload(t *testing.T) { Value: fftypes.JSONAnyPtr("bad"), } - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineContractAPI, }, @@ -273,49 +281,35 @@ func TestHandleContractAPIIDMismatch(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - mbi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(database.IDMismatch) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(database.IDMismatch) + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), "", mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineContractAPI, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Regexp(t, "FF10404", err) - mbi.AssertExpectations(t) -} -func TestPersistContractAPIGetFail(t *testing.T) { - dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) - _, err := dh.persistContractAPI(context.Background(), testContractAPI()) - assert.Regexp(t, "pop", err) - mbi.AssertExpectations(t) -} - -func TestPersistContractAPIDifferentLocation(t *testing.T) { - existing := testContractAPI() - existing.Location = fftypes.JSONAnyPtr(`{"existing": true}`) - dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(existing, nil) - retry, err := dh.persistContractAPI(context.Background(), testContractAPI()) - assert.False(t, retry) - assert.Regexp(t, "FF10405", err) - mbi.AssertExpectations(t) + mdi.AssertExpectations(t) + mcm.AssertExpectations(t) } func TestPersistContractAPIUpsertFail(t *testing.T) { dh, _ := newTestDefinitionHandler(t) - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - mbi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - _, err := dh.persistContractAPI(context.Background(), testContractAPI()) + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), "http://test", mock.Anything).Return(nil) + + _, err := dh.persistContractAPI(context.Background(), "http://test", testContractAPI()) assert.Regexp(t, "pop", err) - mbi.AssertExpectations(t) + + mdi.AssertExpectations(t) + mcm.AssertExpectations(t) } func TestHandleContractAPIBroadcastValidateFail(t *testing.T) { @@ -327,9 +321,9 @@ func TestHandleContractAPIBroadcastValidateFail(t *testing.T) { data := &core.Data{ Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineContractAPI, }, @@ -347,16 +341,21 @@ func TestHandleContractAPIBroadcastPersistFail(t *testing.T) { data := &core.Data{ Value: fftypes.JSONAnyPtrBytes(b), } - mbi := dh.database.(*databasemocks.Plugin) - mbi.On("GetContractAPIByName", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - mbi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + mdi := dh.database.(*databasemocks.Plugin) + mdi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), "", mock.Anything).Return(nil) + + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineContractAPI, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) + bs.assertNoFinalizers() + + mdi.AssertExpectations(t) + mcm.AssertExpectations(t) } diff --git a/internal/definitions/definition_handler_datatype.go b/internal/definitions/handler_datatype.go similarity index 92% rename from internal/definitions/definition_handler_datatype.go rename to internal/definitions/handler_datatype.go index 0f6c79a00d..bb99dde0f4 100644 --- a/internal/definitions/definition_handler_datatype.go +++ b/internal/definitions/handler_datatype.go @@ -25,7 +25,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (dh *definitionHandlers) handleDatatypeBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { +func (dh *definitionHandler) handleDatatypeBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { var dt core.Datatype valid := dh.getSystemBroadcastPayload(ctx, msg, data, &dt) if !valid { diff --git a/internal/definitions/definition_handler_datatype_test.go b/internal/definitions/handler_datatype_test.go similarity index 96% rename from internal/definitions/definition_handler_datatype_test.go rename to internal/definitions/handler_datatype_test.go index a901bde154..d289a82aba 100644 --- a/internal/definitions/definition_handler_datatype_test.go +++ b/internal/definitions/handler_datatype_test.go @@ -54,14 +54,14 @@ func TestHandleDefinitionBroadcastDatatypeOk(t *testing.T) { mbi.On("GetDatatypeByName", mock.Anything, "ns1", "name1", "ver1").Return(nil, nil) mbi.On("UpsertDatatype", mock.Anything, mock.Anything, false).Return(nil) mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.NoError(t, err) mdm.AssertExpectations(t) @@ -92,14 +92,14 @@ func TestHandleDefinitionBroadcastDatatypeEventFail(t *testing.T) { mbi.On("GetDatatypeByName", mock.Anything, "ns1", "name1", "ver1").Return(nil, nil) mbi.On("UpsertDatatype", mock.Anything, mock.Anything, false).Return(nil) mbi.On("InsertEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.EqualError(t, err, "pop") mdm.AssertExpectations(t) @@ -123,7 +123,7 @@ func TestHandleDefinitionBroadcastDatatypeMissingID(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, @@ -153,7 +153,7 @@ func TestHandleDefinitionBroadcastBadSchema(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, @@ -178,7 +178,7 @@ func TestHandleDefinitionBroadcastMissingData(t *testing.T) { } dt.Hash = dt.Value.Hash() - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, @@ -210,7 +210,7 @@ func TestHandleDefinitionBroadcastDatatypeLookupFail(t *testing.T) { mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(nil) mbi := dh.database.(*databasemocks.Plugin) mbi.On("GetDatatypeByName", mock.Anything, "ns1", "name1", "ver1").Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Namespace: "ns1", Tag: core.SystemTagDefineDatatype, @@ -247,7 +247,7 @@ func TestHandleDefinitionBroadcastUpsertFail(t *testing.T) { mbi := dh.database.(*databasemocks.Plugin) mbi.On("GetDatatypeByName", mock.Anything, "ns1", "name1", "ver1").Return(nil, nil) mbi.On("UpsertDatatype", mock.Anything, mock.Anything, false).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, @@ -282,7 +282,7 @@ func TestHandleDefinitionBroadcastDatatypeDuplicate(t *testing.T) { mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(nil) mbi := dh.database.(*databasemocks.Plugin) mbi.On("GetDatatypeByName", mock.Anything, "ns1", "name1", "ver1").Return(dt, nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineDatatype, }, diff --git a/internal/definitions/definition_handler_identity_claim.go b/internal/definitions/handler_identity_claim.go similarity index 71% rename from internal/definitions/definition_handler_identity_claim.go rename to internal/definitions/handler_identity_claim.go index a541144b61..442f021c15 100644 --- a/internal/definitions/definition_handler_identity_claim.go +++ b/internal/definitions/handler_identity_claim.go @@ -28,18 +28,39 @@ import ( "github.com/hyperledger/firefly/pkg/database" ) -func (dh *definitionHandlers) handleIdentityClaimBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, verificationID *fftypes.UUID) (HandlerResult, error) { +type identityMsgInfo struct { + core.SignerRef + claimMsg struct { + ID *fftypes.UUID + Hash *fftypes.Bytes32 + } + verifyMsg struct { + ID *fftypes.UUID + } +} + +func buildIdentityMsgInfo(msg *core.Message, verifyMsgID *fftypes.UUID) *identityMsgInfo { + info := &identityMsgInfo{} + info.claimMsg.ID = msg.Header.ID + info.claimMsg.Hash = msg.Hash + info.verifyMsg.ID = verifyMsgID + info.SignerRef = msg.Header.SignerRef + return info +} + +func (dh *definitionHandler) handleIdentityClaimBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, verifyMsgID *fftypes.UUID) (HandlerResult, error) { var claim core.IdentityClaim if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &claim); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "identity claim", msg.Header.ID) } - return dh.handleIdentityClaim(ctx, state, msg, &claim, verificationID) + claim.Identity.Messages.Claim = msg.Header.ID + return dh.handleIdentityClaim(ctx, state, buildIdentityMsgInfo(msg, verifyMsgID), &claim) } -func (dh *definitionHandlers) verifyClaimSignature(ctx context.Context, msg *core.Message, identity *core.Identity, parent *core.Identity) error { - author := msg.Header.Author +func (dh *definitionHandler) verifyClaimSignature(ctx context.Context, msg *identityMsgInfo, identity *core.Identity, parent *core.Identity) error { + author := msg.Author if author == "" { - return i18n.NewError(ctx, coremsgs.MsgDefRejectedAuthorBlank, "identity claim", msg.Header.ID) + return i18n.NewError(ctx, coremsgs.MsgDefRejectedAuthorBlank, "identity claim", msg.claimMsg.ID) } var expectedSigner *core.Identity @@ -54,13 +75,13 @@ func (dh *definitionHandlers) verifyClaimSignature(ctx context.Context, msg *cor valid := author == expectedSigner.DID || (expectedSigner.Type == core.IdentityTypeOrg && author == fmt.Sprintf("%s%s", core.FireFlyOrgDIDPrefix, expectedSigner.ID)) if !valid { - log.L(ctx).Errorf("unable to process identity claim %s - signature mismatch type=%s author=%s expected=%s", msg.Header.ID, identity.Type, author, expectedSigner.DID) - return i18n.NewError(ctx, coremsgs.MsgDefRejectedSignatureMismatch, "identity claim", msg.Header.ID) + log.L(ctx).Errorf("unable to process identity claim %s - signature mismatch type=%s author=%s expected=%s", msg.claimMsg.ID, identity.Type, author, expectedSigner.DID) + return i18n.NewError(ctx, coremsgs.MsgDefRejectedSignatureMismatch, "identity claim", msg.claimMsg.ID) } return nil } -func (dh *definitionHandlers) getClaimVerifier(msg *core.Message, identity *core.Identity) *core.Verifier { +func (dh *definitionHandler) getClaimVerifier(msg *identityMsgInfo, identity *core.Identity) *core.Verifier { verifier := &core.Verifier{ Identity: identity.ID, Namespace: identity.Namespace, @@ -71,13 +92,13 @@ func (dh *definitionHandlers) getClaimVerifier(msg *core.Message, identity *core verifier.VerifierRef.Value = identity.Profile.GetString("id") default: verifier.VerifierRef.Type = dh.blockchain.VerifierType() - verifier.VerifierRef.Value = msg.Header.Key + verifier.VerifierRef.Value = msg.Key } verifier.Seal() return verifier } -func (dh *definitionHandlers) confirmVerificationForClaim(ctx context.Context, state DefinitionBatchState, msg *core.Message, identity, parent *core.Identity) (*fftypes.UUID, error) { +func (dh *definitionHandler) confirmVerificationForClaim(ctx context.Context, state *core.BatchState, msg *identityMsgInfo, identity, parent *core.Identity) (*fftypes.UUID, error) { // Query for messages on the topic for this DID, signed by the right identity idTopic := identity.Topic() fb := database.MessageQueryFactory.NewFilter(ctx) @@ -93,7 +114,7 @@ func (dh *definitionHandlers) confirmVerificationForClaim(ctx context.Context, s return nil, err } // We also need to check pending messages in the current pin batch - for _, pending := range state.GetPendingConfirm() { + for _, pending := range state.PendingConfirms { if pending.Header.Topics.String() == idTopic && pending.Header.Author == parent.DID && pending.Header.Type == core.MessageTypeDefinition && @@ -111,40 +132,43 @@ func (dh *definitionHandlers) confirmVerificationForClaim(ctx context.Context, s var verificationHash *fftypes.Bytes32 if foundAll { var verification core.IdentityVerification - if !dh.getSystemBroadcastPayload(ctx, msg, data, &verification) { + if !dh.getSystemBroadcastPayload(ctx, candidate, data, &verification) { return nil, nil } identityMatches = verification.Identity.Equals(ctx, &identity.IdentityBase) verificationID = verification.Claim.ID verificationHash = verification.Claim.Hash - if identityMatches && msg.Header.ID.Equals(verificationID) && msg.Hash.Equals(verificationHash) { + if identityMatches && msg.claimMsg.ID.Equals(verificationID) && msg.claimMsg.Hash.Equals(verificationHash) { return candidate.Header.ID, nil } } - log.L(ctx).Warnf("Skipping invalid potential verification '%s' for identity claimID='%s' claimHash=%s: foundData=%t identityMatch=%t id=%s hash=%s", candidate.Header.ID, msg.Header.ID, msg.Hash, foundAll, identityMatches, verificationID, verificationHash) + log.L(ctx).Warnf("Skipping invalid potential verification '%s' for identity claimID='%s' claimHash=%s: foundData=%t identityMatch=%t id=%s hash=%s", candidate.Header.ID, msg.claimMsg.ID, msg.claimMsg.Hash, foundAll, identityMatches, verificationID, verificationHash) } return nil, nil } -func (dh *definitionHandlers) handleIdentityClaim(ctx context.Context, state DefinitionBatchState, msg *core.Message, identityClaim *core.IdentityClaim, verificationID *fftypes.UUID) (HandlerResult, error) { +func (dh *definitionHandler) handleIdentityClaim(ctx context.Context, state *core.BatchState, msg *identityMsgInfo, identityClaim *core.IdentityClaim) (HandlerResult, error) { l := log.L(ctx) identity := identityClaim.Identity parent, retryable, err := dh.identity.VerifyIdentityChain(ctx, identity) - if err != nil && retryable { - return HandlerResult{Action: ActionRetry}, err - } else if err != nil { + if err != nil { + if retryable { + return HandlerResult{Action: ActionRetry}, err + } // This cannot be processed as something in the identity chain is invalid. // We treat this as a park - because we don't know if the parent identity // will be processed after this message and generate a rewind. // They are on separate topics, so there is not ordering assurance between the two messages. - l.Infof("Unable to process identity claim (parked) %s: %s", msg.Header.ID, err) + l.Infof("Unable to process identity claim (parked) %s: %s", msg.claimMsg.ID, err) return HandlerResult{Action: ActionWait}, nil } - // Check signature verification - if err := dh.verifyClaimSignature(ctx, msg, identity, parent); err != nil { - return HandlerResult{Action: ActionReject}, err + // For multi-party namespaces, check that the claim message was appropriately signed + if dh.multiparty { + if err := dh.verifyClaimSignature(ctx, msg, identity, parent); err != nil { + return HandlerResult{Action: ActionReject}, err + } } existingIdentity, err := dh.database.GetIdentityByName(ctx, identity.Type, identity.Namespace, identity.Name) @@ -171,24 +195,25 @@ func (dh *definitionHandlers) handleIdentityClaim(ctx context.Context, state Def return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedConflict, "identity verifier", verifierLabel, existingVerifierLabel) } - if parent != nil && identity.Type != core.IdentityTypeNode { + // For child identities in multi-party namespaces, check that the parent signed a verification message + if dh.multiparty && parent != nil && identity.Type != core.IdentityTypeNode { // The verification might be passed into this function, if we confirm the verification second, // or we might have to hunt for it, if we confirm the verification first. - if verificationID == nil { + if msg.verifyMsg.ID == nil { // Search for a corresponding verification message on the same topic - verificationID, err = dh.confirmVerificationForClaim(ctx, state, msg, identity, parent) + msg.verifyMsg.ID, err = dh.confirmVerificationForClaim(ctx, state, msg, identity, parent) if err != nil { return HandlerResult{Action: ActionRetry}, err // retry database errors } } - if verificationID == nil { + if msg.verifyMsg.ID == nil { // Ok, we still confirm the message as it's valid, and we do not want to block the context. // But we do NOT go on to create the identity - we will be called back - log.L(ctx).Infof("Identity %s (%s) awaiting verification claim='%s'", identity.DID, identity.ID, msg.Header.ID) + log.L(ctx).Infof("Identity %s (%s) awaiting verification claim='%s'", identity.DID, identity.ID, msg.claimMsg.ID) return HandlerResult{Action: ActionConfirm}, nil } - log.L(ctx).Infof("Identity %s (%s) verified claim='%s' verification='%s'", identity.DID, identity.ID, msg.Header.ID, verificationID) - identity.Messages.Verification = verificationID + log.L(ctx).Infof("Identity %s (%s) verified claim='%s' verification='%s'", identity.DID, identity.ID, msg.claimMsg.ID, msg.verifyMsg.ID) + identity.Messages.Verification = msg.verifyMsg.ID } if existingVerifier == nil { @@ -211,7 +236,7 @@ func (dh *definitionHandlers) handleIdentityClaim(ctx context.Context, state Def }) } - state.DIDClaimConfirmed(identity.DID) + state.AddConfirmedDIDClaim(identity.DID) state.AddFinalize(func(ctx context.Context) error { event := core.NewEvent(core.EventTypeIdentityConfirmed, identity.Namespace, identity.ID, nil, core.SystemTopicDefinitions) return dh.database.InsertEvent(ctx, event) diff --git a/internal/definitions/definition_handler_identity_claim_test.go b/internal/definitions/handler_identity_claim_test.go similarity index 86% rename from internal/definitions/definition_handler_identity_claim_test.go rename to internal/definitions/handler_identity_claim_test.go index 586c41a2c4..0ce6db8ad4 100644 --- a/internal/definitions/definition_handler_identity_claim_test.go +++ b/internal/definitions/handler_identity_claim_test.go @@ -97,10 +97,11 @@ func testCustomClaimAndVerification(t *testing.T) (*core.Identity, *core.Identit claimMsg := &core.Message{ Header: core.MessageHeader{ - ID: custom1.Messages.Claim, - Type: core.MessageTypeDefinition, - Tag: core.SystemTagIdentityClaim, - Topics: core.FFStringArray{custom1.Topic()}, + Namespace: "ns1", + ID: custom1.Messages.Claim, + Type: core.MessageTypeDefinition, + Tag: core.SystemTagIdentityClaim, + Topics: core.FFStringArray{custom1.Topic()}, SignerRef: core.SignerRef{ Author: custom1.DID, Key: "0x12345", @@ -173,14 +174,16 @@ func TestHandleDefinitionIdentityClaimCustomWithExistingParentVerificationOk(t * mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, false, nil).Once() mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, true, nil) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - assert.Equal(t, bs.confirmedDIDClaims, []string{custom1.DID}) + assert.Equal(t, bs.ConfirmedDIDClaims, []string{custom1.DID}) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -220,13 +223,15 @@ func TestHandleDefinitionIdentityClaimIdempotentReplay(t *testing.T) { mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, false, nil).Once() mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, true, nil) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true + + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mim.AssertExpectations(t) @@ -254,9 +259,11 @@ func TestHandleDefinitionIdentityClaimFailInsertIdentity(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, true, nil) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -284,9 +291,11 @@ func TestHandleDefinitionIdentityClaimVerificationDataFail(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(nil, false, fmt.Errorf("pop")) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true + + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -314,9 +323,11 @@ func TestHandleDefinitionIdentityClaimVerificationMissingData(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{}, true, nil) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) @@ -345,9 +356,11 @@ func TestHandleDefinitionIdentityClaimFailInsertVerifier(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{verifyData}, true, nil) - bs.pendingConfirms[*verifyMsg.Header.ID] = verifyMsg + dh.multiparty = true + + bs.AddPendingConfirm(verifyMsg.Header.ID, verifyMsg) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -372,7 +385,9 @@ func TestHandleDefinitionIdentityClaimCustomMissingParentVerificationOk(t *testi mdi.On("GetVerifierByValue", ctx, core.VerifierTypeEthAddress, "ns1", "0x12345").Return(nil, nil) mdi.On("GetMessages", ctx, "ns1", mock.Anything).Return([]*core.Message{}, nil, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) // Just wait for the verification to come in later assert.NoError(t, err) @@ -396,7 +411,9 @@ func TestHandleDefinitionIdentityClaimCustomParentVerificationFail(t *testing.T) mdi.On("GetVerifierByValue", ctx, core.VerifierTypeEthAddress, "ns1", "0x12345").Return(nil, nil) mdi.On("GetMessages", ctx, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -421,7 +438,9 @@ func TestHandleDefinitionIdentityClaimVerifierClash(t *testing.T) { Hash: fftypes.NewRandB32(), }, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -444,7 +463,9 @@ func TestHandleDefinitionIdentityClaimVerifierError(t *testing.T) { mdi.On("GetIdentityByID", ctx, "ns1", custom1.ID).Return(nil, nil) mdi.On("GetVerifierByValue", ctx, core.VerifierTypeEthAddress, "ns1", "0x12345").Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -469,7 +490,9 @@ func TestHandleDefinitionIdentityClaimIdentityClash(t *testing.T) { }, }, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -491,7 +514,9 @@ func TestHandleDefinitionIdentityClaimIdentityError(t *testing.T) { mdi.On("GetIdentityByName", ctx, custom1.Type, custom1.Namespace, custom1.Name).Return(nil, nil) mdi.On("GetIdentityByID", ctx, "ns1", custom1.ID).Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -510,7 +535,9 @@ func TestHandleDefinitionIdentityMissingAuthor(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("VerifyIdentityChain", ctx, custom1).Return(org1, false, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -528,7 +555,9 @@ func TestHandleDefinitionIdentityClaimBadSignature(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("VerifyIdentityChain", ctx, custom1).Return(org1, false, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -546,7 +575,7 @@ func TestHandleDefinitionIdentityVerifyChainFail(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("VerifyIdentityChain", ctx, custom1).Return(nil, true, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -564,7 +593,7 @@ func TestHandleDefinitionIdentityVerifyChainInvalid(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("VerifyIdentityChain", ctx, custom1).Return(nil, false, fmt.Errorf("wrong")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{claimData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionWait}, action) assert.NoError(t, err) @@ -579,7 +608,7 @@ func TestHandleDefinitionIdentityClaimBadData(t *testing.T) { _, org1, claimMsg, _, _, _ := testCustomClaimAndVerification(t) claimMsg.Header.Author = org1.DID // should be the child for the claim - action, err := dh.HandleDefinitionBroadcast(ctx, bs, claimMsg, core.DataArray{}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, claimMsg, core.DataArray{}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) diff --git a/internal/definitions/definition_handler_identity_update.go b/internal/definitions/handler_identity_update.go similarity index 69% rename from internal/definitions/definition_handler_identity_update.go rename to internal/definitions/handler_identity_update.go index 627ff98b76..d6d9f32eb0 100644 --- a/internal/definitions/definition_handler_identity_update.go +++ b/internal/definitions/handler_identity_update.go @@ -19,19 +19,32 @@ package definitions import ( "context" + "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" ) -func (dh *definitionHandlers) handleIdentityUpdateBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { +type identityUpdateMsgInfo struct { + ID *fftypes.UUID + Author string +} + +func (dh *definitionHandler) handleIdentityUpdateBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { var update core.IdentityUpdate if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &update); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "identity update", msg.Header.ID) } + return dh.handleIdentityUpdate(ctx, state, &identityUpdateMsgInfo{ + ID: msg.Header.ID, + Author: msg.Header.Author, + }, &update) +} + +func (dh *definitionHandler) handleIdentityUpdate(ctx context.Context, state *core.BatchState, msg *identityUpdateMsgInfo, update *core.IdentityUpdate) (HandlerResult, error) { if err := update.Identity.Validate(ctx); err != nil { - return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedValidateFail, "identity update", msg.Header.ID, err) + return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedValidateFail, "identity update", update.Identity.ID, err) } // Get the existing identity (must be a confirmed identity at the point an update is issued) @@ -40,17 +53,17 @@ func (dh *definitionHandlers) handleIdentityUpdateBroadcast(ctx context.Context, return HandlerResult{Action: ActionRetry}, err } if identity == nil { - return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedIdentityNotFound, "identity update", msg.Header.ID, update.Identity.ID) + return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedIdentityNotFound, "identity update", update.Identity.ID, update.Identity.ID) } // Check the author matches - if identity.DID != msg.Header.Author { - return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedWrongAuthor, "identity update", msg.Header.ID, msg.Header.Author) + if dh.multiparty && identity.DID != msg.Author { + return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedWrongAuthor, "identity update", update.Identity.ID, msg.Author) } // Update the profile identity.IdentityProfile = update.Updates - identity.Messages.Update = msg.Header.ID + identity.Messages.Update = msg.ID err = dh.database.UpsertIdentity(ctx, identity, database.UpsertOptimizationExisting) if err != nil { return HandlerResult{Action: ActionRetry}, err diff --git a/internal/definitions/definition_handler_identity_update_test.go b/internal/definitions/handler_identity_update_test.go similarity index 87% rename from internal/definitions/definition_handler_identity_update_test.go rename to internal/definitions/handler_identity_update_test.go index cc1208cbff..ccabbe31f2 100644 --- a/internal/definitions/definition_handler_identity_update_test.go +++ b/internal/definitions/handler_identity_update_test.go @@ -87,11 +87,11 @@ func TestHandleDefinitionIdentityUpdateOk(t *testing.T) { return event.Type == core.EventTypeIdentityUpdated })).Return(nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mim.AssertExpectations(t) @@ -110,7 +110,7 @@ func TestHandleDefinitionIdentityUpdateUpsertFail(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("UpsertIdentity", ctx, mock.Anything, database.UpsertOptimizationExisting).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -122,6 +122,7 @@ func TestHandleDefinitionIdentityUpdateUpsertFail(t *testing.T) { func TestHandleDefinitionIdentityInvalidIdentity(t *testing.T) { dh, bs := newTestDefinitionHandler(t) ctx := context.Background() + dh.multiparty = true org1, updateMsg, updateData, _ := testIdentityUpdate(t) updateMsg.Header.Author = "wrong" @@ -129,7 +130,7 @@ func TestHandleDefinitionIdentityInvalidIdentity(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(org1, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -146,7 +147,7 @@ func TestHandleDefinitionIdentityNotFound(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(nil, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -163,7 +164,7 @@ func TestHandleDefinitionIdentityLookupFail(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -200,7 +201,7 @@ func TestHandleDefinitionIdentityValidateFail(t *testing.T) { }, } - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{updateData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -225,7 +226,7 @@ func TestHandleDefinitionIdentityMissingData(t *testing.T) { }, } - action, err := dh.HandleDefinitionBroadcast(ctx, bs, updateMsg, core.DataArray{}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, updateMsg, core.DataArray{}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) diff --git a/internal/definitions/definition_handler_identity_verification.go b/internal/definitions/handler_identity_verification.go similarity index 91% rename from internal/definitions/definition_handler_identity_verification.go rename to internal/definitions/handler_identity_verification.go index aaf5918ea9..14b5e65496 100644 --- a/internal/definitions/definition_handler_identity_verification.go +++ b/internal/definitions/handler_identity_verification.go @@ -24,7 +24,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (dh *definitionHandlers) handleIdentityVerificationBroadcast(ctx context.Context, state DefinitionBatchState, verifyMsg *core.Message, data core.DataArray) (HandlerResult, error) { +func (dh *definitionHandler) handleIdentityVerificationBroadcast(ctx context.Context, state *core.BatchState, verifyMsg *core.Message, data core.DataArray) (HandlerResult, error) { var verification core.IdentityVerification valid := dh.getSystemBroadcastPayload(ctx, verifyMsg, data, &verification) if !valid { @@ -48,15 +48,14 @@ func (dh *definitionHandlers) handleIdentityVerificationBroadcast(ctx context.Co } // At this point, this is a valid verification, but we don't know if the claim has arrived. - // It might be being processed in the same pin batch as us - so we can't - // See if the message has already arrived, if so we need to queue a rewind to it claimMsg, err := dh.database.GetMessageByID(ctx, dh.namespace, verification.Claim.ID) if err != nil { return HandlerResult{Action: ActionRetry}, err } + // See if the message was processed earlier in this same batch if claimMsg == nil || claimMsg.State != core.MessageStateConfirmed { - claimMsg = state.GetPendingConfirm()[*verification.Claim.ID] + claimMsg = state.PendingConfirms[*verification.Claim.ID] } if claimMsg != nil { diff --git a/internal/definitions/definition_handler_identity_verification_test.go b/internal/definitions/handler_identity_verification_test.go similarity index 86% rename from internal/definitions/definition_handler_identity_verification_test.go rename to internal/definitions/handler_identity_verification_test.go index 4793bd3eaa..36a604c808 100644 --- a/internal/definitions/definition_handler_identity_verification_test.go +++ b/internal/definitions/handler_identity_verification_test.go @@ -65,14 +65,16 @@ func TestHandleDefinitionIdentityVerificationWithExistingClaimOk(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{claimData}, true, nil) - bs.pendingConfirms[*claimMsg.Header.ID] = claimMsg + dh.multiparty = true - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + bs.AddPendingConfirm(claimMsg.Header.ID, claimMsg) + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - assert.Equal(t, bs.confirmedDIDClaims, []string{custom1.DID}) + assert.Equal(t, bs.ConfirmedDIDClaims, []string{custom1.DID}) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mim.AssertExpectations(t) @@ -96,7 +98,7 @@ func TestHandleDefinitionIdentityVerificationIncompleteClaimData(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(core.DataArray{}, false, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) @@ -122,7 +124,7 @@ func TestHandleDefinitionIdentityVerificationClaimDataFail(t *testing.T) { mdm := dh.data.(*datamocks.Manager) mdm.On("GetMessageDataCached", ctx, mock.Anything).Return(nil, false, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -146,7 +148,7 @@ func TestHandleDefinitionIdentityVerificationClaimHashMismatchl(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetMessageByID", ctx, "ns1", claimMsg.Header.ID).Return(claimMsg, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -167,7 +169,7 @@ func TestHandleDefinitionIdentityVerificationBeforeClaim(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetMessageByID", ctx, "ns1", claimMsg.Header.ID).Return(nil, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) @@ -188,7 +190,7 @@ func TestHandleDefinitionIdentityVerificationClaimLookupFail(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetMessageByID", ctx, "ns1", claimMsg.Header.ID).Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -207,7 +209,7 @@ func TestHandleDefinitionIdentityVerificationWrongSigner(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(org1, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -224,7 +226,7 @@ func TestHandleDefinitionIdentityVerificationCheckParentNotFound(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(nil, nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -241,7 +243,7 @@ func TestHandleDefinitionIdentityVerificationCheckParentFail(t *testing.T) { mim := dh.identity.(*identitymanagermocks.Manager) mim.On("CachedIdentityLookupByID", ctx, org1.ID).Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, verifyMsg, core.DataArray{verifyData}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -263,7 +265,7 @@ func TestHandleDefinitionIdentityVerificationInvalidPayload(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - action, err := dh.HandleDefinitionBroadcast(ctx, bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, &core.Message{ Header: core.MessageHeader{ ID: fftypes.NewUUID(), Type: core.MessageTypeBroadcast, @@ -280,7 +282,7 @@ func TestHandleDefinitionIdentityVerificationInvalidData(t *testing.T) { dh, bs := newTestDefinitionHandler(t) ctx := context.Background() - action, err := dh.HandleDefinitionBroadcast(ctx, bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, &core.Message{ Header: core.MessageHeader{ ID: fftypes.NewUUID(), Type: core.MessageTypeBroadcast, diff --git a/internal/definitions/definition_handler_namespace.go b/internal/definitions/handler_namespace.go similarity index 92% rename from internal/definitions/definition_handler_namespace.go rename to internal/definitions/handler_namespace.go index 39b951713d..1bf7ea5f03 100644 --- a/internal/definitions/definition_handler_namespace.go +++ b/internal/definitions/handler_namespace.go @@ -25,7 +25,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (dh *definitionHandlers) handleNamespaceBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { +func (dh *definitionHandler) handleNamespaceBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (HandlerResult, error) { var ns core.Namespace if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &ns); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "namespace", msg.Header.ID) diff --git a/internal/definitions/definition_handler_namespace_test.go b/internal/definitions/handler_namespace_test.go similarity index 94% rename from internal/definitions/definition_handler_namespace_test.go rename to internal/definitions/handler_namespace_test.go index 4f3c3abebf..b62a91a32b 100644 --- a/internal/definitions/definition_handler_namespace_test.go +++ b/internal/definitions/handler_namespace_test.go @@ -46,14 +46,14 @@ func TestHandleDefinitionBroadcastNSOk(t *testing.T) { mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, nil) mdi.On("UpsertNamespace", mock.Anything, mock.Anything, false).Return(nil) mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -76,14 +76,14 @@ func TestHandleDefinitionBroadcastNSEventFail(t *testing.T) { mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, nil) mdi.On("UpsertNamespace", mock.Anything, mock.Anything, false).Return(nil) mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -105,7 +105,7 @@ func TestHandleDefinitionBroadcastNSUpsertFail(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, nil) mdi.On("UpsertNamespace", mock.Anything, mock.Anything, false).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -120,7 +120,7 @@ func TestHandleDefinitionBroadcastNSUpsertFail(t *testing.T) { func TestHandleDefinitionBroadcastNSMissingData(t *testing.T) { dh, bs := newTestDefinitionHandler(t) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -140,7 +140,7 @@ func TestHandleDefinitionBroadcastNSBadID(t *testing.T) { Value: fftypes.JSONAnyPtrBytes(b), } - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -157,7 +157,7 @@ func TestHandleDefinitionBroadcastNSBadData(t *testing.T) { Value: fftypes.JSONAnyPtr(`!{json`), } - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -182,7 +182,7 @@ func TestHandleDefinitionBroadcastDuplicate(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetNamespace", mock.Anything, "ns1").Return(ns, nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -213,14 +213,14 @@ func TestHandleDefinitionBroadcastDuplicateOverrideLocal(t *testing.T) { mdi.On("DeleteNamespace", mock.Anything, mock.Anything).Return(nil) mdi.On("UpsertNamespace", mock.Anything, mock.Anything, false).Return(nil) mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, }, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](context.Background()) + err = bs.RunFinalize(context.Background()) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -243,7 +243,7 @@ func TestHandleDefinitionBroadcastDuplicateOverrideLocalFail(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetNamespace", mock.Anything, "ns1").Return(ns, nil) mdi.On("DeleteNamespace", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, @@ -270,7 +270,7 @@ func TestHandleDefinitionBroadcastDupCheckFail(t *testing.T) { mdi := dh.database.(*databasemocks.Plugin) mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, fmt.Errorf("pop")) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: core.SystemTagDefineNamespace, }, diff --git a/internal/definitions/definition_handler_network_node.go b/internal/definitions/handler_network_node.go similarity index 84% rename from internal/definitions/definition_handler_network_node.go rename to internal/definitions/handler_network_node.go index b739251a8f..d204d6421d 100644 --- a/internal/definitions/definition_handler_network_node.go +++ b/internal/definitions/handler_network_node.go @@ -24,7 +24,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (dh *definitionHandlers) handleDeprecatedNodeBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { +func (dh *definitionHandler) handleDeprecatedNodeBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { var nodeOld core.DeprecatedNode if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &nodeOld); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "node", msg.Header.ID) @@ -41,6 +41,6 @@ func (dh *definitionHandlers) handleDeprecatedNodeBroadcast(ctx context.Context, return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedIdentityNotFound, "node", nodeOld.ID, nodeOld.Owner) } - return dh.handleIdentityClaim(ctx, state, msg, nodeOld.AddMigratedParent(owner.ID), nil) + return dh.handleIdentityClaim(ctx, state, buildIdentityMsgInfo(msg, nil), nodeOld.AddMigratedParent(owner.ID)) } diff --git a/internal/definitions/definition_handler_network_node_test.go b/internal/definitions/handler_network_node_test.go similarity index 93% rename from internal/definitions/definition_handler_network_node_test.go rename to internal/definitions/handler_network_node_test.go index bb90053616..258922008d 100644 --- a/internal/definitions/definition_handler_network_node_test.go +++ b/internal/definitions/handler_network_node_test.go @@ -130,13 +130,15 @@ func TestHandleDeprecatedNodeDefinitionOK(t *testing.T) { mdx := dh.exchange.(*dataexchangemocks.Plugin) mdx.On("AddPeer", ctx, node.DX.Endpoint).Return(nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, msg, core.DataArray{data}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, msg, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.preFinalizers[0](ctx) + err = bs.RunPreFinalize(ctx) assert.NoError(t, err) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mim.AssertExpectations(t) @@ -149,7 +151,7 @@ func TestHandleDeprecatedNodeDefinitionBadData(t *testing.T) { dh, bs := newTestDefinitionHandler(t) ctx := context.Background() - action, err := dh.handleDeprecatedNodeBroadcast(ctx, bs, &core.Message{}, core.DataArray{}) + action, err := dh.handleDeprecatedNodeBroadcast(ctx, &bs.BatchState, &core.Message{}, core.DataArray{}) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) @@ -168,7 +170,7 @@ func TestHandleDeprecatedNodeDefinitionFailOrgLookup(t *testing.T) { Value: node.Owner, }).Return(nil, fmt.Errorf("pop")) - action, err := dh.handleDeprecatedNodeBroadcast(ctx, bs, msg, core.DataArray{data}) + action, err := dh.handleDeprecatedNodeBroadcast(ctx, &bs.BatchState, msg, core.DataArray{data}) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.Regexp(t, "pop", err) @@ -189,7 +191,7 @@ func TestHandleDeprecatedNodeDefinitionOrgNotFound(t *testing.T) { Value: node.Owner, }).Return(nil, nil) - action, err := dh.handleDeprecatedNodeBroadcast(ctx, bs, msg, core.DataArray{data}) + action, err := dh.handleDeprecatedNodeBroadcast(ctx, &bs.BatchState, msg, core.DataArray{data}) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) diff --git a/internal/definitions/definition_handler_network_org.go b/internal/definitions/handler_network_org.go similarity index 79% rename from internal/definitions/definition_handler_network_org.go rename to internal/definitions/handler_network_org.go index a3328a49a0..ecfcc2b2e7 100644 --- a/internal/definitions/definition_handler_network_org.go +++ b/internal/definitions/handler_network_org.go @@ -24,7 +24,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (dh *definitionHandlers) handleDeprecatedOrganizationBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { +func (dh *definitionHandler) handleDeprecatedOrganizationBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { var orgOld core.DeprecatedOrganization valid := dh.getSystemBroadcastPayload(ctx, msg, data, &orgOld) @@ -32,6 +32,6 @@ func (dh *definitionHandlers) handleDeprecatedOrganizationBroadcast(ctx context. return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "org", msg.Header.ID) } - return dh.handleIdentityClaim(ctx, state, msg, orgOld.Migrated(), nil) + return dh.handleIdentityClaim(ctx, state, buildIdentityMsgInfo(msg, nil), orgOld.Migrated()) } diff --git a/internal/definitions/definition_handler_network_org_test.go b/internal/definitions/handler_network_org_test.go similarity index 94% rename from internal/definitions/definition_handler_network_org_test.go rename to internal/definitions/handler_network_org_test.go index 869fd6e0b2..7b4a7928c7 100644 --- a/internal/definitions/definition_handler_network_org_test.go +++ b/internal/definitions/handler_network_org_test.go @@ -110,11 +110,13 @@ func TestHandleDeprecatedOrgDefinitionOK(t *testing.T) { return event.Type == core.EventTypeIdentityConfirmed })).Return(nil) - action, err := dh.HandleDefinitionBroadcast(ctx, bs, msg, core.DataArray{data}, fftypes.NewUUID()) + dh.multiparty = true + + action, err := dh.HandleDefinitionBroadcast(ctx, &bs.BatchState, msg, core.DataArray{data}, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm}, action) assert.NoError(t, err) - err = bs.finalizers[0](ctx) + err = bs.RunFinalize(ctx) assert.NoError(t, err) mim.AssertExpectations(t) @@ -125,7 +127,7 @@ func TestHandleDeprecatedOrgDefinitionBadData(t *testing.T) { dh, bs := newTestDefinitionHandler(t) ctx := context.Background() - action, err := dh.handleDeprecatedOrganizationBroadcast(ctx, bs, &core.Message{}, core.DataArray{}) + action, err := dh.handleDeprecatedOrganizationBroadcast(ctx, &bs.BatchState, &core.Message{}, core.DataArray{}) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) diff --git a/internal/definitions/definition_handler_test.go b/internal/definitions/handler_test.go similarity index 71% rename from internal/definitions/definition_handler_test.go rename to internal/definitions/handler_test.go index 8ef28629ca..9a8c968057 100644 --- a/internal/definitions/definition_handler_test.go +++ b/internal/definitions/handler_test.go @@ -33,7 +33,7 @@ import ( "github.com/stretchr/testify/assert" ) -func newTestDefinitionHandler(t *testing.T) (*definitionHandlers, *testDefinitionBatchState) { +func newTestDefinitionHandler(t *testing.T) (*definitionHandler, *testDefinitionBatchState) { mdi := &databasemocks.Plugin{} mbi := &blockchainmocks.Plugin{} mdx := &dataexchangemocks.Plugin{} @@ -42,54 +42,39 @@ func newTestDefinitionHandler(t *testing.T) (*definitionHandlers, *testDefinitio mam := &assetmocks.Manager{} mcm := &contractmocks.Manager{} mbi.On("VerifierType").Return(core.VerifierTypeEthAddress).Maybe() - dh, _ := NewDefinitionHandler(context.Background(), "ns1", mdi, mbi, mdx, mdm, mim, mam, mcm) - return dh.(*definitionHandlers), newTestDefinitionBatchState(t) + dh, _ := newDefinitionHandler(context.Background(), "ns1", false, mdi, mbi, mdx, mdm, mim, mam, mcm) + return dh, newTestDefinitionBatchState(t) } type testDefinitionBatchState struct { - t *testing.T - preFinalizers []func(ctx context.Context) error - finalizers []func(ctx context.Context) error - pendingConfirms map[fftypes.UUID]*core.Message - confirmedDIDClaims []string + core.BatchState + t *testing.T } func newTestDefinitionBatchState(t *testing.T) *testDefinitionBatchState { return &testDefinitionBatchState{ - t: t, - pendingConfirms: make(map[fftypes.UUID]*core.Message), + BatchState: core.BatchState{ + PendingConfirms: make(map[fftypes.UUID]*core.Message), + PreFinalize: make([]func(ctx context.Context) error, 0), + Finalize: make([]func(ctx context.Context) error, 0), + }, + t: t, } } -func (bs *testDefinitionBatchState) AddPreFinalize(pf func(ctx context.Context) error) { - bs.preFinalizers = append(bs.preFinalizers, pf) -} - -func (bs *testDefinitionBatchState) AddFinalize(pf func(ctx context.Context) error) { - bs.finalizers = append(bs.finalizers, pf) -} - -func (bs *testDefinitionBatchState) GetPendingConfirm() map[fftypes.UUID]*core.Message { - return bs.pendingConfirms -} - -func (bs *testDefinitionBatchState) DIDClaimConfirmed(did string) { - bs.confirmedDIDClaims = append(bs.confirmedDIDClaims, did) -} - func (bs *testDefinitionBatchState) assertNoFinalizers() { - assert.Empty(bs.t, bs.preFinalizers) - assert.Empty(bs.t, bs.finalizers) + assert.Empty(bs.t, bs.PreFinalize) + assert.Empty(bs.t, bs.Finalize) } func TestInitFail(t *testing.T) { - _, err := NewDefinitionHandler(context.Background(), "", nil, nil, nil, nil, nil, nil, nil) + _, err := newDefinitionHandler(context.Background(), "", false, nil, nil, nil, nil, nil, nil, nil) assert.Regexp(t, "FF10128", err) } func TestHandleDefinitionBroadcastUnknown(t *testing.T) { dh, bs := newTestDefinitionHandler(t) - action, err := dh.HandleDefinitionBroadcast(context.Background(), bs, &core.Message{ + action, err := dh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, &core.Message{ Header: core.MessageHeader{ Tag: "unknown", }, diff --git a/internal/definitions/definition_handler_tokenpool.go b/internal/definitions/handler_tokenpool.go similarity index 88% rename from internal/definitions/definition_handler_tokenpool.go rename to internal/definitions/handler_tokenpool.go index a8e12f36d0..9482fa6e85 100644 --- a/internal/definitions/definition_handler_tokenpool.go +++ b/internal/definitions/handler_tokenpool.go @@ -26,7 +26,7 @@ import ( "github.com/hyperledger/firefly/pkg/database" ) -func (dh *definitionHandlers) handleTokenPoolBroadcast(ctx context.Context, state DefinitionBatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { +func (dh *definitionHandler) handleTokenPoolBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray) (HandlerResult, error) { var announce core.TokenPoolAnnouncement if valid := dh.getSystemBroadcastPayload(ctx, msg, data, &announce); !valid { return HandlerResult{Action: ActionReject}, i18n.NewError(ctx, coremsgs.MsgDefRejectedBadPayload, "token pool", msg.Header.ID) @@ -34,7 +34,10 @@ func (dh *definitionHandlers) handleTokenPoolBroadcast(ctx context.Context, stat pool := announce.Pool pool.Message = msg.Header.ID + return dh.handleTokenPoolDefinition(ctx, state, pool) +} +func (dh *definitionHandler) handleTokenPoolDefinition(ctx context.Context, state *core.BatchState, pool *core.TokenPool) (HandlerResult, error) { // Set an event correlator, so that if we reject then the sync-async bridge action can know // from the event (without downloading and parsing the msg) correlator := pool.ID diff --git a/internal/definitions/definition_handler_tokenpool_test.go b/internal/definitions/handler_tokenpool_test.go similarity index 93% rename from internal/definitions/definition_handler_tokenpool_test.go rename to internal/definitions/handler_tokenpool_test.go index d96361aa1f..a5d99fe8fd 100644 --- a/internal/definitions/definition_handler_tokenpool_test.go +++ b/internal/definitions/handler_tokenpool_test.go @@ -82,11 +82,11 @@ func TestHandleDefinitionBroadcastTokenPoolActivateOK(t *testing.T) { })).Return(nil) mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*core.TokenPool")).Return(nil) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionWait, CustomCorrelator: pool.ID}, action) assert.NoError(t, err) - err = bs.preFinalizers[0](context.Background()) + err = bs.RunPreFinalize(context.Background()) assert.NoError(t, err) mdi.AssertExpectations(t) @@ -103,7 +103,7 @@ func TestHandleDefinitionBroadcastTokenPoolGetPoolFail(t *testing.T) { mdi := sh.database.(*databasemocks.Plugin) mdi.On("GetTokenPoolByID", context.Background(), "ns1", pool.ID).Return(nil, fmt.Errorf("pop")) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.EqualError(t, err, "pop") @@ -127,11 +127,11 @@ func TestHandleDefinitionBroadcastTokenPoolExisting(t *testing.T) { })).Return(nil) mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*core.TokenPool")).Return(nil) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionWait, CustomCorrelator: pool.ID}, action) assert.NoError(t, err) - err = bs.preFinalizers[0](context.Background()) + err = bs.RunPreFinalize(context.Background()) assert.NoError(t, err) } @@ -150,7 +150,7 @@ func TestHandleDefinitionBroadcastTokenPoolExistingConfirmed(t *testing.T) { mdi := sh.database.(*databasemocks.Plugin) mdi.On("GetTokenPoolByID", context.Background(), "ns1", pool.ID).Return(existing, nil) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionConfirm, CustomCorrelator: pool.ID}, action) assert.NoError(t, err) @@ -171,7 +171,7 @@ func TestHandleDefinitionBroadcastTokenPoolIDMismatch(t *testing.T) { return *p.ID == *pool.ID && p.Message == msg.Header.ID })).Return(database.IDMismatch) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject, CustomCorrelator: pool.ID}, action) assert.Error(t, err) @@ -193,7 +193,7 @@ func TestHandleDefinitionBroadcastTokenPoolFailUpsert(t *testing.T) { return *p.ID == *pool.ID && p.Message == msg.Header.ID })).Return(fmt.Errorf("pop")) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionRetry}, action) assert.EqualError(t, err, "pop") @@ -217,11 +217,11 @@ func TestHandleDefinitionBroadcastTokenPoolActivateFail(t *testing.T) { })).Return(nil) mam.On("ActivateTokenPool", context.Background(), mock.AnythingOfType("*core.TokenPool")).Return(fmt.Errorf("pop")) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionWait, CustomCorrelator: pool.ID}, action) assert.NoError(t, err) - err = bs.preFinalizers[0](context.Background()) + err = bs.RunPreFinalize(context.Background()) assert.EqualError(t, err, "pop") mdi.AssertExpectations(t) @@ -236,7 +236,7 @@ func TestHandleDefinitionBroadcastTokenPoolValidateFail(t *testing.T) { msg, data, err := buildPoolDefinitionMessage(announce) assert.NoError(t, err) - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, data, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, data, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) bs.assertNoFinalizers() @@ -252,7 +252,7 @@ func TestHandleDefinitionBroadcastTokenPoolBadMessage(t *testing.T) { }, } - action, err := sh.HandleDefinitionBroadcast(context.Background(), bs, msg, nil, fftypes.NewUUID()) + action, err := sh.HandleDefinitionBroadcast(context.Background(), &bs.BatchState, msg, nil, fftypes.NewUUID()) assert.Equal(t, HandlerResult{Action: ActionReject}, action) assert.Error(t, err) bs.assertNoFinalizers() diff --git a/internal/definitions/sender.go b/internal/definitions/sender.go new file mode 100644 index 0000000000..fe85cb0c31 --- /dev/null +++ b/internal/definitions/sender.go @@ -0,0 +1,140 @@ +// Copyright © 2022 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 definitions + +import ( + "context" + "encoding/json" + + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly/internal/assets" + "github.com/hyperledger/firefly/internal/broadcast" + "github.com/hyperledger/firefly/internal/contracts" + "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/data" + "github.com/hyperledger/firefly/internal/identity" + "github.com/hyperledger/firefly/pkg/blockchain" + "github.com/hyperledger/firefly/pkg/core" + "github.com/hyperledger/firefly/pkg/database" + "github.com/hyperledger/firefly/pkg/dataexchange" +) + +type Sender interface { + core.Named + + ClaimIdentity(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, parentSigner *core.SignerRef, waitConfirm bool) error + UpdateIdentity(ctx context.Context, identity *core.Identity, def *core.IdentityUpdate, signingIdentity *core.SignerRef, waitConfirm bool) error + DefineDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) error + DefineTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) error + DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error + DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error +} + +type definitionSender struct { + ctx context.Context + namespace string + multiparty bool + database database.Plugin + broadcast broadcast.Manager // optional + identity identity.Manager + data data.Manager + contracts contracts.Manager + handler *definitionHandler +} + +// Definitions that get processed immediately will create a temporary batch state and then finalize it inline +func fakeBatch(ctx context.Context, handler func(context.Context, *core.BatchState) (HandlerResult, error)) (err error) { + var state core.BatchState + _, err = handler(ctx, &state) + if err == nil { + err = state.RunPreFinalize(ctx) + } + if err == nil { + err = state.RunFinalize(ctx) + } + return err +} + +func NewDefinitionSender(ctx context.Context, ns string, multiparty bool, di database.Plugin, bi blockchain.Plugin, dx dataexchange.Plugin, bm broadcast.Manager, im identity.Manager, dm data.Manager, am assets.Manager, cm contracts.Manager) (Sender, Handler, error) { + if di == nil || im == nil || dm == nil || cm == nil { + return nil, nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError) + } + ds := &definitionSender{ + ctx: ctx, + namespace: ns, + multiparty: multiparty, + database: di, + broadcast: bm, + identity: im, + data: dm, + contracts: cm, + } + dh, err := newDefinitionHandler(ctx, ns, multiparty, di, bi, dx, dm, im, am, cm) + ds.handler = dh + return ds, dh, err +} + +func (bm *definitionSender) Name() string { + return "DefinitionSender" +} + +func (bm *definitionSender) sendDefinitionDefault(ctx context.Context, def core.Definition, tag string, waitConfirm bool) (msg *core.Message, err error) { + return bm.sendDefinition(ctx, def, &core.SignerRef{ /* resolve to node default */ }, tag, waitConfirm) +} + +func (bm *definitionSender) sendDefinition(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (msg *core.Message, err error) { + + err = bm.identity.ResolveInputSigningIdentity(ctx, signingIdentity) + if err != nil { + return nil, err + } + + return bm.sendDefinitionCommon(ctx, def, signingIdentity, tag, waitConfirm) +} + +func (bm *definitionSender) sendDefinitionCommon(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (*core.Message, error) { + + b, err := json.Marshal(&def) + if err != nil { + return nil, i18n.WrapError(ctx, err, coremsgs.MsgSerializationFailed) + } + dataValue := fftypes.JSONAnyPtrBytes(b) + message := &core.MessageInOut{ + Message: core.Message{ + Header: core.MessageHeader{ + Namespace: bm.namespace, + Type: core.MessageTypeDefinition, + Topics: core.FFStringArray{def.Topic()}, + Tag: tag, + TxType: core.TransactionTypeBatchPin, + SignerRef: *signingIdentity, + }, + }, + InlineData: core.InlineData{ + &core.DataRefOrValue{Value: dataValue}, + }, + } + + sender := bm.broadcast.NewBroadcast(message) + if waitConfirm { + err = sender.SendAndWait(ctx) + } else { + err = sender.Send(ctx) + } + return &message.Message, err +} diff --git a/internal/definitions/sender_contracts.go b/internal/definitions/sender_contracts.go new file mode 100644 index 0000000000..59026521b4 --- /dev/null +++ b/internal/definitions/sender_contracts.go @@ -0,0 +1,72 @@ +// Copyright © 2022 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 definitions + +import ( + "context" + + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly/pkg/core" +) + +func (bm *definitionSender) DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error { + ffi.ID = fftypes.NewUUID() + ffi.Namespace = bm.namespace + for _, method := range ffi.Methods { + method.ID = fftypes.NewUUID() + } + for _, event := range ffi.Events { + event.ID = fftypes.NewUUID() + } + + if bm.multiparty { + if err := bm.contracts.ResolveFFI(ctx, ffi); err != nil { + return err + } + + msg, err := bm.sendDefinitionDefault(ctx, ffi, core.SystemTagDefineFFI, waitConfirm) + if msg != nil { + ffi.Message = msg.Header.ID + } + return err + } + + return fakeBatch(ctx, func(ctx context.Context, state *core.BatchState) (HandlerResult, error) { + return bm.handler.handleFFIDefinition(ctx, state, ffi, nil) + }) +} + +func (bm *definitionSender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error { + api.ID = fftypes.NewUUID() + api.Namespace = bm.namespace + + if bm.multiparty { + if err := bm.contracts.ResolveContractAPI(ctx, httpServerURL, api); err != nil { + return err + } + + msg, err := bm.sendDefinitionDefault(ctx, api, core.SystemTagDefineContractAPI, waitConfirm) + if msg != nil { + api.Message = msg.Header.ID + } + return err + } + + return fakeBatch(ctx, func(ctx context.Context, state *core.BatchState) (HandlerResult, error) { + return bm.handler.handleContractAPIDefinition(ctx, state, httpServerURL, api, nil) + }) +} diff --git a/internal/definitions/sender_contracts_test.go b/internal/definitions/sender_contracts_test.go new file mode 100644 index 0000000000..1cb017b42d --- /dev/null +++ b/internal/definitions/sender_contracts_test.go @@ -0,0 +1,196 @@ +// Copyright © 2022 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 definitions + +import ( + "context" + "fmt" + "testing" + + "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" + "github.com/hyperledger/firefly/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestDefineFFIResolveFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + ffi := &core.FFI{ + Methods: []*core.FFIMethod{{}}, + Events: []*core.FFIEvent{{}}, + } + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop")) + + err := ds.DefineFFI(context.Background(), ffi, false) + assert.EqualError(t, err, "pop") + + mcm.AssertExpectations(t) +} + +func TestDefineFFIFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + ffi := &core.FFI{} + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) + + mim := ds.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) + + err := ds.DefineFFI(context.Background(), ffi, false) + assert.EqualError(t, err, "pop") + + mcm.AssertExpectations(t) + mim.AssertExpectations(t) +} + +func TestDefineFFIOk(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + ffi := &core.FFI{} + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) + + mim := ds.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(nil) + + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + err := ds.DefineFFI(context.Background(), ffi, false) + assert.NoError(t, err) + + mcm.AssertExpectations(t) + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestDefineFFINonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + dh := ds.handler + + ffi := &core.FFI{} + + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop")) + + err := ds.DefineFFI(context.Background(), ffi, false) + assert.Regexp(t, "FF10403", err) + + mcm.AssertExpectations(t) +} + +func TestDefineContractAPIResolveFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + url := "http://firefly" + api := &core.ContractAPI{} + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), url, api).Return(fmt.Errorf("pop")) + + err := ds.DefineContractAPI(context.Background(), url, api, false) + assert.EqualError(t, err, "pop") + + mcm.AssertExpectations(t) +} + +func TestDefineContractAPIFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + url := "http://firefly" + api := &core.ContractAPI{} + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), url, api).Return(nil) + + mim := ds.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(fmt.Errorf("pop")) + + err := ds.DefineContractAPI(context.Background(), url, api, false) + assert.EqualError(t, err, "pop") + + mcm.AssertExpectations(t) + mim.AssertExpectations(t) +} + +func TestDefineContractAPIOk(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = true + + url := "http://firefly" + api := &core.ContractAPI{} + + mcm := ds.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), url, api).Return(nil) + + mim := ds.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(nil) + + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + err := ds.DefineContractAPI(context.Background(), url, api, false) + assert.NoError(t, err) + + mcm.AssertExpectations(t) + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestDefineContractAPINonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + dh := ds.handler + + url := "http://firefly" + api := &core.ContractAPI{} + + mcm := dh.contracts.(*contractmocks.Manager) + mcm.On("ResolveContractAPI", context.Background(), url, api).Return(fmt.Errorf("pop")) + + err := ds.DefineContractAPI(context.Background(), url, api, false) + assert.Regexp(t, "FF10403", err) + + mcm.AssertExpectations(t) +} diff --git a/internal/broadcast/datatype.go b/internal/definitions/sender_datatype.go similarity index 59% rename from internal/broadcast/datatype.go rename to internal/definitions/sender_datatype.go index 159731bdfb..4d26cd3971 100644 --- a/internal/broadcast/datatype.go +++ b/internal/definitions/sender_datatype.go @@ -14,17 +14,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package broadcast +package definitions import ( "context" "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/pkg/core" ) -func (bm *broadcastManager) BroadcastDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) (*core.Message, error) { - +func (bm *definitionSender) DefineDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) error { // Validate the input data definition data datatype.ID = fftypes.NewUUID() datatype.Created = fftypes.Now() @@ -32,18 +33,23 @@ func (bm *broadcastManager) BroadcastDatatype(ctx context.Context, datatype *cor if datatype.Validator == "" { datatype.Validator = core.ValidatorTypeJSON } - if err := datatype.Validate(ctx, false); err != nil { - return nil, err - } datatype.Hash = datatype.Value.Hash() - // Verify the data type is now all valid, before we broadcast it - if err := bm.data.CheckDatatype(ctx, datatype); err != nil { - return nil, err - } - msg, err := bm.BroadcastDefinitionAsNode(ctx, datatype, core.SystemTagDefineDatatype, waitConfirm) - if msg != nil { - datatype.Message = msg.Header.ID + if bm.multiparty { + if err := datatype.Validate(ctx, false); err != nil { + return err + } + // Verify the data type is now all valid, before we broadcast it + if err := bm.data.CheckDatatype(ctx, datatype); err != nil { + return err + } + + msg, err := bm.sendDefinitionDefault(ctx, datatype, core.SystemTagDefineDatatype, waitConfirm) + if msg != nil { + datatype.Message = msg.Header.ID + } + return err } - return msg, err + + return i18n.NewError(ctx, coremsgs.MsgActionNotSupported) } diff --git a/internal/broadcast/datatype_test.go b/internal/definitions/sender_datatype_test.go similarity index 59% rename from internal/broadcast/datatype_test.go rename to internal/definitions/sender_datatype_test.go index 7d98afba61..bca20dfa6d 100644 --- a/internal/broadcast/datatype_test.go +++ b/internal/definitions/sender_datatype_test.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package broadcast +package definitions import ( "context" @@ -22,93 +22,81 @@ import ( "testing" "github.com/hyperledger/firefly-common/pkg/fftypes" - "github.com/hyperledger/firefly/mocks/databasemocks" + "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/core" - "github.com/hyperledger/firefly/pkg/database" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) -func TestBroadcastDatatypeBadType(t *testing.T) { - bm, cancel := newTestBroadcast(t) +func TestDefineDatatypeBadType(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) defer cancel() - _, err := bm.BroadcastDatatype(context.Background(), &core.Datatype{ + ds.multiparty = true + err := ds.DefineDatatype(context.Background(), &core.Datatype{ Validator: core.ValidatorType("wrong"), }, false) assert.Regexp(t, "FF00111.*validator", err) } func TestBroadcastDatatypeBadValue(t *testing.T) { - bm, cancel := newTestBroadcast(t) + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdm := bm.data.(*datamocks.Manager) + ds.multiparty = true + mdm := ds.data.(*datamocks.Manager) mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(nil) - mim := bm.identity.(*identitymanagermocks.Manager) + mim := ds.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - _, err := bm.BroadcastDatatype(context.Background(), &core.Datatype{ + err := ds.DefineDatatype(context.Background(), &core.Datatype{ Namespace: "ns1", Name: "ent1", Version: "0.0.1", Value: fftypes.JSONAnyPtr(`!unparsable`), }, false) assert.Regexp(t, "FF10137.*value", err) -} - -func TestBroadcastUpsertFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) - defer cancel() - mdm := bm.data.(*datamocks.Manager) - mim := bm.identity.(*identitymanagermocks.Manager) - - mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - mdm.On("WriteNewMessage", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(nil) - - _, err := bm.BroadcastDatatype(context.Background(), &core.Datatype{ - Namespace: "ns1", - Name: "ent1", - Version: "0.0.1", - Value: fftypes.JSONAnyPtr(`{"some": "data"}`), - }, false) - assert.EqualError(t, err, "pop") - mim.AssertExpectations(t) mdm.AssertExpectations(t) + mim.AssertExpectations(t) } -func TestBroadcastDatatypeInvalid(t *testing.T) { - bm, cancel := newTestBroadcast(t) +func TestDefineDatatypeInvalid(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdi := bm.database.(*databasemocks.Plugin) - mdm := bm.data.(*datamocks.Manager) - mim := bm.identity.(*identitymanagermocks.Manager) + ds.multiparty = true + mdm := ds.data.(*datamocks.Manager) + mim := ds.identity.(*identitymanagermocks.Manager) mim.On("ResolveInputIdentity", mock.Anything, mock.Anything).Return(nil) - mdi.On("UpsertData", mock.Anything, mock.Anything, database.UpsertOptimizationNew).Return(nil) mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - _, err := bm.BroadcastDatatype(context.Background(), &core.Datatype{ + err := ds.DefineDatatype(context.Background(), &core.Datatype{ Namespace: "ns1", Name: "ent1", Version: "0.0.1", Value: fftypes.JSONAnyPtr(`{"some": "data"}`), }, false) assert.EqualError(t, err, "pop") + + mdm.AssertExpectations(t) } func TestBroadcastOk(t *testing.T) { - bm, cancel := newTestBroadcast(t) + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdm := bm.data.(*datamocks.Manager) - mim := bm.identity.(*identitymanagermocks.Manager) + ds.multiparty = true + mdm := ds.data.(*datamocks.Manager) + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) mdm.On("CheckDatatype", mock.Anything, mock.Anything).Return(nil) - mdm.On("WriteNewMessage", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) - _, err := bm.BroadcastDatatype(context.Background(), &core.Datatype{ + err := ds.DefineDatatype(context.Background(), &core.Datatype{ Namespace: "ns1", Name: "ent1", Version: "0.0.1", @@ -118,4 +106,20 @@ func TestBroadcastOk(t *testing.T) { mdm.AssertExpectations(t) mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestDefineDatatypeNonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + ds.multiparty = false + + err := ds.DefineDatatype(context.Background(), &core.Datatype{ + Namespace: "ns1", + Name: "ent1", + Version: "0.0.1", + Value: fftypes.JSONAnyPtr(`{"some": "data"}`), + }, false) + assert.Regexp(t, "FF10414", err) } diff --git a/internal/definitions/sender_identity.go b/internal/definitions/sender_identity.go new file mode 100644 index 0000000000..757f07b1ed --- /dev/null +++ b/internal/definitions/sender_identity.go @@ -0,0 +1,75 @@ +// Copyright © 2022 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 definitions + +import ( + "context" + + "github.com/hyperledger/firefly/internal/identity" + "github.com/hyperledger/firefly/pkg/core" +) + +// ClaimIdentity is a special form of CreateDefinition where the signing identity does not need to have been pre-registered +// The blockchain "key" will be normalized, but the "author" will pass through unchecked +func (bm *definitionSender) ClaimIdentity(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, parentSigner *core.SignerRef, waitConfirm bool) error { + if bm.multiparty { + var err error + signingIdentity.Key, err = bm.identity.NormalizeSigningKey(ctx, signingIdentity.Key, identity.KeyNormalizationBlockchainPlugin) + if err != nil { + return err + } + + claimMsg, err := bm.sendDefinitionCommon(ctx, def, signingIdentity, core.SystemTagIdentityClaim, waitConfirm) + if err != nil { + return err + } + def.Identity.Messages.Claim = claimMsg.Header.ID + + // Send the verification if one is required. + if parentSigner != nil { + verifyMsg, err := bm.sendDefinition(ctx, &core.IdentityVerification{ + Claim: core.MessageRef{ + ID: claimMsg.Header.ID, + Hash: claimMsg.Hash, + }, + Identity: def.Identity.IdentityBase, + }, parentSigner, core.SystemTagIdentityVerification, false) + if err != nil { + return err + } + def.Identity.Messages.Verification = verifyMsg.Header.ID + } + + return nil + } + + return fakeBatch(ctx, func(ctx context.Context, state *core.BatchState) (HandlerResult, error) { + return bm.handler.handleIdentityClaim(ctx, state, &identityMsgInfo{SignerRef: *signingIdentity}, def) + }) +} + +func (bm *definitionSender) UpdateIdentity(ctx context.Context, identity *core.Identity, def *core.IdentityUpdate, signingIdentity *core.SignerRef, waitConfirm bool) error { + if bm.multiparty { + updateMsg, err := bm.sendDefinition(ctx, def, signingIdentity, core.SystemTagIdentityUpdate, waitConfirm) + identity.Messages.Update = updateMsg.Header.ID + return err + } + + return fakeBatch(ctx, func(ctx context.Context, state *core.BatchState) (HandlerResult, error) { + return bm.handler.handleIdentityUpdate(ctx, state, &identityUpdateMsgInfo{}, def) + }) +} diff --git a/internal/definitions/sender_identity_test.go b/internal/definitions/sender_identity_test.go new file mode 100644 index 0000000000..a239a2c9b6 --- /dev/null +++ b/internal/definitions/sender_identity_test.go @@ -0,0 +1,234 @@ +// 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 definitions + +import ( + "fmt" + "testing" + + "github.com/hyperledger/firefly/internal/identity" + "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" + "github.com/hyperledger/firefly/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestClaimIdentity(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + + mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("SendAndWait", mock.Anything).Return(nil) + + ds.multiparty = true + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, nil, true) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestClaimIdentityFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + + mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("SendAndWait", mock.Anything).Return(fmt.Errorf("pop")) + + ds.multiparty = true + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, nil, true) + assert.EqualError(t, err, "pop") + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestClaimIdentityFailKey(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + + mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", fmt.Errorf("pop")) + + ds.multiparty = true + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, nil, true) + assert.EqualError(t, err, "pop") + + mim.AssertExpectations(t) +} + +func TestClaimIdentityChild(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms1 := &sysmessagingmocks.MessageSender{} + mms2 := &sysmessagingmocks.MessageSender{} + + mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms1).Once() + mbm.On("NewBroadcast", mock.Anything).Return(mms2).Once() + mms1.On("SendAndWait", mock.Anything).Return(nil) + mms2.On("Send", mock.Anything).Return(nil) + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.MatchedBy(func(signer *core.SignerRef) bool { + return signer.Key == "0x2345" + })).Return(nil) + + ds.multiparty = true + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, &core.SignerRef{ + Key: "0x2345", + }, true) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms1.AssertExpectations(t) +} + +func TestClaimIdentityChildFail(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms1 := &sysmessagingmocks.MessageSender{} + mms2 := &sysmessagingmocks.MessageSender{} + + mim.On("NormalizeSigningKey", mock.Anything, "0x1234", identity.KeyNormalizationBlockchainPlugin).Return("", nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms1).Once() + mbm.On("NewBroadcast", mock.Anything).Return(mms2).Once() + mms1.On("SendAndWait", mock.Anything).Return(nil) + mms2.On("Send", mock.Anything).Return(fmt.Errorf("pop")) + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.MatchedBy(func(signer *core.SignerRef) bool { + return signer.Key == "0x2345" + })).Return(nil) + + ds.multiparty = true + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, &core.SignerRef{ + Key: "0x2345", + }, true) + assert.EqualError(t, err, "pop") + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms1.AssertExpectations(t) +} + +func TestClaimIdentityNonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + dh := ds.handler + + mim := dh.identity.(*identitymanagermocks.Manager) + mim.On("VerifyIdentityChain", mock.Anything, mock.AnythingOfType("*core.Identity")).Return(nil, false, fmt.Errorf("pop")) + + ds.multiparty = false + + err := ds.ClaimIdentity(ds.ctx, &core.IdentityClaim{ + Identity: &core.Identity{}, + }, &core.SignerRef{ + Key: "0x1234", + }, nil, true) + assert.NoError(t, err) + + mim.AssertExpectations(t) +} + +func TestUpdateIdentity(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", mock.Anything).Return(nil) + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.MatchedBy(func(signer *core.SignerRef) bool { + return signer.Key == "0x1234" + })).Return(nil) + + ds.multiparty = true + + err := ds.UpdateIdentity(ds.ctx, &core.Identity{}, &core.IdentityUpdate{ + Identity: core.IdentityBase{}, + Updates: core.IdentityProfile{}, + }, &core.SignerRef{ + Key: "0x1234", + }, false) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestUpdateIdentityNonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + ds.multiparty = false + + err := ds.UpdateIdentity(ds.ctx, &core.Identity{}, &core.IdentityUpdate{ + Identity: core.IdentityBase{}, + Updates: core.IdentityProfile{}, + }, &core.SignerRef{ + Key: "0x1234", + }, false) + assert.Regexp(t, "FF10403", err) +} diff --git a/internal/definitions/sender_test.go b/internal/definitions/sender_test.go new file mode 100644 index 0000000000..a53a4e617a --- /dev/null +++ b/internal/definitions/sender_test.go @@ -0,0 +1,121 @@ +// 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 definitions + +import ( + "context" + "fmt" + "testing" + + "github.com/hyperledger/firefly/mocks/assetmocks" + "github.com/hyperledger/firefly/mocks/blockchainmocks" + "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/mocks/databasemocks" + "github.com/hyperledger/firefly/mocks/dataexchangemocks" + "github.com/hyperledger/firefly/mocks/datamocks" + "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" + "github.com/hyperledger/firefly/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func newTestDefinitionSender(t *testing.T) (*definitionSender, func()) { + mdi := &databasemocks.Plugin{} + mbi := &blockchainmocks.Plugin{} + mdx := &dataexchangemocks.Plugin{} + mbm := &broadcastmocks.Manager{} + mim := &identitymanagermocks.Manager{} + mdm := &datamocks.Manager{} + mam := &assetmocks.Manager{} + mcm := &contractmocks.Manager{} + + ctx, cancel := context.WithCancel(context.Background()) + ds, _, err := NewDefinitionSender(ctx, "ns1", false, mdi, mbi, mdx, mbm, mim, mdm, mam, mcm) + assert.NoError(t, err) + return ds.(*definitionSender), cancel +} + +func TestInitSenderFail(t *testing.T) { + _, _, err := NewDefinitionSender(context.Background(), "", false, nil, nil, nil, nil, nil, nil, nil, nil) + assert.Regexp(t, "FF10128", err) +} + +func TestName(t *testing.T) { + bm, cancel := newTestDefinitionSender(t) + defer cancel() + assert.Equal(t, "DefinitionSender", bm.Name()) +} + +func TestCreateDefinitionConfirm(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("SendAndWait", mock.Anything).Return(nil) + + ds.multiparty = true + _, err := ds.sendDefinitionDefault(ds.ctx, &core.Namespace{}, core.SystemTagDefineNamespace, true) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestCreateDatatypeDefinitionAsNodeConfirm(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} + + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("SendAndWait", mock.Anything).Return(nil) + + ds.multiparty = true + + _, err := ds.sendDefinitionDefault(ds.ctx, &core.Datatype{}, core.SystemTagDefineNamespace, true) + assert.NoError(t, err) + + mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) +} + +func TestCreateDefinitionBadIdentity(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) + defer cancel() + + ds.multiparty = true + + mim := ds.identity.(*identitymanagermocks.Manager) + mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) + _, err := ds.sendDefinition(ds.ctx, &core.Namespace{}, &core.SignerRef{ + Author: "wrong", + Key: "wrong", + }, core.SystemTagDefineNamespace, false) + assert.Regexp(t, "pop", err) +} diff --git a/internal/broadcast/tokenpool.go b/internal/definitions/sender_tokenpool.go similarity index 55% rename from internal/broadcast/tokenpool.go rename to internal/definitions/sender_tokenpool.go index 7b62e9d901..0889002a3c 100644 --- a/internal/broadcast/tokenpool.go +++ b/internal/definitions/sender_tokenpool.go @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package broadcast +package definitions import ( "context" @@ -22,14 +22,20 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (bm *broadcastManager) BroadcastTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) (msg *core.Message, err error) { - if err := pool.Pool.Validate(ctx); err != nil { - return nil, err - } +func (bm *definitionSender) DefineTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) error { + if bm.multiparty { + if err := pool.Pool.Validate(ctx); err != nil { + return err + } - msg, err = bm.BroadcastDefinitionAsNode(ctx, pool, core.SystemTagDefinePool, waitConfirm) - if msg != nil { - pool.Pool.Message = msg.Header.ID + msg, err := bm.sendDefinitionDefault(ctx, pool, core.SystemTagDefinePool, waitConfirm) + if msg != nil { + pool.Pool.Message = msg.Header.ID + } + return err } - return msg, err + + return fakeBatch(ctx, func(ctx context.Context, state *core.BatchState) (HandlerResult, error) { + return bm.handler.handleTokenPoolDefinition(ctx, state, pool.Pool) + }) } diff --git a/internal/broadcast/tokenpool_test.go b/internal/definitions/sender_tokenpool_test.go similarity index 63% rename from internal/broadcast/tokenpool_test.go rename to internal/definitions/sender_tokenpool_test.go index aea276d411..4e7ab01a15 100644 --- a/internal/broadcast/tokenpool_test.go +++ b/internal/definitions/sender_tokenpool_test.go @@ -14,27 +14,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -package broadcast +package definitions import ( "context" - "fmt" "testing" "github.com/hyperledger/firefly-common/pkg/fftypes" - "github.com/hyperledger/firefly/mocks/databasemocks" + "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/datamocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" + "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestBroadcastTokenPoolInvalid(t *testing.T) { - bm, cancel := newTestBroadcast(t) + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdi := bm.database.(*databasemocks.Plugin) - mdm := bm.data.(*datamocks.Manager) + ds.multiparty = true + + mdm := ds.data.(*datamocks.Manager) pool := &core.TokenPoolAnnouncement{ Pool: &core.TokenPool{ @@ -47,45 +48,45 @@ func TestBroadcastTokenPoolInvalid(t *testing.T) { }, } - _, err := bm.BroadcastTokenPool(context.Background(), pool, false) + err := ds.DefineTokenPool(context.Background(), pool, false) assert.Regexp(t, "FF00140", err) - mdi.AssertExpectations(t) mdm.AssertExpectations(t) } -func TestBroadcastTokenPoolBroadcastFail(t *testing.T) { - bm, cancel := newTestBroadcast(t) +func TestBroadcastTokenPoolInvalidNonMultiparty(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdm := bm.data.(*datamocks.Manager) - mim := bm.identity.(*identitymanagermocks.Manager) + ds.multiparty = false + + mdm := ds.data.(*datamocks.Manager) pool := &core.TokenPoolAnnouncement{ Pool: &core.TokenPool{ ID: fftypes.NewUUID(), - Namespace: "ns1", - Name: "mypool", + Namespace: "", + Name: "", Type: core.TokenTypeNonFungible, Locator: "N1", Symbol: "COIN", }, } - mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - mdm.On("WriteNewMessage", mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - - _, err := bm.BroadcastTokenPool(context.Background(), pool, false) - assert.EqualError(t, err, "pop") + err := ds.DefineTokenPool(context.Background(), pool, false) + assert.Regexp(t, "FF00140", err) mdm.AssertExpectations(t) - mim.AssertExpectations(t) } -func TestBroadcastTokenPoolOk(t *testing.T) { - bm, cancel := newTestBroadcast(t) +func TestDefineTokenPoolOk(t *testing.T) { + ds, cancel := newTestDefinitionSender(t) defer cancel() - mdm := bm.data.(*datamocks.Manager) - mim := bm.identity.(*identitymanagermocks.Manager) + ds.multiparty = true + + mdm := ds.data.(*datamocks.Manager) + mim := ds.identity.(*identitymanagermocks.Manager) + mbm := ds.broadcast.(*broadcastmocks.Manager) + mms := &sysmessagingmocks.MessageSender{} pool := &core.TokenPoolAnnouncement{ Pool: &core.TokenPool{ @@ -99,11 +100,14 @@ func TestBroadcastTokenPoolOk(t *testing.T) { } mim.On("ResolveInputSigningIdentity", mock.Anything, mock.Anything).Return(nil) - mdm.On("WriteNewMessage", mock.Anything, mock.Anything).Return(nil) + mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) - _, err := bm.BroadcastTokenPool(context.Background(), pool, false) + err := ds.DefineTokenPool(context.Background(), pool, false) assert.NoError(t, err) mdm.AssertExpectations(t) mim.AssertExpectations(t) + mbm.AssertExpectations(t) + mms.AssertExpectations(t) } diff --git a/internal/events/aggregator.go b/internal/events/aggregator.go index de3a771c9d..2d123bd4cf 100644 --- a/internal/events/aggregator.go +++ b/internal/events/aggregator.go @@ -48,7 +48,7 @@ type aggregator struct { namespace string database database.Plugin messaging privatemessaging.Manager - definitions definitions.DefinitionHandler + definitions definitions.Handler identity identity.Manager data data.Manager eventPoller *eventPoller @@ -65,7 +65,7 @@ type batchCacheEntry struct { manifest *core.BatchManifest } -func newAggregator(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, pm privatemessaging.Manager, sh definitions.DefinitionHandler, im identity.Manager, dm data.Manager, en *eventNotifier, mm metrics.Manager) *aggregator { +func newAggregator(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, pm privatemessaging.Manager, sh definitions.Handler, im identity.Manager, dm data.Manager, en *eventNotifier, mm metrics.Manager) *aggregator { batchSize := config.GetInt(coreconfig.EventAggregatorBatchSize) ag := &aggregator{ ctx: log.WithLogField(ctx, "role", "aggregator"), @@ -453,19 +453,19 @@ func (ag *aggregator) processMessage(ctx context.Context, manifest *core.BatchMa l.Errorf("Message '%s' in batch '%s' has invalid pin at index %d: '%s'", msg.Header.ID, manifest.ID, i, pinStr) return nil } - nextPin, err := state.CheckMaskedContextReady(ctx, msg, msg.Header.Topics[i], pin.Sequence, &msgContext, nonceStr) + nextPin, err := state.checkMaskedContextReady(ctx, msg, msg.Header.Topics[i], pin.Sequence, &msgContext, nonceStr) if err != nil || nextPin == nil { return err } nextPins = append(nextPins, nextPin) } } else { - for i, topic := range msg.Header.Topics { + for _, topic := range msg.Header.Topics { h := sha256.New() h.Write([]byte(topic)) msgContext := fftypes.HashResult(h) unmaskedContexts = append(unmaskedContexts, msgContext) - ready, err := state.CheckUnmaskedContextReady(ctx, msgContext, msg, msg.Header.Topics[i], pin.Sequence) + ready, err := state.checkUnmaskedContextReady(ctx, msgContext, msg, pin.Sequence) if err != nil || !ready { return err } @@ -490,7 +490,7 @@ func (ag *aggregator) processMessage(ctx context.Context, manifest *core.BatchMa for _, np := range nextPins { np.IncrementNextPin(ctx) } - state.MarkMessageDispatched(ctx, manifest.ID, msg, msgBaseIndex, newState) + state.markMessageDispatched(manifest.ID, msg, msgBaseIndex, newState) } else { for _, unmaskedContext := range unmaskedContexts { state.SetContextBlockedBy(ctx, *unmaskedContext, pin.Sequence) @@ -534,7 +534,7 @@ func (ag *aggregator) attemptMessageDispatch(ctx context.Context, msg *core.Mess case msg.Header.Type == core.MessageTypeDefinition: // We handle definition events in-line on the aggregator, as it would be confusing for apps to be // dispatched subsequent events before we have processed the definition events they depend on. - handlerResult, err := ag.definitions.HandleDefinitionBroadcast(ctx, state, msg, data, tx) + handlerResult, err := ag.definitions.HandleDefinitionBroadcast(ctx, &state.BatchState, msg, data, tx) log.L(ctx).Infof("Result of definition broadcast '%s' [%s]: %s", msg.Header.Tag, msg.Header.ID, handlerResult.Action) if handlerResult.Action == definitions.ActionRetry { return "", false, err @@ -561,7 +561,7 @@ func (ag *aggregator) attemptMessageDispatch(ctx context.Context, msg *core.Mess newState = core.MessageStateConfirmed eventType := core.EventTypeMessageConfirmed if valid { - state.pendingConfirms[*msg.Header.ID] = msg + state.AddPendingConfirm(msg.Header.ID, msg) } else { newState = core.MessageStateRejected eventType = core.EventTypeMessageRejected diff --git a/internal/events/aggregator_batch_state.go b/internal/events/aggregator_batch_state.go index 3dc54708a3..5448c49f56 100644 --- a/internal/events/aggregator_batch_state.go +++ b/internal/events/aggregator_batch_state.go @@ -40,10 +40,9 @@ func newBatchState(ag *aggregator) *batchState { maskedContexts: make(map[fftypes.Bytes32]*nextPinGroupState), unmaskedContexts: make(map[fftypes.Bytes32]*contextState), dispatchedMessages: make([]*dispatchedMessage, 0), - pendingConfirms: make(map[fftypes.UUID]*core.Message), - - PreFinalize: make([]func(ctx context.Context) error, 0), - Finalize: make([]func(ctx context.Context) error, 0), + BatchState: core.BatchState{ + PendingConfirms: make(map[fftypes.UUID]*core.Message), + }, } } @@ -96,6 +95,8 @@ type dispatchedMessage struct { // Runs in a database operation group/tranaction, which will be the same as phase (1) if there // are no pre-finalize handlers registered. type batchState struct { + core.BatchState + namespace string database database.Plugin messaging privatemessaging.Manager @@ -103,67 +104,26 @@ type batchState struct { maskedContexts map[fftypes.Bytes32]*nextPinGroupState unmaskedContexts map[fftypes.Bytes32]*contextState dispatchedMessages []*dispatchedMessage - pendingConfirms map[fftypes.UUID]*core.Message - confirmedDIDClaims []string - - // PreFinalize callbacks may perform blocking actions (possibly to an external connector) - // - Will execute after all batch messages have been processed - // - Will execute outside database RunAsGroup - // - If any PreFinalize callback errors out, batch will be aborted and retried - PreFinalize []func(ctx context.Context) error - - // Finalize callbacks may perform final, non-idempotent database operations (such as inserting Events) - // - Will execute after all batch messages have been processed and any PreFinalize callbacks have succeeded - // - Will execute inside database RunAsGroup - // - If any Finalize callback errors out, batch will be aborted and retried (small chance of duplicate execution here) - Finalize []func(ctx context.Context) error -} - -func (bs *batchState) AddPreFinalize(action func(ctx context.Context) error) { - if action != nil { - bs.PreFinalize = append(bs.PreFinalize, action) - } -} - -func (bs *batchState) AddFinalize(action func(ctx context.Context) error) { - if action != nil { - bs.Finalize = append(bs.Finalize, action) - } -} - -func (bs *batchState) GetPendingConfirm() map[fftypes.UUID]*core.Message { - return bs.pendingConfirms } func (bs *batchState) RunPreFinalize(ctx context.Context) error { - for _, action := range bs.PreFinalize { - if err := action(ctx); err != nil { - return err - } - } - return nil + return bs.BatchState.RunPreFinalize(ctx) } func (bs *batchState) RunFinalize(ctx context.Context) error { - for _, action := range bs.Finalize { - if err := action(ctx); err != nil { - return err - } + if err := bs.BatchState.RunFinalize(ctx); err != nil { + return err } return bs.flushPins(ctx) } -func (bs *batchState) DIDClaimConfirmed(did string) { - bs.confirmedDIDClaims = append(bs.confirmedDIDClaims, did) -} - func (bs *batchState) queueRewinds(ag *aggregator) { - for _, did := range bs.confirmedDIDClaims { + for _, did := range bs.ConfirmedDIDClaims { ag.queueDIDRewind(did) } } -func (bs *batchState) CheckUnmaskedContextReady(ctx context.Context, contextUnmasked *fftypes.Bytes32, msg *core.Message, topic string, firstMsgPinSequence int64) (bool, error) { +func (bs *batchState) checkUnmaskedContextReady(ctx context.Context, contextUnmasked *fftypes.Bytes32, msg *core.Message, firstMsgPinSequence int64) (bool, error) { ucs, found := bs.unmaskedContexts[*contextUnmasked] if !found { @@ -195,7 +155,7 @@ func (bs *batchState) CheckUnmaskedContextReady(ctx context.Context, contextUnma } -func (bs *batchState) CheckMaskedContextReady(ctx context.Context, msg *core.Message, topic string, firstMsgPinSequence int64, pin *fftypes.Bytes32, nonceStr string) (*nextPinState, error) { +func (bs *batchState) checkMaskedContextReady(ctx context.Context, msg *core.Message, topic string, firstMsgPinSequence int64, pin *fftypes.Bytes32, nonceStr string) (*nextPinState, error) { l := log.L(ctx) // For masked pins, we can only process if: @@ -240,7 +200,7 @@ func (bs *batchState) CheckMaskedContextReady(ctx context.Context, msg *core.Mes }, err } -func (bs *batchState) MarkMessageDispatched(ctx context.Context, batchID *fftypes.UUID, msg *core.Message, msgBaseIndex int64, newState core.MessageState) { +func (bs *batchState) markMessageDispatched(batchID *fftypes.UUID, msg *core.Message, msgBaseIndex int64, newState core.MessageState) { bs.dispatchedMessages = append(bs.dispatchedMessages, &dispatchedMessage{ batchID: batchID, msgID: msg.Header.ID, diff --git a/internal/events/aggregator_batch_state_test.go b/internal/events/aggregator_batch_state_test.go index e0f267626e..7735e55ed6 100644 --- a/internal/events/aggregator_batch_state_test.go +++ b/internal/events/aggregator_batch_state_test.go @@ -36,7 +36,7 @@ func TestFlushPinsFailUpdatePins(t *testing.T) { mdi := ag.database.(*databasemocks.Plugin) mdi.On("UpdatePins", ag.ctx, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) - bs.MarkMessageDispatched(ag.ctx, fftypes.NewUUID(), &core.Message{ + bs.markMessageDispatched(fftypes.NewUUID(), &core.Message{ Header: core.MessageHeader{ ID: fftypes.NewUUID(), Topics: core.FFStringArray{"topic1"}, @@ -60,7 +60,7 @@ func TestFlushPinsFailUpdateMessages(t *testing.T) { mdm := ag.data.(*datamocks.Manager) mdm.On("UpdateMessageStateIfCached", ag.ctx, msgID, core.MessageStateConfirmed, mock.Anything).Return() - bs.MarkMessageDispatched(ag.ctx, fftypes.NewUUID(), &core.Message{ + bs.markMessageDispatched(fftypes.NewUUID(), &core.Message{ Header: core.MessageHeader{ ID: msgID, Topics: core.FFStringArray{"topic1"}, @@ -80,7 +80,7 @@ func TestSetContextBlockedByNoState(t *testing.T) { unmaskedContext := fftypes.NewRandB32() bs.SetContextBlockedBy(ag.ctx, *unmaskedContext, 10) - ready, err := bs.CheckUnmaskedContextReady(ag.ctx, unmaskedContext, &core.Message{}, "topic1", 1) + ready, err := bs.checkUnmaskedContextReady(ag.ctx, unmaskedContext, &core.Message{}, 1) assert.NoError(t, err) assert.False(t, ready) } diff --git a/internal/events/aggregator_test.go b/internal/events/aggregator_test.go index 5cc20c1a94..886185c72f 100644 --- a/internal/events/aggregator_test.go +++ b/internal/events/aggregator_test.go @@ -49,7 +49,7 @@ func newTestAggregatorCommon(metrics bool) (*aggregator, func()) { mdi := &databasemocks.Plugin{} mdm := &datamocks.Manager{} mpm := &privatemessagingmocks.Manager{} - msh := &definitionsmocks.DefinitionHandler{} + msh := &definitionsmocks.Handler{} mim := &identitymanagermocks.Manager{} mmi := &metricsmocks.Manager{} mbi := &blockchainmocks.Plugin{} @@ -261,7 +261,7 @@ func TestAggregationMaskedZeroNonceMatch(t *testing.T) { err = bs.RunFinalize(ag.ctx) assert.NoError(t, err) - assert.NotNil(t, bs.GetPendingConfirm()[*msgID]) + assert.NotNil(t, bs.PendingConfirms[*msgID]) // Confirm the offset assert.Equal(t, int64(10001), <-ag.eventPoller.offsetCommitted) @@ -1130,7 +1130,7 @@ func TestCheckMaskedContextReadyMismatchedAuthor(t *testing.T) { }, nil, nil) bs := newBatchState(ag) - _, err := bs.CheckMaskedContextReady(ag.ctx, &core.Message{ + _, err := bs.checkMaskedContextReady(ag.ctx, &core.Message{ Header: core.MessageHeader{ ID: fftypes.NewUUID(), Group: fftypes.NewRandB32(), @@ -1516,7 +1516,7 @@ func TestDefinitionBroadcastActionRejectCustomCorrelator(t *testing.T) { mim.On("FindIdentityForVerifier", ag.ctx, mock.Anything, mock.Anything).Return(org1, nil) customCorrelator := fftypes.NewUUID() - msh := ag.definitions.(*definitionsmocks.DefinitionHandler) + msh := ag.definitions.(*definitionsmocks.Handler) msh.On("HandleDefinitionBroadcast", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(definitions.HandlerResult{Action: definitions.ActionReject, CustomCorrelator: customCorrelator}, nil) @@ -1733,7 +1733,7 @@ func TestDefinitionBroadcastActionRetry(t *testing.T) { mim := ag.identity.(*identitymanagermocks.Manager) mim.On("FindIdentityForVerifier", ag.ctx, mock.Anything, mock.Anything).Return(org1, nil) - msh := ag.definitions.(*definitionsmocks.DefinitionHandler) + msh := ag.definitions.(*definitionsmocks.Handler) msh.On("HandleDefinitionBroadcast", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(definitions.HandlerResult{Action: definitions.ActionRetry}, fmt.Errorf("pop")) mdm := ag.data.(*datamocks.Manager) @@ -1799,7 +1799,7 @@ func TestDefinitionBroadcastParkUnregisteredSignerIdentityClaim(t *testing.T) { mim := ag.identity.(*identitymanagermocks.Manager) mim.On("FindIdentityForVerifier", ag.ctx, mock.Anything, mock.Anything).Return(nil, nil) - msh := ag.definitions.(*definitionsmocks.DefinitionHandler) + msh := ag.definitions.(*definitionsmocks.Handler) msh.On("HandleDefinitionBroadcast", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(definitions.HandlerResult{Action: definitions.ActionWait}, nil) newState, valid, err := ag.attemptMessageDispatch(ag.ctx, msg1, nil, nil, &batchState{}, &core.Pin{Signer: "0x12345"}) @@ -1836,7 +1836,7 @@ func TestDefinitionBroadcastActionWait(t *testing.T) { mim := ag.identity.(*identitymanagermocks.Manager) mim.On("FindIdentityForVerifier", ag.ctx, mock.Anything, mock.Anything).Return(org1, nil) - msh := ag.definitions.(*definitionsmocks.DefinitionHandler) + msh := ag.definitions.(*definitionsmocks.Handler) msh.On("HandleDefinitionBroadcast", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(definitions.HandlerResult{Action: definitions.ActionWait}, nil) _, _, err := ag.attemptMessageDispatch(ag.ctx, msg1, nil, nil, &batchState{}, &core.Pin{Signer: "0x12345"}) @@ -2131,7 +2131,7 @@ func TestProcessWithBatchRewindsSuccess(t *testing.T) { } err := ag.processWithBatchState(func(ctx context.Context, actions *batchState) error { - actions.DIDClaimConfirmed("did:firefly:org/test") + actions.AddConfirmedDIDClaim("did:firefly:org/test") return nil }) assert.NoError(t, err) diff --git a/internal/events/batch_pin_complete.go b/internal/events/batch_pin_complete.go index d08d5dbf98..9f6a5e0e23 100644 --- a/internal/events/batch_pin_complete.go +++ b/internal/events/batch_pin_complete.go @@ -32,6 +32,10 @@ import ( // We must block here long enough to get the payload from the sharedstorage, persist the messages in the correct // sequence, and also persist all the data. func (em *eventManager) BatchPinComplete(batchPin *blockchain.BatchPin, signingKey *core.VerifierRef) error { + if em.multiparty == nil { + log.L(em.ctx).Errorf("Ignoring batch pin from non-multiparty network!") + return nil + } if batchPin.TransactionID == nil { log.L(em.ctx).Errorf("Invalid BatchPin transaction - ID is nil") return nil // move on diff --git a/internal/events/batch_pin_complete_test.go b/internal/events/batch_pin_complete_test.go index 7ded6ccb89..4d57d94fe8 100644 --- a/internal/events/batch_pin_complete_test.go +++ b/internal/events/batch_pin_complete_test.go @@ -421,6 +421,26 @@ func TestBatchPinCompleteWrongNamespace(t *testing.T) { assert.NoError(t, err) } +func TestBatchPinCompleteNonMultiparty(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + em.multiparty = nil + + batch := &blockchain.BatchPin{ + Namespace: "ns1", + TransactionID: fftypes.NewUUID(), + Event: blockchain.Event{ + BlockchainTXID: "0x12345", + }, + } + + err := em.BatchPinComplete(batch, &core.VerifierRef{ + Type: core.VerifierTypeEthAddress, + Value: "0x12345", + }) + assert.NoError(t, err) +} + func TestPersistBatchMissingID(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() diff --git a/internal/events/dx_callbacks.go b/internal/events/dx_callbacks.go index 4c8ad85496..d9821f1bf9 100644 --- a/internal/events/dx_callbacks.go +++ b/internal/events/dx_callbacks.go @@ -77,6 +77,10 @@ func (em *eventManager) checkReceivedOffchainIdentity(ctx context.Context, peerI } func (em *eventManager) privateBatchReceived(peerID string, batch *core.Batch, wrapperGroup *core.Group) (manifest string, err error) { + if em.multiparty == nil { + log.L(em.ctx).Errorf("Ignoring private batch from non-multiparty network!") + return "", nil + } // Retry for persistence errors (not validation errors) err = em.retry.Do(em.ctx, "private batch received", func(attempt int) (bool, error) { diff --git a/internal/events/dx_callbacks_test.go b/internal/events/dx_callbacks_test.go index e797236c1e..70f5366c32 100644 --- a/internal/events/dx_callbacks_test.go +++ b/internal/events/dx_callbacks_test.go @@ -254,6 +254,23 @@ func TestMessageReceivedWrongNS(t *testing.T) { } +func TestMessageReceivedNonMultiparty(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + em.multiparty = nil + + _, b := sampleBatchTransfer(t, core.TransactionTypeBatchPin) + + mdx := &dataexchangemocks.Plugin{} + mdx.On("Name").Return("utdx") + + mde := newMessageReceived("peer1", b, "") + em.messageReceived(mdx, mde) + + mde.AssertExpectations(t) + +} + func TestMessageReceiveNodeLookupError(t *testing.T) { em, cancel := newTestEventManager(t) cancel() // to stop retry diff --git a/internal/events/event_dispatcher.go b/internal/events/event_dispatcher.go index 2462146c68..6b8e4bcfdf 100644 --- a/internal/events/event_dispatcher.go +++ b/internal/events/event_dispatcher.go @@ -56,8 +56,8 @@ type eventDispatcher struct { data data.Manager database database.Plugin transport events.Plugin - broadcast broadcast.Manager - messaging privatemessaging.Manager + broadcast broadcast.Manager // optional + messaging privatemessaging.Manager // optional elected bool eventPoller *eventPoller inflight map[fftypes.UUID]*core.Event diff --git a/internal/events/event_manager.go b/internal/events/event_manager.go index 56a7c40012..d7fa66e55c 100644 --- a/internal/events/event_manager.go +++ b/internal/events/event_manager.go @@ -91,19 +91,19 @@ type eventManager struct { ctx context.Context namespace string ni sysmessaging.LocalNodeInfo - sharedstorage sharedstorage.Plugin database database.Plugin txHelper txcommon.Helper identity identity.Manager - definitions definitions.DefinitionHandler + defsender definitions.Sender + defhandler definitions.Handler data data.Manager subManager *subscriptionManager retry retry.Retry aggregator *aggregator - broadcast broadcast.Manager - messaging privatemessaging.Manager + broadcast broadcast.Manager // optional + messaging privatemessaging.Manager // optional assets assets.Manager - sharedDownload shareddownload.Manager + sharedDownload shareddownload.Manager // optional blobReceiver *blobReceiver newEventNotifier *eventNotifier newPinNotifier *eventNotifier @@ -112,11 +112,11 @@ type eventManager struct { metrics metrics.Manager chainListenerCache *ccache.Cache chainListenerCacheTTL time.Duration - multiparty multiparty.Manager + multiparty multiparty.Manager // optional } -func NewEventManager(ctx context.Context, ns string, ni sysmessaging.LocalNodeInfo, si sharedstorage.Plugin, di database.Plugin, bi blockchain.Plugin, im identity.Manager, dh definitions.DefinitionHandler, dm data.Manager, bm broadcast.Manager, pm privatemessaging.Manager, am assets.Manager, sd shareddownload.Manager, mm metrics.Manager, txHelper txcommon.Helper, transports map[string]events.Plugin, mp multiparty.Manager) (EventManager, error) { - if ni == nil || si == nil || di == nil || bi == nil || im == nil || dh == nil || dm == nil || bm == nil || pm == nil || am == nil { +func NewEventManager(ctx context.Context, ns string, ni sysmessaging.LocalNodeInfo, di database.Plugin, bi blockchain.Plugin, im identity.Manager, dh definitions.Handler, dm data.Manager, ds definitions.Sender, bm broadcast.Manager, pm privatemessaging.Manager, am assets.Manager, sd shareddownload.Manager, mm metrics.Manager, txHelper txcommon.Helper, transports map[string]events.Plugin, mp multiparty.Manager) (EventManager, error) { + if ni == nil || di == nil || bi == nil || im == nil || dh == nil || dm == nil || ds == nil || am == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "EventManager") } newPinNotifier := newEventNotifier(ctx, "pins") @@ -125,11 +125,11 @@ func NewEventManager(ctx context.Context, ns string, ni sysmessaging.LocalNodeIn ctx: log.WithLogField(ctx, "role", "event-manager"), namespace: ns, ni: ni, - sharedstorage: si, database: di, txHelper: txHelper, identity: im, - definitions: dh, + defsender: ds, + defhandler: dh, data: dm, broadcast: bm, messaging: pm, diff --git a/internal/events/event_manager_test.go b/internal/events/event_manager_test.go index c06d684713..190989e8bb 100644 --- a/internal/events/event_manager_test.go +++ b/internal/events/event_manager_test.go @@ -38,7 +38,6 @@ import ( "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/mocks/privatemessagingmocks" "github.com/hyperledger/firefly/mocks/shareddownloadmocks" - "github.com/hyperledger/firefly/mocks/sharedstoragemocks" "github.com/hyperledger/firefly/mocks/sysmessagingmocks" "github.com/hyperledger/firefly/mocks/txcommonmocks" "github.com/hyperledger/firefly/pkg/core" @@ -72,10 +71,10 @@ func newTestEventManagerCommon(t *testing.T, metrics, dbconcurrency bool) (*even mdi := &databasemocks.Plugin{} mbi := &blockchainmocks.Plugin{} mim := &identitymanagermocks.Manager{} - mpi := &sharedstoragemocks.Plugin{} met := &eventsmocks.Plugin{} mdm := &datamocks.Manager{} - msh := &definitionsmocks.DefinitionHandler{} + msh := &definitionsmocks.Handler{} + mds := &definitionsmocks.Sender{} mbm := &broadcastmocks.Manager{} mpm := &privatemessagingmocks.Manager{} mam := &assetmocks.Manager{} @@ -96,7 +95,7 @@ func newTestEventManagerCommon(t *testing.T, metrics, dbconcurrency bool) (*even mdi.On("Capabilities").Return(&database.Capabilities{Concurrency: dbconcurrency}).Maybe() mev.On("SetHandler", "ns1", mock.Anything).Return(nil).Maybe() mev.On("ValidateOptions", mock.Anything).Return(nil).Maybe() - emi, err := NewEventManager(ctx, "ns1", mni, mpi, mdi, mbi, mim, msh, mdm, mbm, mpm, mam, mdd, mmi, txHelper, events, mmp) + emi, err := NewEventManager(ctx, "ns1", mni, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, mdd, mmi, txHelper, events, mmp) em := emi.(*eventManager) em.txHelper = &txcommonmocks.Helper{} mockRunAsGroupPassthrough(mdi) @@ -142,9 +141,9 @@ func TestStartStopEventListenerFail(t *testing.T) { mdi := &databasemocks.Plugin{} mbi := &blockchainmocks.Plugin{} mim := &identitymanagermocks.Manager{} - mpi := &sharedstoragemocks.Plugin{} mdm := &datamocks.Manager{} - msh := &definitionsmocks.DefinitionHandler{} + msh := &definitionsmocks.Handler{} + mds := &definitionsmocks.Sender{} mbm := &broadcastmocks.Manager{} mpm := &privatemessagingmocks.Manager{} mni := &sysmessagingmocks.LocalNodeInfo{} @@ -158,7 +157,7 @@ func TestStartStopEventListenerFail(t *testing.T) { mdi.On("Capabilities").Return(&database.Capabilities{Concurrency: false}) mbi.On("VerifierType").Return(core.VerifierTypeEthAddress) mev.On("SetHandler", "ns1", mock.Anything).Return(fmt.Errorf("pop")) - _, err := NewEventManager(context.Background(), "ns1", mni, mpi, mdi, mbi, mim, msh, mdm, mbm, mpm, mam, msd, mm, txHelper, events, mmp) + _, err := NewEventManager(context.Background(), "ns1", mni, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, msd, mm, txHelper, events, mmp) assert.EqualError(t, err, "pop") } diff --git a/internal/events/network_action.go b/internal/events/network_action.go index 8337a48c8c..35f826f5bd 100644 --- a/internal/events/network_action.go +++ b/internal/events/network_action.go @@ -43,6 +43,11 @@ func (em *eventManager) actionTerminate(mm multiparty.Manager, event *blockchain } func (em *eventManager) BlockchainNetworkAction(action string, event *blockchain.Event, signingKey *core.VerifierRef) error { + if em.multiparty == nil { + log.L(em.ctx).Errorf("Ignoring network action from non-multiparty network!") + return nil + } + return em.retry.Do(em.ctx, "handle network action", func(attempt int) (retry bool, err error) { // Verify that the action came from a registered root org resolvedAuthor, err := em.identity.FindIdentityForVerifier(em.ctx, []core.IdentityType{core.IdentityTypeOrg}, signingKey) diff --git a/internal/events/network_action_test.go b/internal/events/network_action_test.go index b9b3fbab45..671eba4462 100644 --- a/internal/events/network_action_test.go +++ b/internal/events/network_action_test.go @@ -107,6 +107,20 @@ func TestNetworkActionNonRootIdentity(t *testing.T) { mii.AssertExpectations(t) } +func TestNetworkActionNonMultiparty(t *testing.T) { + em, cancel := newTestEventManager(t) + defer cancel() + em.multiparty = nil + + verifier := &core.VerifierRef{ + Type: core.VerifierTypeEthAddress, + Value: "0x1234", + } + + err := em.BlockchainNetworkAction("terminate", &blockchain.Event{}, verifier) + assert.NoError(t, err) +} + func TestNetworkActionUnknown(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() diff --git a/internal/events/reply_sender.go b/internal/events/reply_sender.go index 557914e9cc..72c65cfbf6 100644 --- a/internal/events/reply_sender.go +++ b/internal/events/reply_sender.go @@ -18,6 +18,7 @@ package events import ( "context" + "fmt" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly/pkg/core" @@ -26,9 +27,17 @@ import ( func (ed *eventDispatcher) sendReply(ctx context.Context, event *core.Event, reply *core.MessageInOut) { var err error if reply.Header.Group != nil { - err = ed.messaging.NewMessage(reply).Send(ctx) + if ed.messaging == nil { + err = fmt.Errorf("private messaging manager not initialized") + } else { + err = ed.messaging.NewMessage(reply).Send(ctx) + } } else { - err = ed.broadcast.NewBroadcast(reply).Send(ctx) + if ed.broadcast == nil { + err = fmt.Errorf("broadcast manager not initialized") + } else { + err = ed.broadcast.NewBroadcast(reply).Send(ctx) + } } if err != nil { log.L(ctx).Errorf("Failed to send reply: %s", err) diff --git a/internal/events/reply_sender_test.go b/internal/events/reply_sender_test.go index e44dad0fef..46ee368eeb 100644 --- a/internal/events/reply_sender_test.go +++ b/internal/events/reply_sender_test.go @@ -102,3 +102,37 @@ func TestSendReplyPrivateOk(t *testing.T) { mpm.AssertExpectations(t) mms.AssertExpectations(t) } + +func TestSendReplyBroadcastDisabled(t *testing.T) { + ed, cancel := newTestEventDispatcher(&subscription{ + definition: &core.Subscription{}, + }) + defer cancel() + ed.broadcast = nil + + ed.sendReply(context.Background(), &core.Event{ + ID: fftypes.NewUUID(), + Namespace: "ns1", + }, &core.MessageInOut{}) +} + +func TestSendReplyPrivateDisabled(t *testing.T) { + ed, cancel := newTestEventDispatcher(&subscription{ + definition: &core.Subscription{}, + }) + defer cancel() + ed.messaging = nil + + msg := &core.Message{ + Header: core.MessageHeader{ + Group: fftypes.NewRandB32(), + }, + } + + ed.sendReply(context.Background(), &core.Event{ + ID: fftypes.NewUUID(), + Namespace: "ns1", + }, &core.MessageInOut{ + Message: *msg, + }) +} diff --git a/internal/events/ss_callbacks.go b/internal/events/ss_callbacks.go index 906fdddf4f..f31e3fe96d 100644 --- a/internal/events/ss_callbacks.go +++ b/internal/events/ss_callbacks.go @@ -30,6 +30,11 @@ func (em *eventManager) SharedStorageBatchDownloaded(ss sharedstorage.Plugin, pa l := log.L(em.ctx) + if em.multiparty == nil { + l.Errorf("Ignoring private batch from non-multiparty network!") + return nil, nil + } + // De-serialize the batch var batch *core.Batch err := json.Unmarshal(data, &batch) diff --git a/internal/events/ss_callbacks_test.go b/internal/events/ss_callbacks_test.go index 86bef1388c..a79963c406 100644 --- a/internal/events/ss_callbacks_test.go +++ b/internal/events/ss_callbacks_test.go @@ -41,7 +41,7 @@ func TestSharedStorageBatchDownloadedOk(t *testing.T) { b, _ := json.Marshal(&batch) mdi := em.database.(*databasemocks.Plugin) - mss := em.sharedstorage.(*sharedstoragemocks.Plugin) + mss := &sharedstoragemocks.Plugin{} mdi.On("UpsertBatch", em.ctx, mock.Anything).Return(nil, nil) mdi.On("InsertDataArray", em.ctx, mock.Anything).Return(nil, nil) mdi.On("InsertMessages", em.ctx, mock.Anything, mock.AnythingOfType("database.PostCompletionHook")).Return(nil, nil).Run(func(args mock.Arguments) { @@ -74,7 +74,7 @@ func TestSharedStorageBatchDownloadedPersistFail(t *testing.T) { b, _ := json.Marshal(&batch) mdi := em.database.(*databasemocks.Plugin) - mss := em.sharedstorage.(*sharedstoragemocks.Plugin) + mss := &sharedstoragemocks.Plugin{} mdi.On("UpsertBatch", em.ctx, mock.Anything).Return(fmt.Errorf("pop")) mss.On("Name").Return("utdx").Maybe() @@ -95,7 +95,7 @@ func TestSharedStorageBatchDownloadedNSMismatch(t *testing.T) { batch := sampleBatch(t, core.BatchTypeBroadcast, core.TransactionTypeBatchPin, core.DataArray{data}) b, _ := json.Marshal(&batch) - mss := em.sharedstorage.(*sharedstoragemocks.Plugin) + mss := &sharedstoragemocks.Plugin{} mss.On("Name").Return("utdx").Maybe() em.namespace = "ns2" @@ -106,12 +106,30 @@ func TestSharedStorageBatchDownloadedNSMismatch(t *testing.T) { } +func TestSharedStorageBatchDownloadedNonMultiparty(t *testing.T) { + + em, cancel := newTestEventManager(t) + defer cancel() + em.multiparty = nil + + data := &core.Data{ID: fftypes.NewUUID(), Value: fftypes.JSONAnyPtr(`"test"`)} + batch := sampleBatch(t, core.BatchTypeBroadcast, core.TransactionTypeBatchPin, core.DataArray{data}) + b, _ := json.Marshal(&batch) + + mss := &sharedstoragemocks.Plugin{} + _, err := em.SharedStorageBatchDownloaded(mss, "payload1", b) + assert.NoError(t, err) + + mss.AssertExpectations(t) + +} + func TestSharedStorageBatchDownloadedBadData(t *testing.T) { em, cancel := newTestEventManager(t) defer cancel() - mss := em.sharedstorage.(*sharedstoragemocks.Plugin) + mss := &sharedstoragemocks.Plugin{} mss.On("Name").Return("utdx").Maybe() _, err := em.SharedStorageBatchDownloaded(mss, "payload1", []byte("!json")) @@ -127,7 +145,7 @@ func TestSharedStorageBlobDownloadedOk(t *testing.T) { defer cancel() mdi := em.database.(*databasemocks.Plugin) - mss := em.sharedstorage.(*sharedstoragemocks.Plugin) + mss := &sharedstoragemocks.Plugin{} mss.On("Name").Return("utsd") mdi.On("GetBlobs", em.ctx, mock.Anything).Return([]*core.Blob{}, nil, nil) mdi.On("InsertBlobs", em.ctx, mock.Anything).Return(nil, nil) diff --git a/internal/events/subscription_manager.go b/internal/events/subscription_manager.go index 50da22db03..b0cdf88984 100644 --- a/internal/events/subscription_manager.go +++ b/internal/events/subscription_manager.go @@ -106,8 +106,8 @@ func newSubscriptionManager(ctx context.Context, ns string, di database.Plugin, maxSubs: uint64(config.GetUint(coreconfig.SubscriptionMax)), cancelCtx: cancelCtx, eventNotifier: en, - broadcast: bm, - messaging: pm, + broadcast: bm, // optional + messaging: pm, // optional txHelper: txHelper, retry: retry.Retry{ InitialDelay: config.GetDuration(coreconfig.SubscriptionsRetryInitialDelay), diff --git a/internal/events/token_pool_created.go b/internal/events/token_pool_created.go index 3ebd5e7a6d..ed93ce6f2b 100644 --- a/internal/events/token_pool_created.go +++ b/internal/events/token_pool_created.go @@ -195,7 +195,7 @@ func (em *eventManager) TokenPoolCreated(ti tokens.Plugin, pool *tokens.TokenPoo Pool: announcePool, } log.L(em.ctx).Infof("Announcing token pool, id=%s", announcePool.ID) - _, err = em.broadcast.BroadcastTokenPool(em.ctx, broadcast, false) + err = em.defsender.DefineTokenPool(em.ctx, broadcast, false) } } diff --git a/internal/events/token_pool_created_test.go b/internal/events/token_pool_created_test.go index d42d57c8a2..8205f40c20 100644 --- a/internal/events/token_pool_created_test.go +++ b/internal/events/token_pool_created_test.go @@ -22,9 +22,9 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/mocks/assetmocks" - "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/datamocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/tokenmocks" "github.com/hyperledger/firefly/mocks/txcommonmocks" "github.com/hyperledger/firefly/pkg/blockchain" @@ -472,7 +472,7 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { defer cancel() mdi := em.database.(*databasemocks.Plugin) mti := &tokenmocks.Plugin{} - mbm := em.broadcast.(*broadcastmocks.Manager) + mds := em.defsender.(*definitionsmocks.Sender) poolID := fftypes.NewUUID() txID := fftypes.NewUUID() @@ -505,7 +505,7 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { mdi.On("GetTokenPoolByLocator", em.ctx, "ns1", "erc1155", "123").Return(nil, nil).Times(2) mdi.On("GetOperations", em.ctx, "ns1", mock.Anything).Return(nil, nil, fmt.Errorf("pop")).Once() mdi.On("GetOperations", em.ctx, "ns1", mock.Anything).Return(operations, nil, nil).Once() - mbm.On("BroadcastTokenPool", em.ctx, mock.MatchedBy(func(pool *core.TokenPoolAnnouncement) bool { + mds.On("DefineTokenPool", em.ctx, mock.MatchedBy(func(pool *core.TokenPoolAnnouncement) bool { return pool.Pool.Namespace == "ns1" && pool.Pool.Name == "my-pool" && *pool.Pool.ID == *poolID }), false).Return(nil, nil) @@ -514,7 +514,7 @@ func TestTokenPoolCreatedAnnounce(t *testing.T) { mti.AssertExpectations(t) mdi.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } func TestTokenPoolCreatedAnnounceWrongNS(t *testing.T) { diff --git a/internal/identity/identitymanager.go b/internal/identity/identitymanager.go index b4111dc18d..7646593e7b 100644 --- a/internal/identity/identitymanager.go +++ b/internal/identity/identitymanager.go @@ -57,7 +57,7 @@ type Manager interface { type identityManager struct { database database.Plugin blockchain blockchain.Plugin - multiparty multiparty.Manager + multiparty multiparty.Manager // optional namespace string defaultKey string multipartyRootVerifier *core.VerifierRef @@ -229,7 +229,10 @@ func (im *identityManager) getDefaultVerifier(ctx context.Context) (verifier *co if im.defaultKey != "" { return im.normalizeKeyViaBlockchainPlugin(ctx, im.defaultKey) } - return im.GetMultipartyRootVerifier(ctx) + if im.multiparty != nil { + return im.GetMultipartyRootVerifier(ctx) + } + return nil, i18n.NewError(ctx, coremsgs.MsgNodeMissingBlockchainKey) } // GetMultipartyRootVerifier gets the blockchain verifier of the root org via the configuration @@ -332,7 +335,7 @@ func (im *identityManager) VerifyIdentityChain(ctx context.Context, checkIdentit if err := im.validateParentType(ctx, current, parent); err != nil { return nil, false, err } - if parent.Messages.Claim == nil { + if im.multiparty != nil && parent.Messages.Claim == nil { return nil, false, i18n.NewError(ctx, coremsgs.MsgParentIdentityMissingClaim, parent.DID, parent.ID) } current = parent @@ -343,7 +346,8 @@ func (im *identityManager) VerifyIdentityChain(ctx context.Context, checkIdentit } -func (im *identityManager) ResolveIdentitySigner(ctx context.Context, identity *core.Identity) (parentSigner *core.SignerRef, err error) { +func (im *identityManager) ResolveIdentitySigner(ctx context.Context, identity *core.Identity) (signer *core.SignerRef, err error) { + // Find the message that registered the identity msg, err := im.database.GetMessageByID(ctx, im.namespace, identity.Messages.Claim) if err != nil { @@ -385,7 +389,7 @@ func (im *identityManager) cachedIdentityLookupByVerifierRef(ctx context.Context if err != nil { return nil, err } else if verifier == nil { - if namespace != core.LegacySystemNamespace && im.multiparty.GetNetworkVersion() == 1 { + if namespace != core.LegacySystemNamespace && im.multiparty != nil && im.multiparty.GetNetworkVersion() == 1 { // For V1 networks, fall back to LegacySystemNamespace for looking up identities // This assumes that the system namespace shares a database with this manager's namespace! return im.cachedIdentityLookupByVerifierRef(ctx, core.LegacySystemNamespace, verifierRef) diff --git a/internal/identity/identitymanager_test.go b/internal/identity/identitymanager_test.go index 055be5ef01..91023fd7cc 100644 --- a/internal/identity/identitymanager_test.go +++ b/internal/identity/identitymanager_test.go @@ -493,6 +493,16 @@ func TestNormalizeSigningKeyOrgFallbackErr(t *testing.T) { } +func TestNormalizeSigningKeyNoDefault(t *testing.T) { + + ctx, im := newTestIdentityManager(t) + im.multiparty = nil + + _, err := im.NormalizeSigningKey(ctx, "", KeyNormalizationBlockchainPlugin) + assert.Regexp(t, "FF10354", err) + +} + func TestResolveInputSigningKeyOk(t *testing.T) { ctx, im := newTestIdentityManager(t) @@ -997,8 +1007,8 @@ func TestVerifyIdentityChainCustomOrgOrgOk(t *testing.T) { mdi.On("GetIdentityByID", ctx, "ns1", idIntermediateCustom.ID).Return(idIntermediateCustom, nil).Once() mdi.On("GetIdentityByID", ctx, "ns1", idRoot.ID).Return(idRoot, nil).Once() - immeidateParent, _, err := im.VerifyIdentityChain(ctx, idLeaf) - assert.Equal(t, idIntermediateCustom, immeidateParent) + immediateParent, _, err := im.VerifyIdentityChain(ctx, idLeaf) + assert.Equal(t, idIntermediateCustom, immediateParent) assert.NoError(t, err) mdi.AssertExpectations(t) diff --git a/internal/namespace/manager.go b/internal/namespace/manager.go index 723a3263f0..4fea92bf16 100644 --- a/internal/namespace/manager.go +++ b/internal/namespace/manager.go @@ -220,6 +220,21 @@ func (nm *namespaceManager) Start() error { metrics.Registry() } for _, ns := range nm.namespaces { + if ns.plugins.Blockchain.Plugin != nil { + if err := ns.plugins.Blockchain.Plugin.Start(); err != nil { + return err + } + } + if ns.plugins.DataExchange.Plugin != nil { + if err := ns.plugins.DataExchange.Plugin.Start(); err != nil { + return err + } + } + for _, plugin := range ns.plugins.Tokens { + if err := plugin.Plugin.Start(); err != nil { + return err + } + } if err := ns.orchestrator.Start(); err != nil { return err } @@ -375,14 +390,17 @@ func (nm *namespaceManager) getDatabasePlugins(ctx context.Context) (plugins map // check for deprecated config if len(plugins) == 0 { pluginType := deprecatedDatabaseConfig.GetString(coreconfig.PluginConfigType) - plugin, err := difactory.GetPlugin(ctx, deprecatedDatabaseConfig.GetString(coreconfig.PluginConfigType)) - if err != nil { - return nil, err - } - name := "database_0" - plugins[name] = databasePlugin{ - config: deprecatedDatabaseConfig.SubSection(pluginType), - plugin: plugin, + if pluginType != "" { + plugin, err := difactory.GetPlugin(ctx, deprecatedDatabaseConfig.GetString(coreconfig.PluginConfigType)) + if err != nil { + return nil, err + } + log.L(ctx).Warnf("Your database config uses a deprecated configuration structure - the database configuration has been moved under the 'plugins' section") + name := "database_0" + plugins[name] = databasePlugin{ + config: deprecatedDatabaseConfig.SubSection(pluginType), + plugin: plugin, + } } } @@ -430,18 +448,20 @@ func (nm *namespaceManager) getDataExchangePlugins(ctx context.Context) (plugins } } + // check deprecated config if len(plugins) == 0 { - log.L(ctx).Warnf("Your data exchange config uses a deprecated configuration structure - the data exchange configuration has been moved under the 'plugins' section") pluginType := deprecatedDataexchangeConfig.GetString(coreconfig.PluginConfigType) - plugin, err := dxfactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } - - name := "dataexchange_0" - plugins[name] = dataExchangePlugin{ - config: deprecatedDataexchangeConfig.SubSection(pluginType), - plugin: plugin, + if pluginType != "" { + plugin, err := dxfactory.GetPlugin(ctx, pluginType) + if err != nil { + return nil, err + } + log.L(ctx).Warnf("Your data exchange config uses a deprecated configuration structure - the data exchange configuration has been moved under the 'plugins' section") + name := "dataexchange_0" + plugins[name] = dataExchangePlugin{ + config: deprecatedDataexchangeConfig.SubSection(pluginType), + plugin: plugin, + } } } @@ -496,15 +516,17 @@ func (nm *namespaceManager) getBlockchainPlugins(ctx context.Context) (plugins m // check deprecated config if len(plugins) == 0 { pluginType := deprecatedBlockchainConfig.GetString(coreconfig.PluginConfigType) - plugin, err := bifactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } - - name := "blockchain_0" - plugins[name] = blockchainPlugin{ - config: deprecatedBlockchainConfig.SubSection(pluginType), - plugin: plugin, + if pluginType != "" { + plugin, err := bifactory.GetPlugin(ctx, pluginType) + if err != nil { + return nil, err + } + log.L(ctx).Warnf("Your blockchain config uses a deprecated configuration structure - the blockchain configuration has been moved under the 'plugins' section") + name := "blockchain_0" + plugins[name] = blockchainPlugin{ + config: deprecatedBlockchainConfig.SubSection(pluginType), + plugin: plugin, + } } } @@ -535,15 +557,17 @@ func (nm *namespaceManager) getSharedStoragePlugins(ctx context.Context) (plugin // check deprecated config if len(plugins) == 0 { pluginType := deprecatedSharedStorageConfig.GetString(coreconfig.PluginConfigType) - plugin, err := ssfactory.GetPlugin(ctx, pluginType) - if err != nil { - return nil, err - } - - name := "sharedstorage_0" - plugins[name] = sharedStoragePlugin{ - config: deprecatedSharedStorageConfig.SubSection(pluginType), - plugin: plugin, + if pluginType != "" { + plugin, err := ssfactory.GetPlugin(ctx, pluginType) + if err != nil { + return nil, err + } + log.L(ctx).Warnf("Your shared storage config uses a deprecated configuration structure - the shared storage configuration has been moved under the 'plugins' section") + name := "sharedstorage_0" + plugins[name] = sharedStoragePlugin{ + config: deprecatedSharedStorageConfig.SubSection(pluginType), + plugin: plugin, + } } } @@ -623,16 +647,22 @@ func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, inde multipartyConf := conf.SubSection(coreconfig.NamespaceMultiparty) // If any multiparty org information is configured (here or at the root), assume multiparty mode by default orgName := multipartyConf.GetString(coreconfig.NamespaceMultipartyOrgName) + orgKey := multipartyConf.GetString(coreconfig.NamespaceMultipartyOrgKey) + orgDesc := multipartyConf.GetString(coreconfig.NamespaceMultipartyOrgDescription) + deprecatedOrgName := config.GetString(coreconfig.OrgName) + deprecatedOrgKey := config.GetString(coreconfig.OrgKey) + deprecatedOrgDesc := config.GetString(coreconfig.OrgDescription) + if deprecatedOrgName != "" || deprecatedOrgKey != "" || deprecatedOrgDesc != "" { + log.L(ctx).Warnf("Your org config uses a deprecated configuration structure - the org configuration has been moved under the 'namespaces.predefined[].multiparty' section") + } if orgName == "" { - orgName = config.GetString(coreconfig.OrgName) + orgName = deprecatedOrgName } - orgKey := multipartyConf.GetString(coreconfig.NamespaceMultipartyOrgKey) if orgKey == "" { - orgKey = config.GetString(coreconfig.OrgKey) + orgKey = deprecatedOrgKey } - orgDesc := multipartyConf.GetString(coreconfig.NamespaceMultipartyOrgDescription) if orgDesc == "" { - orgDesc = config.GetString(coreconfig.OrgDescription) + orgDesc = deprecatedOrgDesc } multiparty := multipartyConf.Get(coreconfig.NamespaceMultipartyEnabled) if multiparty == nil { @@ -700,7 +730,7 @@ func (nm *namespaceManager) loadNamespace(ctx context.Context, name string, inde config.Multiparty.Contracts = contracts p, err = nm.validateMultiPartyConfig(ctx, name, plugins) } else { - p, err = nm.validateGatewayConfig(ctx, name, plugins) + p, err = nm.validateNonMultipartyConfig(ctx, name, plugins) } if err != nil { return nil, err @@ -723,7 +753,7 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s for _, pluginName := range plugins { if instance, ok := nm.plugins.blockchain[pluginName]; ok { if result.Blockchain.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "blockchain") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "blockchain") } result.Blockchain = orchestrator.BlockchainPlugin{ Name: pluginName, @@ -733,7 +763,7 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s } if instance, ok := nm.plugins.dataexchange[pluginName]; ok { if result.DataExchange.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "dataexchange") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "dataexchange") } result.DataExchange = orchestrator.DataExchangePlugin{ Name: pluginName, @@ -743,7 +773,7 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s } if instance, ok := nm.plugins.sharedstorage[pluginName]; ok { if result.SharedStorage.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "sharedstorage") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "sharedstorage") } result.SharedStorage = orchestrator.SharedStoragePlugin{ Name: pluginName, @@ -753,7 +783,7 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s } if instance, ok := nm.plugins.database[pluginName]; ok { if result.Database.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "database") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "database") } result.Database = orchestrator.DatabasePlugin{ Name: pluginName, @@ -783,18 +813,18 @@ func (nm *namespaceManager) validateMultiPartyConfig(ctx context.Context, name s result.SharedStorage.Plugin == nil || result.DataExchange.Plugin == nil || result.Blockchain.Plugin == nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultipartyConfiguration, name) + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceWrongPluginsMultiparty, name) } return &result, nil } -func (nm *namespaceManager) validateGatewayConfig(ctx context.Context, name string, plugins []string) (*orchestrator.Plugins, error) { +func (nm *namespaceManager) validateNonMultipartyConfig(ctx context.Context, name string, plugins []string) (*orchestrator.Plugins, error) { var result orchestrator.Plugins for _, pluginName := range plugins { if instance, ok := nm.plugins.blockchain[pluginName]; ok { if result.Blockchain.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "blockchain") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "blockchain") } result.Blockchain = orchestrator.BlockchainPlugin{ Name: pluginName, @@ -803,14 +833,14 @@ func (nm *namespaceManager) validateGatewayConfig(ctx context.Context, name stri continue } if _, ok := nm.plugins.dataexchange[pluginName]; ok { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayInvalidPlugins, name) + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceWrongPluginsNonMultiparty, name) } if _, ok := nm.plugins.sharedstorage[pluginName]; ok { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayInvalidPlugins, name) + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceWrongPluginsNonMultiparty, name) } if instance, ok := nm.plugins.database[pluginName]; ok { if result.Database.Plugin != nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayMultiplePluginType, name, "database") + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceMultiplePluginType, name, "database") } result.Database = orchestrator.DatabasePlugin{ Name: pluginName, @@ -830,7 +860,7 @@ func (nm *namespaceManager) validateGatewayConfig(ctx context.Context, name stri } if result.Database.Plugin == nil { - return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceGatewayNoDB, name) + return nil, i18n.NewError(ctx, coremsgs.MsgNamespaceNoDatabase, name) } return &result, nil diff --git a/internal/namespace/manager_test.go b/internal/namespace/manager_test.go index 9ce16f3598..1ca9242126 100644 --- a/internal/namespace/manager_test.go +++ b/internal/namespace/manager_test.go @@ -29,6 +29,7 @@ import ( "github.com/hyperledger/firefly/internal/database/difactory" "github.com/hyperledger/firefly/internal/dataexchange/dxfactory" "github.com/hyperledger/firefly/internal/identity/iifactory" + "github.com/hyperledger/firefly/internal/orchestrator" "github.com/hyperledger/firefly/internal/sharedstorage/ssfactory" "github.com/hyperledger/firefly/internal/tokens/tifactory" "github.com/hyperledger/firefly/mocks/blockchainmocks" @@ -828,7 +829,7 @@ func TestLoadNamespacesUseDefaults(t *testing.T) { assert.Len(t, nm.namespaces, 1) } -func TestLoadNamespacesGatewayNoDatabase(t *testing.T) { +func TestLoadNamespacesNonMultipartyNoDatabase(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -980,7 +981,7 @@ func TestLoadNamespacesMultipartyContractBadLocation(t *testing.T) { assert.Regexp(t, "json:", err) } -func TestLoadNamespacesGatewayMultipleDB(t *testing.T) { +func TestLoadNamespacesNonMultipartyMultipleDB(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -998,7 +999,7 @@ func TestLoadNamespacesGatewayMultipleDB(t *testing.T) { assert.Regexp(t, "FF10394.*database", err) } -func TestLoadNamespacesGatewayMultipleBlockchains(t *testing.T) { +func TestLoadNamespacesNonMultipartyMultipleBlockchains(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -1036,7 +1037,7 @@ func TestLoadNamespacesMultipartyMissingPlugins(t *testing.T) { assert.Regexp(t, "FF10391", err) } -func TestLoadNamespacesGatewayWithDX(t *testing.T) { +func TestLoadNamespacesNonMultipartyWithDX(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -1054,7 +1055,7 @@ func TestLoadNamespacesGatewayWithDX(t *testing.T) { assert.Regexp(t, "FF10393", err) } -func TestLoadNamespacesGatewayWithSharedStorage(t *testing.T) { +func TestLoadNamespacesNonMultipartyWithSharedStorage(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -1072,7 +1073,7 @@ func TestLoadNamespacesGatewayWithSharedStorage(t *testing.T) { assert.Regexp(t, "FF10393", err) } -func TestLoadNamespacesGatewayUnknownPlugin(t *testing.T) { +func TestLoadNamespacesNonMultipartyUnknownPlugin(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -1090,7 +1091,7 @@ func TestLoadNamespacesGatewayUnknownPlugin(t *testing.T) { assert.Regexp(t, "FF10390.*unknown", err) } -func TestLoadNamespacesGatewayTokens(t *testing.T) { +func TestLoadNamespacesNonMultipartyTokens(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) @@ -1126,7 +1127,70 @@ func TestStart(t *testing.T) { mo.AssertExpectations(t) } -func TestStartFail(t *testing.T) { +func TestStartBlockchainFail(t *testing.T) { + nm := newTestNamespaceManager(true) + defer nm.cleanup(t) + + nm.namespaces = map[string]*namespace{ + "ns": { + plugins: orchestrator.Plugins{ + Blockchain: orchestrator.BlockchainPlugin{ + Plugin: nm.mbi, + }, + }, + }, + } + + nm.mbi.On("Start").Return(fmt.Errorf("pop")) + + err := nm.Start() + assert.EqualError(t, err, "pop") + +} + +func TestStartDataExchangeFail(t *testing.T) { + nm := newTestNamespaceManager(true) + defer nm.cleanup(t) + + nm.namespaces = map[string]*namespace{ + "ns": { + plugins: orchestrator.Plugins{ + DataExchange: orchestrator.DataExchangePlugin{ + Plugin: nm.mdx, + }, + }, + }, + } + + nm.mdx.On("Start").Return(fmt.Errorf("pop")) + + err := nm.Start() + assert.EqualError(t, err, "pop") + +} + +func TestStartTokensFail(t *testing.T) { + nm := newTestNamespaceManager(true) + defer nm.cleanup(t) + + nm.namespaces = map[string]*namespace{ + "ns": { + plugins: orchestrator.Plugins{ + Tokens: []orchestrator.TokensPlugin{{ + Plugin: nm.mti, + }}, + }, + }, + } + + nm.mti.On("Start").Return(fmt.Errorf("pop")) + + err := nm.Start() + assert.EqualError(t, err, "pop") + +} + +func TestStartOrchestratorFail(t *testing.T) { nm := newTestNamespaceManager(true) defer nm.cleanup(t) diff --git a/internal/networkmap/manager.go b/internal/networkmap/manager.go index 0f7881e410..feaafda3fd 100644 --- a/internal/networkmap/manager.go +++ b/internal/networkmap/manager.go @@ -20,8 +20,8 @@ import ( "context" "github.com/hyperledger/firefly-common/pkg/i18n" - "github.com/hyperledger/firefly/internal/broadcast" "github.com/hyperledger/firefly/internal/coremsgs" + "github.com/hyperledger/firefly/internal/definitions" "github.com/hyperledger/firefly/internal/identity" "github.com/hyperledger/firefly/internal/multiparty" "github.com/hyperledger/firefly/internal/syncasync" @@ -59,15 +59,15 @@ type networkMap struct { ctx context.Context namespace string database database.Plugin - broadcast broadcast.Manager - exchange dataexchange.Plugin + defsender definitions.Sender + exchange dataexchange.Plugin // optional identity identity.Manager syncasync syncasync.Bridge - multiparty multiparty.Manager + multiparty multiparty.Manager // optional } -func NewNetworkMap(ctx context.Context, ns string, di database.Plugin, dx dataexchange.Plugin, bm broadcast.Manager, im identity.Manager, sa syncasync.Bridge, mm multiparty.Manager) (Manager, error) { - if di == nil || bm == nil || dx == nil || im == nil || mm == nil { +func NewNetworkMap(ctx context.Context, ns string, di database.Plugin, dx dataexchange.Plugin, ds definitions.Sender, im identity.Manager, sa syncasync.Bridge, mm multiparty.Manager) (Manager, error) { + if di == nil || ds == nil || im == nil { return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "NetworkMap") } @@ -75,7 +75,7 @@ func NewNetworkMap(ctx context.Context, ns string, di database.Plugin, dx dataex ctx: ctx, namespace: ns, database: di, - broadcast: bm, + defsender: ds, exchange: dx, identity: im, syncasync: sa, diff --git a/internal/networkmap/manager_test.go b/internal/networkmap/manager_test.go index 8dad0170b6..204bc5f622 100644 --- a/internal/networkmap/manager_test.go +++ b/internal/networkmap/manager_test.go @@ -21,9 +21,9 @@ import ( "testing" "github.com/hyperledger/firefly/internal/coreconfig" - "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/databasemocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" @@ -34,12 +34,12 @@ func newTestNetworkmap(t *testing.T) (*networkMap, func()) { coreconfig.Reset() ctx, cancel := context.WithCancel(context.Background()) mdi := &databasemocks.Plugin{} - mbm := &broadcastmocks.Manager{} + mds := &definitionsmocks.Sender{} mdx := &dataexchangemocks.Plugin{} mim := &identitymanagermocks.Manager{} msa := &syncasyncmocks.Bridge{} mmp := &multipartymocks.Manager{} - nm, err := NewNetworkMap(ctx, "ns1", mdi, mdx, mbm, mim, msa, mmp) + nm, err := NewNetworkMap(ctx, "ns1", mdi, mdx, mds, mim, msa, mmp) assert.NoError(t, err) return nm.(*networkMap), cancel diff --git a/internal/networkmap/register_identity.go b/internal/networkmap/register_identity.go index b7af679758..43a4c218be 100644 --- a/internal/networkmap/register_identity.go +++ b/internal/networkmap/register_identity.go @@ -60,7 +60,6 @@ func (nm *networkMap) RegisterIdentity(ctx context.Context, dto *core.IdentityCr if identity.Type == "" { identity.Type = core.IdentityTypeCustom } - identity.DID, _ = identity.GenerateDID(ctx) // Verify the chain @@ -69,29 +68,37 @@ func (nm *networkMap) RegisterIdentity(ctx context.Context, dto *core.IdentityCr return nil, err } - // Resolve if we need to perform a validation + var claimSigner *core.SignerRef var parentSigner *core.SignerRef - if immediateParent != nil { - parentSigner, err = nm.identity.ResolveIdentitySigner(ctx, immediateParent) - if err != nil { - return nil, err + + if nm.multiparty != nil { + // Resolve if we need to perform a validation + if immediateParent != nil { + parentSigner, err = nm.identity.ResolveIdentitySigner(ctx, immediateParent) + if err != nil { + return nil, err + } } - } - // Determine claim signer - var claimSigner *core.SignerRef - if dto.Type == core.IdentityTypeNode { - // Nodes are special - as they need the claim to be signed directly by the parent - claimSigner = parentSigner - parentSigner = nil - } else { - if dto.Key == "" { - return nil, i18n.NewError(ctx, coremsgs.MsgBlockchainKeyNotSet) + // Determine claim signer + if dto.Type == core.IdentityTypeNode { + // Nodes are special - as they need the claim to be signed directly by the parent + claimSigner = parentSigner + parentSigner = nil + } else { + if dto.Key == "" { + return nil, i18n.NewError(ctx, coremsgs.MsgBlockchainKeyNotSet) + } + claimSigner = &core.SignerRef{ + Key: dto.Key, + Author: identity.DID, + } } + } else { claimSigner = &core.SignerRef{ - Key: dto.Key, + Key: dto.Key, + Author: identity.DID, } - claimSigner.Author = identity.DID } if waitConfirm { @@ -100,36 +107,9 @@ func (nm *networkMap) RegisterIdentity(ctx context.Context, dto *core.IdentityCr }) } err = nm.sendIdentityRequest(ctx, identity, claimSigner, parentSigner) - if err != nil { - return nil, err - } - return identity, nil + return identity, err } func (nm *networkMap) sendIdentityRequest(ctx context.Context, identity *core.Identity, claimSigner *core.SignerRef, parentSigner *core.SignerRef) error { - - // Send the claim - we disable the check on the DID author here, as we are registering the identity so it will not exist - claimMsg, err := nm.broadcast.BroadcastIdentityClaim(ctx, &core.IdentityClaim{ - Identity: identity, - }, claimSigner, core.SystemTagIdentityClaim, false) - if err != nil { - return err - } - identity.Messages.Claim = claimMsg.Header.ID - - // Send the verification if one is required. - if parentSigner != nil { - verifyMsg, err := nm.broadcast.BroadcastDefinition(ctx, &core.IdentityVerification{ - Claim: core.MessageRef{ - ID: claimMsg.Header.ID, - Hash: claimMsg.Hash, - }, - Identity: identity.IdentityBase, - }, parentSigner, core.SystemTagIdentityVerification, false) - if err != nil { - return err - } - identity.Messages.Verification = verifyMsg.Header.ID - } - return nil + return nm.defsender.ClaimIdentity(ctx, &core.IdentityClaim{Identity: identity}, claimSigner, parentSigner, false) } diff --git a/internal/networkmap/register_identity_test.go b/internal/networkmap/register_identity_test.go index e9f51569c8..b026bfbf9b 100644 --- a/internal/networkmap/register_identity_test.go +++ b/internal/networkmap/register_identity_test.go @@ -23,7 +23,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/syncasync" - "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/syncasyncmocks" "github.com/hyperledger/firefly/pkg/core" @@ -44,23 +44,17 @@ func TestRegisterIdentityOrgWithParentOk(t *testing.T) { Key: "0x23456", }, nil) - mockMsg1 := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mockMsg2 := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) + mds := nm.defsender.(*definitionsmocks.Sender) - mbm.On("BroadcastIdentityClaim", nm.ctx, + mds.On("ClaimIdentity", nm.ctx, mock.AnythingOfType("*core.IdentityClaim"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityClaim, false).Return(mockMsg1, nil) - - mbm.On("BroadcastDefinition", nm.ctx, - mock.AnythingOfType("*core.IdentityVerification"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x23456" }), - core.SystemTagIdentityVerification, false).Return(mockMsg2, nil) + false).Return(nil) org, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ Name: "child1", @@ -68,11 +62,10 @@ func TestRegisterIdentityOrgWithParentOk(t *testing.T) { Parent: fftypes.NewUUID().String(), }, false) assert.NoError(t, err) - assert.Equal(t, *mockMsg1.Header.ID, *org.Messages.Claim) - assert.Equal(t, *mockMsg2.Header.ID, *org.Messages.Verification) + assert.NotNil(t, org) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } func TestRegisterIdentityOrgWithParentWaitConfirmOk(t *testing.T) { @@ -98,23 +91,17 @@ func TestRegisterIdentityOrgWithParentWaitConfirmOk(t *testing.T) { assert.NoError(t, err) }).Return(nil, nil) - mockMsg1 := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mockMsg2 := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) + mds := nm.defsender.(*definitionsmocks.Sender) - mbm.On("BroadcastIdentityClaim", nm.ctx, + mds.On("ClaimIdentity", nm.ctx, mock.AnythingOfType("*core.IdentityClaim"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityClaim, false).Return(mockMsg1, nil) - - mbm.On("BroadcastDefinition", nm.ctx, - mock.AnythingOfType("*core.IdentityVerification"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x23456" }), - core.SystemTagIdentityVerification, false).Return(mockMsg2, nil) + false).Return(nil) _, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ Name: "child1", @@ -124,14 +111,15 @@ func TestRegisterIdentityOrgWithParentWaitConfirmOk(t *testing.T) { assert.NoError(t, err) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) msa.AssertExpectations(t) } -func TestRegisterIdentityCustomWithParentFail(t *testing.T) { +func TestRegisterIdentityOrgNonMultiparty(t *testing.T) { nm, cancel := newTestNetworkmap(t) defer cancel() + nm.multiparty = nil parentIdentity := testOrg("parent1") @@ -143,26 +131,15 @@ func TestRegisterIdentityCustomWithParentFail(t *testing.T) { DID: "did:firefly:org/parent1", }, }, false, nil) - mim.On("ResolveIdentitySigner", nm.ctx, parentIdentity).Return(&core.SignerRef{ - Key: "0x23456", - }, nil) - mockMsg := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) - - mbm.On("BroadcastIdentityClaim", nm.ctx, + mds := nm.defsender.(*definitionsmocks.Sender) + mds.On("ClaimIdentity", nm.ctx, mock.AnythingOfType("*core.IdentityClaim"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityClaim, false).Return(mockMsg, nil) - - mbm.On("BroadcastDefinition", nm.ctx, - mock.AnythingOfType("*core.IdentityVerification"), - mock.MatchedBy(func(sr *core.SignerRef) bool { - return sr.Key == "0x23456" - }), - core.SystemTagIdentityVerification, false).Return(nil, fmt.Errorf("pop")) + (*core.SignerRef)(nil), + false).Return(fmt.Errorf("pop")) _, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ Name: "custom1", @@ -172,10 +149,10 @@ func TestRegisterIdentityCustomWithParentFail(t *testing.T) { assert.Regexp(t, "pop", err) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } -func TestRegisterIdentityGetParentMsgFail(t *testing.T) { +func TestRegisterIdentityCustomWithParentFail(t *testing.T) { nm, cancel := newTestNetworkmap(t) defer cancel() @@ -184,33 +161,44 @@ func TestRegisterIdentityGetParentMsgFail(t *testing.T) { mim := nm.identity.(*identitymanagermocks.Manager) mim.On("VerifyIdentityChain", nm.ctx, mock.AnythingOfType("*core.Identity")).Return(parentIdentity, false, nil) - mim.On("ResolveIdentitySigner", nm.ctx, parentIdentity).Return(nil, fmt.Errorf("pop")) + mim.On("ResolveIdentitySigner", nm.ctx, parentIdentity).Return(&core.SignerRef{ + Key: "0x23456", + }, nil) - _, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ - Name: "custom1", + mds := nm.defsender.(*definitionsmocks.Sender) + + mds.On("ClaimIdentity", nm.ctx, + mock.AnythingOfType("*core.IdentityClaim"), + mock.MatchedBy(func(sr *core.SignerRef) bool { + return sr.Key == "0x12345" + }), + mock.MatchedBy(func(sr *core.SignerRef) bool { + return sr.Key == "0x23456" + }), + false).Return(nil) + + org, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ + Name: "child1", Key: "0x12345", Parent: fftypes.NewUUID().String(), }, false) - assert.Regexp(t, "pop", err) + assert.NoError(t, err) + assert.NotNil(t, org) mim.AssertExpectations(t) + mds.AssertExpectations(t) } -func TestRegisterIdentityRootBroadcastFail(t *testing.T) { +func TestRegisterIdentityGetParentMsgFail(t *testing.T) { nm, cancel := newTestNetworkmap(t) defer cancel() - mim := nm.identity.(*identitymanagermocks.Manager) - mim.On("VerifyIdentityChain", nm.ctx, mock.AnythingOfType("*core.Identity")).Return(nil, false, nil) + parentIdentity := testOrg("parent1") - mbm := nm.broadcast.(*broadcastmocks.Manager) - mbm.On("BroadcastIdentityClaim", nm.ctx, - mock.AnythingOfType("*core.IdentityClaim"), - mock.MatchedBy(func(sr *core.SignerRef) bool { - return sr.Key == "0x12345" - }), - core.SystemTagIdentityClaim, false).Return(nil, fmt.Errorf("pop")) + mim := nm.identity.(*identitymanagermocks.Manager) + mim.On("VerifyIdentityChain", nm.ctx, mock.AnythingOfType("*core.Identity")).Return(parentIdentity, false, nil) + mim.On("ResolveIdentitySigner", nm.ctx, parentIdentity).Return(nil, fmt.Errorf("pop")) _, err := nm.RegisterIdentity(nm.ctx, &core.IdentityCreateDTO{ Name: "custom1", @@ -220,7 +208,6 @@ func TestRegisterIdentityRootBroadcastFail(t *testing.T) { assert.Regexp(t, "pop", err) mim.AssertExpectations(t) - mbm.AssertExpectations(t) } func TestRegisterIdentityMissingKey(t *testing.T) { diff --git a/internal/networkmap/register_node_test.go b/internal/networkmap/register_node_test.go index 4ae4c28a02..092ee0f49f 100644 --- a/internal/networkmap/register_node_test.go +++ b/internal/networkmap/register_node_test.go @@ -23,8 +23,8 @@ import ( "github.com/hyperledger/firefly-common/pkg/config" "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/coreconfig" - "github.com/hyperledger/firefly/mocks/broadcastmocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" @@ -54,20 +54,20 @@ func TestRegisterNodeOk(t *testing.T) { "endpoint": "details", }, nil) - mockMsg := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) - mbm.On("BroadcastIdentityClaim", nm.ctx, + mds := nm.defsender.(*definitionsmocks.Sender) + mds.On("ClaimIdentity", nm.ctx, mock.AnythingOfType("*core.IdentityClaim"), signerRef, - core.SystemTagIdentityClaim, false).Return(mockMsg, nil) + (*core.SignerRef)(nil), + false).Return(nil) node, err := nm.RegisterNode(nm.ctx, false) assert.NoError(t, err) - assert.Equal(t, *mockMsg.Header.ID, *node.Messages.Claim) + assert.NotNil(t, node) mim.AssertExpectations(t) mdx.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } func TestRegisterNodePeerInfoFail(t *testing.T) { diff --git a/internal/networkmap/register_org_test.go b/internal/networkmap/register_org_test.go index 8a8ccc801b..ae4ed84c5b 100644 --- a/internal/networkmap/register_org_test.go +++ b/internal/networkmap/register_org_test.go @@ -23,7 +23,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/multiparty" - "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/pkg/core" @@ -67,21 +67,21 @@ func TestRegisterNodeOrgOk(t *testing.T) { mmp := nm.multiparty.(*multipartymocks.Manager) mmp.On("RootOrg").Return(multiparty.RootOrg{Name: "org0"}) - mockMsg := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) - mbm.On("BroadcastIdentityClaim", nm.ctx, + mds := nm.defsender.(*definitionsmocks.Sender) + mds.On("ClaimIdentity", nm.ctx, mock.AnythingOfType("*core.IdentityClaim"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityClaim, false).Return(mockMsg, nil) + (*core.SignerRef)(nil), + false).Return(nil) org, err := nm.RegisterNodeOrganization(nm.ctx, false) assert.NoError(t, err) - assert.Equal(t, *mockMsg.Header.ID, *org.Messages.Claim) + assert.NotNil(t, org) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) mmp.AssertExpectations(t) } diff --git a/internal/networkmap/update_identity.go b/internal/networkmap/update_identity.go index 6e0d6d741d..a7435182e6 100644 --- a/internal/networkmap/update_identity.go +++ b/internal/networkmap/update_identity.go @@ -44,10 +44,14 @@ func (nm *networkMap) updateIdentityID(ctx context.Context, id *fftypes.UUID, dt return nil, i18n.NewError(ctx, coremsgs.Msg404NoResult) } - // Resolve the signer of the original claim - updateSigner, err := nm.identity.ResolveIdentitySigner(ctx, identity) - if err != nil { - return nil, err + var updateSigner *core.SignerRef + + if nm.multiparty != nil { + // Resolve the signer of the original claim + updateSigner, err = nm.identity.ResolveIdentitySigner(ctx, identity) + if err != nil { + return nil, err + } } identity.IdentityProfile = dto.IdentityProfile @@ -56,14 +60,9 @@ func (nm *networkMap) updateIdentityID(ctx context.Context, id *fftypes.UUID, dt } // Send the update - updateMsg, err := nm.broadcast.BroadcastDefinition(ctx, &core.IdentityUpdate{ + err = nm.defsender.UpdateIdentity(ctx, identity, &core.IdentityUpdate{ Identity: identity.IdentityBase, Updates: dto.IdentityProfile, - }, updateSigner, core.SystemTagIdentityUpdate, waitConfirm) - if err != nil { - return nil, err - } - identity.Messages.Update = updateMsg.Header.ID - + }, updateSigner, waitConfirm) return identity, err } diff --git a/internal/networkmap/update_identity_test.go b/internal/networkmap/update_identity_test.go index 593bb1df61..20f0ce921e 100644 --- a/internal/networkmap/update_identity_test.go +++ b/internal/networkmap/update_identity_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/hyperledger/firefly-common/pkg/fftypes" - "github.com/hyperledger/firefly/mocks/broadcastmocks" + "github.com/hyperledger/firefly/mocks/definitionsmocks" "github.com/hyperledger/firefly/mocks/identitymanagermocks" "github.com/hyperledger/firefly/pkg/core" "github.com/stretchr/testify/assert" @@ -40,15 +40,15 @@ func TestUpdateIdentityProfileOk(t *testing.T) { signerRef := &core.SignerRef{Key: "0x12345"} mim.On("ResolveIdentitySigner", nm.ctx, identity).Return(signerRef, nil) - mockMsg1 := &core.Message{Header: core.MessageHeader{ID: fftypes.NewUUID()}} - mbm := nm.broadcast.(*broadcastmocks.Manager) + mds := nm.defsender.(*definitionsmocks.Sender) - mbm.On("BroadcastDefinition", nm.ctx, + mds.On("UpdateIdentity", nm.ctx, + mock.AnythingOfType("*core.Identity"), mock.AnythingOfType("*core.IdentityUpdate"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityUpdate, true).Return(mockMsg1, nil) + true).Return(nil) org, err := nm.UpdateIdentity(nm.ctx, identity.ID.String(), &core.IdentityUpdateDTO{ IdentityProfile: core.IdentityProfile{ @@ -57,10 +57,10 @@ func TestUpdateIdentityProfileOk(t *testing.T) { }, }, true) assert.NoError(t, err) - assert.Equal(t, *mockMsg1.Header.ID, *org.Messages.Update) + assert.NotNil(t, org) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } func TestUpdateIdentityProfileBroadcastFail(t *testing.T) { @@ -75,13 +75,14 @@ func TestUpdateIdentityProfileBroadcastFail(t *testing.T) { signerRef := &core.SignerRef{Key: "0x12345"} mim.On("ResolveIdentitySigner", nm.ctx, identity).Return(signerRef, nil) - mbm := nm.broadcast.(*broadcastmocks.Manager) - mbm.On("BroadcastDefinition", nm.ctx, + mds := nm.defsender.(*definitionsmocks.Sender) + mds.On("UpdateIdentity", nm.ctx, + mock.AnythingOfType("*core.Identity"), mock.AnythingOfType("*core.IdentityUpdate"), mock.MatchedBy(func(sr *core.SignerRef) bool { return sr.Key == "0x12345" }), - core.SystemTagIdentityUpdate, true).Return(nil, fmt.Errorf("pop")) + true).Return(fmt.Errorf("pop")) _, err := nm.UpdateIdentity(nm.ctx, identity.ID.String(), &core.IdentityUpdateDTO{ IdentityProfile: core.IdentityProfile{ @@ -92,7 +93,7 @@ func TestUpdateIdentityProfileBroadcastFail(t *testing.T) { assert.Regexp(t, "pop", err) mim.AssertExpectations(t) - mbm.AssertExpectations(t) + mds.AssertExpectations(t) } func TestUpdateIdentityProfileBadProfile(t *testing.T) { diff --git a/internal/orchestrator/bound_callbacks.go b/internal/orchestrator/bound_callbacks.go index 9acaa17014..fdc9689464 100644 --- a/internal/orchestrator/bound_callbacks.go +++ b/internal/orchestrator/bound_callbacks.go @@ -19,7 +19,6 @@ package orchestrator import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly/internal/events" - "github.com/hyperledger/firefly/internal/multiparty" "github.com/hyperledger/firefly/internal/operations" "github.com/hyperledger/firefly/pkg/blockchain" "github.com/hyperledger/firefly/pkg/core" @@ -29,11 +28,10 @@ import ( ) type boundCallbacks struct { - dx dataexchange.Plugin - ss sharedstorage.Plugin - ei events.EventManager - om operations.Manager - multiparty multiparty.Manager + dx dataexchange.Plugin + ss sharedstorage.Plugin + ei events.EventManager + om operations.Manager } func (bc *boundCallbacks) BlockchainOpUpdate(plugin blockchain.Plugin, nsOpID string, txState blockchain.TransactionStatus, blockchainTXID, errorMessage string, opOutput fftypes.JSONObject) { diff --git a/internal/orchestrator/bound_callbacks_test.go b/internal/orchestrator/bound_callbacks_test.go index 187027a676..0ddf0a1edd 100644 --- a/internal/orchestrator/bound_callbacks_test.go +++ b/internal/orchestrator/bound_callbacks_test.go @@ -25,7 +25,6 @@ import ( "github.com/hyperledger/firefly/mocks/blockchainmocks" "github.com/hyperledger/firefly/mocks/dataexchangemocks" "github.com/hyperledger/firefly/mocks/eventmocks" - "github.com/hyperledger/firefly/mocks/multipartymocks" "github.com/hyperledger/firefly/mocks/operationmocks" "github.com/hyperledger/firefly/mocks/sharedstoragemocks" "github.com/hyperledger/firefly/mocks/tokenmocks" @@ -40,12 +39,11 @@ import ( func TestBoundCallbacks(t *testing.T) { mei := &eventmocks.EventManager{} mbi := &blockchainmocks.Plugin{} - mmp := &multipartymocks.Manager{} mdx := &dataexchangemocks.Plugin{} mti := &tokenmocks.Plugin{} mss := &sharedstoragemocks.Plugin{} mom := &operationmocks.Manager{} - bc := boundCallbacks{dx: mdx, ei: mei, ss: mss, om: mom, multiparty: mmp} + bc := boundCallbacks{dx: mdx, ei: mei, ss: mss, om: mom} info := fftypes.JSONObject{"hello": "world"} batch := &blockchain.BatchPin{TransactionID: fftypes.NewUUID()} diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 5957c1d30f..35d47e703a 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -20,11 +20,13 @@ import ( "context" "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly/internal/assets" "github.com/hyperledger/firefly/internal/batch" "github.com/hyperledger/firefly/internal/broadcast" "github.com/hyperledger/firefly/internal/contracts" + "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/internal/data" "github.com/hyperledger/firefly/internal/definitions" "github.com/hyperledger/firefly/internal/events" @@ -52,16 +54,18 @@ type Orchestrator interface { Init(ctx context.Context, cancelCtx context.CancelFunc) error Start() error WaitStop() // The close itself is performed by canceling the context + + MultiParty() multiparty.Manager // only for multiparty + BatchManager() batch.Manager // only for multiparty + Broadcast() broadcast.Manager // only for multiparty + PrivateMessaging() privatemessaging.Manager // only for multiparty Assets() assets.Manager - BatchManager() batch.Manager - Broadcast() broadcast.Manager + DefinitionSender() definitions.Sender Contracts() contracts.Manager - MultiParty() multiparty.Manager Data() data.Manager Events() events.EventManager NetworkMap() networkmap.Manager Operations() operations.Manager - PrivateMessaging() privatemessaging.Manager // Status GetStatus(ctx context.Context) (*core.NodeStatus, error) @@ -166,13 +170,16 @@ type orchestrator struct { namespace string config Config plugins Plugins + multiparty multiparty.Manager // only for multiparty + batch batch.Manager // only for multiparty + broadcast broadcast.Manager // only for multiparty + messaging privatemessaging.Manager // only for multiparty + sharedDownload shareddownload.Manager // only for multiparty identity identity.Manager events events.EventManager networkmap networkmap.Manager - batch batch.Manager - broadcast broadcast.Manager - messaging privatemessaging.Manager - definitions definitions.DefinitionHandler + defhandler definitions.Handler + defsender definitions.Sender data data.Manager syncasync syncasync.Bridge assets assets.Manager @@ -181,9 +188,7 @@ type orchestrator struct { node *fftypes.UUID metrics metrics.Manager operations operations.Manager - sharedDownload shareddownload.Manager txHelper txcommon.Helper - multiparty multiparty.Manager } func NewOrchestrator(ns string, config Config, plugins Plugins, metrics metrics.Manager) Orchestrator { @@ -204,7 +209,6 @@ func (or *orchestrator) Init(ctx context.Context, cancelCtx context.CancelFunc) err = or.initComponents(or.ctx) } // Bind together the blockchain interface callbacks, with the events manager - or.bc.multiparty = or.multiparty or.bc.ei = or.events or.bc.dx = or.plugins.DataExchange.Plugin or.bc.ss = or.plugins.SharedStorage.Plugin @@ -237,48 +241,37 @@ func (or *orchestrator) tokens() map[string]tokens.Plugin { } func (or *orchestrator) Start() (err error) { - var ns *core.Namespace - ns, err = or.database().GetNamespace(or.ctx, or.namespace) - if err == nil { - if ns == nil { - ns = &core.Namespace{ - Name: or.namespace, - Created: fftypes.Now(), + if or.config.Multiparty.Enabled { + var ns *core.Namespace + ns, err = or.database().GetNamespace(or.ctx, or.namespace) + if err == nil { + if ns == nil { + ns = &core.Namespace{ + Name: or.namespace, + Created: fftypes.Now(), + } } + err = or.multiparty.ConfigureContract(or.ctx, &ns.Contracts) + } + if err == nil { + err = or.database().UpsertNamespace(or.ctx, ns, true) + } + if err == nil { + err = or.batch.Start() + } + if err == nil { + err = or.broadcast.Start() + } + if err == nil { + err = or.sharedDownload.Start() } - err = or.multiparty.ConfigureContract(or.ctx, &ns.Contracts) - } - if err == nil { - err = or.blockchain().Start() - } - if err == nil { - err = or.database().UpsertNamespace(or.ctx, ns, true) - } - if err == nil { - err = or.batch.Start() } if err == nil { err = or.events.Start() } - if err == nil { - err = or.broadcast.Start() - } - if err == nil { - err = or.messaging.Start() - } if err == nil { err = or.operations.Start() } - if err == nil { - err = or.sharedDownload.Start() - } - if err == nil { - for _, el := range or.tokens() { - if err = el.Start(); err != nil { - break - } - } - } or.started = true return err } @@ -318,6 +311,10 @@ func (or *orchestrator) PrivateMessaging() privatemessaging.Manager { return or.messaging } +func (or *orchestrator) DefinitionSender() definitions.Sender { + return or.defsender +} + func (or *orchestrator) Events() events.EventManager { return or.events } @@ -352,22 +349,30 @@ func (or *orchestrator) MultiParty() multiparty.Manager { func (or *orchestrator) initPlugins(ctx context.Context) (err error) { or.plugins.Database.Plugin.SetHandler(or.namespace, or) - or.plugins.Blockchain.Plugin.SetHandler(&or.bc) - or.plugins.SharedStorage.Plugin.SetHandler(or.namespace, &or.bc) - fb := database.IdentityQueryFactory.NewFilter(ctx) - nodes, _, err := or.database().GetIdentities(ctx, or.namespace, fb.And( - fb.Eq("type", core.IdentityTypeNode), - )) - if err != nil { - return err + if or.plugins.Blockchain.Plugin != nil { + or.plugins.Blockchain.Plugin.SetHandler(&or.bc) } - nodeInfo := make([]fftypes.JSONObject, len(nodes)) - for i, node := range nodes { - nodeInfo[i] = node.Profile + + if or.plugins.SharedStorage.Plugin != nil { + or.plugins.SharedStorage.Plugin.SetHandler(or.namespace, &or.bc) + } + + if or.plugins.DataExchange.Plugin != nil { + fb := database.IdentityQueryFactory.NewFilter(ctx) + nodes, _, err := or.database().GetIdentities(ctx, or.namespace, fb.And( + fb.Eq("type", core.IdentityTypeNode), + )) + if err != nil { + return err + } + nodeInfo := make([]fftypes.JSONObject, len(nodes)) + for i, node := range nodes { + nodeInfo[i] = node.Profile + } + or.plugins.DataExchange.Plugin.SetNodes(nodeInfo) + or.plugins.DataExchange.Plugin.SetHandler(or.namespace, &or.bc) } - or.plugins.DataExchange.Plugin.SetNodes(nodeInfo) - or.plugins.DataExchange.Plugin.SetHandler(or.namespace, &or.bc) for _, token := range or.plugins.Tokens { if err := token.Plugin.SetHandler(or.namespace, &or.bc); err != nil { @@ -378,14 +383,7 @@ func (or *orchestrator) initPlugins(ctx context.Context) (err error) { return nil } -func (or *orchestrator) initComponents(ctx context.Context) (err error) { - - if or.data == nil { - or.data, err = data.NewDataManager(ctx, or.namespace, or.database(), or.sharedstorage(), or.dataexchange()) - if err != nil { - return err - } - } +func (or *orchestrator) initManagers(ctx context.Context) (err error) { if or.txHelper == nil { or.txHelper = txcommon.NewTransactionHelper(or.namespace, or.database(), or.data) @@ -397,10 +395,12 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) { } } - if or.multiparty == nil { - or.multiparty, err = multiparty.NewMultipartyManager(or.ctx, or.namespace, or.config.Multiparty, or.database(), or.blockchain(), or.operations, or.metrics, or.txHelper) - if err != nil { - return err + if or.config.Multiparty.Enabled { + if or.multiparty == nil { + or.multiparty, err = multiparty.NewMultipartyManager(or.ctx, or.namespace, or.config.Multiparty, or.database(), or.blockchain(), or.operations, or.metrics, or.txHelper) + if err != nil { + return err + } } } @@ -413,27 +413,32 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) { or.syncasync = syncasync.NewSyncAsyncBridge(ctx, or.namespace, or.database(), or.data) - if or.batch == nil { - or.batch, err = batch.NewBatchManager(ctx, or.namespace, or, or.database(), or.data, or.txHelper) - if err != nil { - return err + if or.config.Multiparty.Enabled { + if or.batch == nil { + or.batch, err = batch.NewBatchManager(ctx, or.namespace, or, or.database(), or.data, or.txHelper) + if err != nil { + return err + } } - } - if or.messaging == nil { - if or.messaging, err = privatemessaging.NewPrivateMessaging(ctx, or.namespace, or.database(), or.dataexchange(), or.blockchain(), or.identity, or.batch, or.data, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil { - return err + if or.messaging == nil { + if or.messaging, err = privatemessaging.NewPrivateMessaging(ctx, or.namespace, or.database(), or.dataexchange(), or.blockchain(), or.identity, or.batch, or.data, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil { + return err + } } - } - if or.broadcast == nil { - if or.broadcast, err = broadcast.NewBroadcastManager(ctx, or.namespace, or.database(), or.blockchain(), or.dataexchange(), or.sharedstorage(), or.identity, or.data, or.batch, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil { - return err + if or.broadcast == nil { + if or.broadcast, err = broadcast.NewBroadcastManager(ctx, or.namespace, or.database(), or.blockchain(), or.dataexchange(), or.sharedstorage(), or.identity, or.data, or.batch, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil { + return err + } } - } - if or.networkmap == nil { - or.networkmap, err = networkmap.NewNetworkMap(ctx, or.namespace, or.database(), or.dataexchange(), or.broadcast, or.identity, or.syncasync, or.multiparty) + if or.sharedDownload == nil { + or.sharedDownload, err = shareddownload.NewDownloadManager(ctx, or.namespace, or.database(), or.sharedstorage(), or.dataexchange(), or.operations, &or.bc) + if err != nil { + return err + } + } } if or.assets == nil { @@ -444,38 +449,57 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) { } if or.contracts == nil { - or.contracts, err = contracts.NewContractManager(ctx, or.namespace, or.database(), or.blockchain(), or.broadcast, or.identity, or.operations, or.txHelper, or.syncasync) + or.contracts, err = contracts.NewContractManager(ctx, or.namespace, or.database(), or.blockchain(), or.identity, or.operations, or.txHelper, or.syncasync) if err != nil { return err } } - if or.definitions == nil { - or.definitions, err = definitions.NewDefinitionHandler(ctx, or.namespace, or.database(), or.blockchain(), or.dataexchange(), or.data, or.identity, or.assets, or.contracts) + if or.defsender == nil { + or.defsender, or.defhandler, err = definitions.NewDefinitionSender(ctx, or.namespace, or.config.Multiparty.Enabled, or.database(), or.blockchain(), or.dataexchange(), or.broadcast, or.identity, or.data, or.assets, or.contracts) if err != nil { return err } } - if or.sharedDownload == nil { - or.sharedDownload, err = shareddownload.NewDownloadManager(ctx, or.namespace, or.database(), or.sharedstorage(), or.dataexchange(), or.operations, &or.bc) + if or.networkmap == nil { + or.networkmap, err = networkmap.NewNetworkMap(ctx, or.namespace, or.database(), or.dataexchange(), or.defsender, or.identity, or.syncasync, or.multiparty) if err != nil { return err } } + return nil +} + +func (or *orchestrator) initComponents(ctx context.Context) (err error) { + if or.data == nil { + or.data, err = data.NewDataManager(ctx, or.namespace, or.database(), or.dataexchange()) + if err != nil { + return err + } + } + + if err := or.initManagers(ctx); err != nil { + return err + } + if or.events == nil { - or.events, err = events.NewEventManager(ctx, or.namespace, or, or.sharedstorage(), or.database(), or.blockchain(), or.identity, or.definitions, or.data, or.broadcast, or.messaging, or.assets, or.sharedDownload, or.metrics, or.txHelper, or.plugins.Events, or.multiparty) + or.events, err = events.NewEventManager(ctx, or.namespace, or, or.database(), or.blockchain(), or.identity, or.defhandler, or.data, or.defsender, or.broadcast, or.messaging, or.assets, or.sharedDownload, or.metrics, or.txHelper, or.plugins.Events, or.multiparty) if err != nil { return err } } or.syncasync.Init(or.events) - return err + + return nil } func (or *orchestrator) SubmitNetworkAction(ctx context.Context, action *core.NetworkAction) error { + if or.multiparty == nil { + return i18n.NewError(ctx, coremsgs.MsgActionNotSupported) + } key, err := or.identity.NormalizeSigningKey(ctx, "", identity.KeyNormalizationBlockchainPlugin) if err != nil { return err diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go index 5a255f9ce6..3851a6a2b3 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -75,8 +75,9 @@ type testOrchestrator struct { mth *txcommonmocks.Helper msd *shareddownloadmocks.Manager mae *spieventsmocks.Manager - mdh *definitionsmocks.DefinitionHandler + mdh *definitionsmocks.Handler mmp *multipartymocks.Manager + mds *definitionsmocks.Sender } func (tor *testOrchestrator) cleanup(t *testing.T) { @@ -133,8 +134,9 @@ func newTestOrchestrator() *testOrchestrator { mth: &txcommonmocks.Helper{}, msd: &shareddownloadmocks.Manager{}, mae: &spieventsmocks.Manager{}, - mdh: &definitionsmocks.DefinitionHandler{}, + mdh: &definitionsmocks.Handler{}, mmp: &multipartymocks.Manager{}, + mds: &definitionsmocks.Sender{}, } tor.orchestrator.multiparty = tor.mmp tor.orchestrator.data = tor.mdm @@ -150,7 +152,9 @@ func newTestOrchestrator() *testOrchestrator { tor.orchestrator.operations = tor.mom tor.orchestrator.sharedDownload = tor.msd tor.orchestrator.txHelper = tor.mth - tor.orchestrator.definitions = tor.mdh + tor.orchestrator.defhandler = tor.mdh + tor.orchestrator.defsender = tor.mds + tor.orchestrator.config.Multiparty.Enabled = true tor.orchestrator.plugins.Blockchain.Plugin = tor.mbi tor.orchestrator.plugins.SharedStorage.Plugin = tor.mps tor.orchestrator.plugins.DataExchange.Plugin = tor.mdx @@ -170,6 +174,7 @@ func newTestOrchestrator() *testOrchestrator { tor.mcm.On("Name").Return("mock-cm").Maybe() tor.mmi.On("Name").Return("mock-mm").Maybe() tor.mmp.On("Name").Return("mock-mp").Maybe() + tor.mds.On("Init", mock.Anything).Maybe() return tor } @@ -199,6 +204,7 @@ func TestInitOK(t *testing.T) { assert.Equal(t, or.mba, or.BatchManager()) assert.Equal(t, or.mbm, or.Broadcast()) assert.Equal(t, or.mpm, or.PrivateMessaging()) + assert.Equal(t, or.mds, or.DefinitionSender()) assert.Equal(t, or.mem, or.Events()) assert.Equal(t, or.mam, or.Assets()) assert.Equal(t, or.mdm, or.Data()) @@ -297,6 +303,15 @@ func TestInitBroadcastComponentFail(t *testing.T) { assert.Regexp(t, "FF10128", err) } +func TestInitDefSenderComponentFail(t *testing.T) { + or := newTestOrchestrator() + defer or.cleanup(t) + or.data = nil + or.defsender = nil + err := or.initManagers(context.Background()) + assert.Regexp(t, "FF10128", err) +} + func TestInitDataComponentFail(t *testing.T) { or := newTestOrchestrator() defer or.cleanup(t) @@ -333,15 +348,6 @@ func TestInitContractsComponentFail(t *testing.T) { assert.Regexp(t, "FF10128", err) } -func TestInitDefinitionsComponentFail(t *testing.T) { - or := newTestOrchestrator() - defer or.cleanup(t) - or.plugins.Database.Plugin = nil - or.definitions = nil - err := or.initComponents(context.Background()) - assert.Regexp(t, "FF10128", err) -} - func TestInitOperationsComponentFail(t *testing.T) { or := newTestOrchestrator() defer or.cleanup(t) @@ -359,42 +365,11 @@ func TestStartBatchFail(t *testing.T) { or.mdi.On("GetNamespace", mock.Anything, "ns").Return(nil, nil) or.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(nil) or.mmp.On("ConfigureContract", mock.Anything, mock.Anything).Return(nil) - or.mbi.On("Start").Return(nil) or.mba.On("Start").Return(fmt.Errorf("pop")) err := or.Start() assert.EqualError(t, err, "pop") } -func TestStartTokensFail(t *testing.T) { - coreconfig.Reset() - or := newTestOrchestrator() - defer or.cleanup(t) - or.mdi.On("GetNamespace", mock.Anything, "ns").Return(nil, nil) - or.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(nil) - or.mmp.On("ConfigureContract", mock.Anything, &core.FireFlyContracts{}).Return(nil) - or.mbi.On("Start").Return(nil) - or.mba.On("Start").Return(nil) - or.mem.On("Start").Return(nil) - or.mbm.On("Start").Return(nil) - or.mpm.On("Start").Return(nil) - or.msd.On("Start").Return(nil) - or.mom.On("Start").Return(nil) - or.mti.On("Start").Return(fmt.Errorf("pop")) - err := or.Start() - assert.EqualError(t, err, "pop") -} - -func TestStartBlockchainsFail(t *testing.T) { - coreconfig.Reset() - or := newTestOrchestrator() - defer or.cleanup(t) - or.mdi.On("GetNamespace", mock.Anything, "ns").Return(nil, nil) - or.mmp.On("ConfigureContract", mock.Anything, &core.FireFlyContracts{}).Return(nil) - or.mbi.On("Start").Return(fmt.Errorf("pop")) - err := or.Start() - assert.EqualError(t, err, "pop") -} - func TestStartBlockchainsConfigureFail(t *testing.T) { coreconfig.Reset() or := newTestOrchestrator() @@ -412,12 +387,9 @@ func TestStartStopOk(t *testing.T) { or.mdi.On("GetNamespace", mock.Anything, "ns").Return(nil, nil) or.mdi.On("UpsertNamespace", mock.Anything, mock.Anything, true).Return(nil) or.mmp.On("ConfigureContract", mock.Anything, &core.FireFlyContracts{}).Return(nil) - or.mbi.On("Start").Return(nil) or.mba.On("Start").Return(nil) or.mem.On("Start").Return(nil) or.mbm.On("Start").Return(nil) - or.mpm.On("Start").Return(nil) - or.mti.On("Start").Return(nil) or.msd.On("Start").Return(nil) or.mom.On("Start").Return(nil) or.mba.On("WaitStop").Return(nil) @@ -449,3 +421,10 @@ func TestNetworkActionBadKey(t *testing.T) { err := or.SubmitNetworkAction(context.Background(), action) assert.EqualError(t, err, "pop") } + +func TestNetworkActionNonMultiparty(t *testing.T) { + or := newTestOrchestrator() + or.multiparty = nil + err := or.SubmitNetworkAction(context.Background(), &core.NetworkAction{Type: core.NetworkActionTerminate}) + assert.Regexp(t, "FF10414", err) +} diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go index 8aa8b675fa..75a1914d76 100644 --- a/internal/privatemessaging/privatemessaging.go +++ b/internal/privatemessaging/privatemessaging.go @@ -49,7 +49,6 @@ type Manager interface { core.Named GroupManager - Start() error NewMessage(msg *core.MessageInOut) sysmessaging.MessageSender SendMessage(ctx context.Context, in *core.MessageInOut, waitConfirm bool) (out *core.Message, err error) RequestReply(ctx context.Context, request *core.MessageInOut) (reply *core.MessageInOut, err error) @@ -161,10 +160,6 @@ func (pm *privateMessaging) Name() string { return "PrivateMessaging" } -func (pm *privateMessaging) Start() error { - return pm.exchange.Start() -} - func (pm *privateMessaging) dispatchPinnedBatch(ctx context.Context, state *batch.DispatchState) error { err := pm.dispatchBatchCommon(ctx, state) if err != nil { diff --git a/internal/privatemessaging/privatemessaging_test.go b/internal/privatemessaging/privatemessaging_test.go index 86ff52fe9d..6546ccc6e6 100644 --- a/internal/privatemessaging/privatemessaging_test.go +++ b/internal/privatemessaging/privatemessaging_test.go @@ -535,14 +535,3 @@ func TestTransferBlobsOpInsertFail(t *testing.T) { mdi.AssertExpectations(t) } - -func TestStart(t *testing.T) { - pm, cancel := newTestPrivateMessaging(t) - defer cancel() - - mdx := pm.exchange.(*dataexchangemocks.Plugin) - mdx.On("Start").Return(nil) - - err := pm.Start() - assert.NoError(t, err) -} diff --git a/internal/shareddownload/download_manager.go b/internal/shareddownload/download_manager.go index 1ba1e70039..751dc0e0ed 100644 --- a/internal/shareddownload/download_manager.go +++ b/internal/shareddownload/download_manager.go @@ -53,7 +53,7 @@ type downloadManager struct { cancelFunc func() namespace string database database.Plugin - sharedstorage sharedstorage.Plugin + sharedstorage sharedstorage.Plugin // optional dataexchange dataexchange.Plugin operations operations.Manager callbacks Callbacks diff --git a/mocks/broadcastmocks/manager.go b/mocks/broadcastmocks/manager.go index 522802d6e9..4c778b9847 100644 --- a/mocks/broadcastmocks/manager.go +++ b/mocks/broadcastmocks/manager.go @@ -18,98 +18,6 @@ type Manager struct { mock.Mock } -// BroadcastDatatype provides a mock function with given fields: ctx, datatype, waitConfirm -func (_m *Manager) BroadcastDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) (*core.Message, error) { - ret := _m.Called(ctx, datatype, waitConfirm) - - var r0 *core.Message - if rf, ok := ret.Get(0).(func(context.Context, *core.Datatype, bool) *core.Message); ok { - r0 = rf(ctx, datatype, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.Message) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *core.Datatype, bool) error); ok { - r1 = rf(ctx, datatype, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// BroadcastDefinition provides a mock function with given fields: ctx, def, signingIdentity, tag, waitConfirm -func (_m *Manager) BroadcastDefinition(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (*core.Message, error) { - ret := _m.Called(ctx, def, signingIdentity, tag, waitConfirm) - - var r0 *core.Message - if rf, ok := ret.Get(0).(func(context.Context, core.Definition, *core.SignerRef, string, bool) *core.Message); ok { - r0 = rf(ctx, def, signingIdentity, tag, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.Message) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, core.Definition, *core.SignerRef, string, bool) error); ok { - r1 = rf(ctx, def, signingIdentity, tag, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// BroadcastDefinitionAsNode provides a mock function with given fields: ctx, def, tag, waitConfirm -func (_m *Manager) BroadcastDefinitionAsNode(ctx context.Context, def core.Definition, tag string, waitConfirm bool) (*core.Message, error) { - ret := _m.Called(ctx, def, tag, waitConfirm) - - var r0 *core.Message - if rf, ok := ret.Get(0).(func(context.Context, core.Definition, string, bool) *core.Message); ok { - r0 = rf(ctx, def, tag, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.Message) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, core.Definition, string, bool) error); ok { - r1 = rf(ctx, def, tag, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// BroadcastIdentityClaim provides a mock function with given fields: ctx, def, signingIdentity, tag, waitConfirm -func (_m *Manager) BroadcastIdentityClaim(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, tag string, waitConfirm bool) (*core.Message, error) { - ret := _m.Called(ctx, def, signingIdentity, tag, waitConfirm) - - var r0 *core.Message - if rf, ok := ret.Get(0).(func(context.Context, *core.IdentityClaim, *core.SignerRef, string, bool) *core.Message); ok { - r0 = rf(ctx, def, signingIdentity, tag, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.Message) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *core.IdentityClaim, *core.SignerRef, string, bool) error); ok { - r1 = rf(ctx, def, signingIdentity, tag, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // BroadcastMessage provides a mock function with given fields: ctx, in, waitConfirm func (_m *Manager) BroadcastMessage(ctx context.Context, in *core.MessageInOut, waitConfirm bool) (*core.Message, error) { ret := _m.Called(ctx, in, waitConfirm) @@ -133,29 +41,6 @@ func (_m *Manager) BroadcastMessage(ctx context.Context, in *core.MessageInOut, return r0, r1 } -// BroadcastTokenPool provides a mock function with given fields: ctx, pool, waitConfirm -func (_m *Manager) BroadcastTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) (*core.Message, error) { - ret := _m.Called(ctx, pool, waitConfirm) - - var r0 *core.Message - if rf, ok := ret.Get(0).(func(context.Context, *core.TokenPoolAnnouncement, bool) *core.Message); ok { - r0 = rf(ctx, pool, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.Message) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *core.TokenPoolAnnouncement, bool) error); ok { - r1 = rf(ctx, pool, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // Name provides a mock function with given fields: func (_m *Manager) Name() string { ret := _m.Called() diff --git a/mocks/contractmocks/manager.go b/mocks/contractmocks/manager.go index 5a65fedde1..bdc6171baf 100644 --- a/mocks/contractmocks/manager.go +++ b/mocks/contractmocks/manager.go @@ -65,52 +65,6 @@ func (_m *Manager) AddContractListener(ctx context.Context, listener *core.Contr return r0, r1 } -// BroadcastContractAPI provides a mock function with given fields: ctx, httpServerURL, api, waitConfirm -func (_m *Manager) BroadcastContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) (*core.ContractAPI, error) { - ret := _m.Called(ctx, httpServerURL, api, waitConfirm) - - var r0 *core.ContractAPI - if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, bool) *core.ContractAPI); ok { - r0 = rf(ctx, httpServerURL, api, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.ContractAPI) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, *core.ContractAPI, bool) error); ok { - r1 = rf(ctx, httpServerURL, api, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// BroadcastFFI provides a mock function with given fields: ctx, ffi, waitConfirm -func (_m *Manager) BroadcastFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) (*core.FFI, error) { - ret := _m.Called(ctx, ffi, waitConfirm) - - var r0 *core.FFI - if rf, ok := ret.Get(0).(func(context.Context, *core.FFI, bool) *core.FFI); ok { - r0 = rf(ctx, ffi, waitConfirm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*core.FFI) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *core.FFI, bool) error); ok { - r1 = rf(ctx, ffi, waitConfirm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // DeleteContractListenerByNameOrID provides a mock function with given fields: ctx, nameOrID func (_m *Manager) DeleteContractListenerByNameOrID(ctx context.Context, nameOrID string) error { ret := _m.Called(ctx, nameOrID) @@ -520,6 +474,34 @@ func (_m *Manager) PrepareOperation(ctx context.Context, op *core.Operation) (*c return r0, r1 } +// ResolveContractAPI provides a mock function with given fields: ctx, httpServerURL, api +func (_m *Manager) ResolveContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI) error { + ret := _m.Called(ctx, httpServerURL, api) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI) error); ok { + r0 = rf(ctx, httpServerURL, api) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResolveFFI provides a mock function with given fields: ctx, ffi +func (_m *Manager) ResolveFFI(ctx context.Context, ffi *core.FFI) error { + ret := _m.Called(ctx, ffi) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.FFI) error); ok { + r0 = rf(ctx, ffi) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RunOperation provides a mock function with given fields: ctx, op func (_m *Manager) RunOperation(ctx context.Context, op *core.PreparedOperation) (fftypes.JSONObject, bool, error) { ret := _m.Called(ctx, op) @@ -549,17 +531,3 @@ func (_m *Manager) RunOperation(ctx context.Context, op *core.PreparedOperation) return r0, r1, r2 } - -// ValidateFFIAndSetPathnames provides a mock function with given fields: ctx, ffi -func (_m *Manager) ValidateFFIAndSetPathnames(ctx context.Context, ffi *core.FFI) error { - ret := _m.Called(ctx, ffi) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *core.FFI) error); ok { - r0 = rf(ctx, ffi) - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/mocks/definitionsmocks/definition_handler.go b/mocks/definitionsmocks/handler.go similarity index 53% rename from mocks/definitionsmocks/definition_handler.go rename to mocks/definitionsmocks/handler.go index 0128b62b13..acd7e46675 100644 --- a/mocks/definitionsmocks/definition_handler.go +++ b/mocks/definitionsmocks/handler.go @@ -13,24 +13,24 @@ import ( mock "github.com/stretchr/testify/mock" ) -// DefinitionHandler is an autogenerated mock type for the DefinitionHandler type -type DefinitionHandler struct { +// Handler is an autogenerated mock type for the Handler type +type Handler struct { mock.Mock } // HandleDefinitionBroadcast provides a mock function with given fields: ctx, state, msg, data, tx -func (_m *DefinitionHandler) HandleDefinitionBroadcast(ctx context.Context, state definitions.DefinitionBatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (definitions.HandlerResult, error) { +func (_m *Handler) HandleDefinitionBroadcast(ctx context.Context, state *core.BatchState, msg *core.Message, data core.DataArray, tx *fftypes.UUID) (definitions.HandlerResult, error) { ret := _m.Called(ctx, state, msg, data, tx) var r0 definitions.HandlerResult - if rf, ok := ret.Get(0).(func(context.Context, definitions.DefinitionBatchState, *core.Message, core.DataArray, *fftypes.UUID) definitions.HandlerResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, *core.BatchState, *core.Message, core.DataArray, *fftypes.UUID) definitions.HandlerResult); ok { r0 = rf(ctx, state, msg, data, tx) } else { r0 = ret.Get(0).(definitions.HandlerResult) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, definitions.DefinitionBatchState, *core.Message, core.DataArray, *fftypes.UUID) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, *core.BatchState, *core.Message, core.DataArray, *fftypes.UUID) error); ok { r1 = rf(ctx, state, msg, data, tx) } else { r1 = ret.Error(1) diff --git a/mocks/definitionsmocks/sender.go b/mocks/definitionsmocks/sender.go new file mode 100644 index 0000000000..102ecbfa23 --- /dev/null +++ b/mocks/definitionsmocks/sender.go @@ -0,0 +1,114 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package definitionsmocks + +import ( + context "context" + + core "github.com/hyperledger/firefly/pkg/core" + + mock "github.com/stretchr/testify/mock" +) + +// Sender is an autogenerated mock type for the Sender type +type Sender struct { + mock.Mock +} + +// ClaimIdentity provides a mock function with given fields: ctx, def, signingIdentity, parentSigner, waitConfirm +func (_m *Sender) ClaimIdentity(ctx context.Context, def *core.IdentityClaim, signingIdentity *core.SignerRef, parentSigner *core.SignerRef, waitConfirm bool) error { + ret := _m.Called(ctx, def, signingIdentity, parentSigner, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.IdentityClaim, *core.SignerRef, *core.SignerRef, bool) error); ok { + r0 = rf(ctx, def, signingIdentity, parentSigner, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DefineContractAPI provides a mock function with given fields: ctx, httpServerURL, api, waitConfirm +func (_m *Sender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error { + ret := _m.Called(ctx, httpServerURL, api, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, bool) error); ok { + r0 = rf(ctx, httpServerURL, api, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DefineDatatype provides a mock function with given fields: ctx, datatype, waitConfirm +func (_m *Sender) DefineDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) error { + ret := _m.Called(ctx, datatype, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.Datatype, bool) error); ok { + r0 = rf(ctx, datatype, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DefineFFI provides a mock function with given fields: ctx, ffi, waitConfirm +func (_m *Sender) DefineFFI(ctx context.Context, ffi *core.FFI, waitConfirm bool) error { + ret := _m.Called(ctx, ffi, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.FFI, bool) error); ok { + r0 = rf(ctx, ffi, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DefineTokenPool provides a mock function with given fields: ctx, pool, waitConfirm +func (_m *Sender) DefineTokenPool(ctx context.Context, pool *core.TokenPoolAnnouncement, waitConfirm bool) error { + ret := _m.Called(ctx, pool, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.TokenPoolAnnouncement, bool) error); ok { + r0 = rf(ctx, pool, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Name provides a mock function with given fields: +func (_m *Sender) Name() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// UpdateIdentity provides a mock function with given fields: ctx, identity, def, signingIdentity, waitConfirm +func (_m *Sender) UpdateIdentity(ctx context.Context, identity *core.Identity, def *core.IdentityUpdate, signingIdentity *core.SignerRef, waitConfirm bool) error { + ret := _m.Called(ctx, identity, def, signingIdentity, waitConfirm) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *core.Identity, *core.IdentityUpdate, *core.SignerRef, bool) error); ok { + r0 = rf(ctx, identity, def, signingIdentity, waitConfirm) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/mocks/orchestratormocks/orchestrator.go b/mocks/orchestratormocks/orchestrator.go index c31c137a21..cfb6babdee 100644 --- a/mocks/orchestratormocks/orchestrator.go +++ b/mocks/orchestratormocks/orchestrator.go @@ -18,6 +18,8 @@ import ( database "github.com/hyperledger/firefly/pkg/database" + definitions "github.com/hyperledger/firefly/internal/definitions" + events "github.com/hyperledger/firefly/internal/events" mock "github.com/stretchr/testify/mock" @@ -162,6 +164,22 @@ func (_m *Orchestrator) Data() data.Manager { return r0 } +// DefinitionSender provides a mock function with given fields: +func (_m *Orchestrator) DefinitionSender() definitions.Sender { + ret := _m.Called() + + var r0 definitions.Sender + if rf, ok := ret.Get(0).(func() definitions.Sender); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(definitions.Sender) + } + } + + return r0 +} + // DeleteSubscription provides a mock function with given fields: ctx, id func (_m *Orchestrator) DeleteSubscription(ctx context.Context, id string) error { ret := _m.Called(ctx, id) diff --git a/mocks/privatemessagingmocks/manager.go b/mocks/privatemessagingmocks/manager.go index dc6ae416fc..15eba3c576 100644 --- a/mocks/privatemessagingmocks/manager.go +++ b/mocks/privatemessagingmocks/manager.go @@ -247,17 +247,3 @@ func (_m *Manager) SendMessage(ctx context.Context, in *core.MessageInOut, waitC return r0, r1 } - -// Start provides a mock function with given fields: -func (_m *Manager) Start() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} diff --git a/pkg/core/batch_state.go b/pkg/core/batch_state.go new file mode 100644 index 0000000000..c4c3b931e8 --- /dev/null +++ b/pkg/core/batch_state.go @@ -0,0 +1,79 @@ +// Copyright © 2022 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 core + +import ( + "context" + + "github.com/hyperledger/firefly-common/pkg/fftypes" +) + +// BatchState tracks the state between definition handlers that run in-line on the pin processing route in the aggregator +// as part of a batch of pins. They might have complex API calls and interdependencies that need to be managed via this state. +type BatchState struct { + // PreFinalize callbacks may perform blocking actions (possibly to an external connector) + // - Will execute after all batch messages have been processed + // - Will execute outside database RunAsGroup + // - If any PreFinalize callback errors out, batch will be aborted and retried + PreFinalize []func(ctx context.Context) error + + // Finalize callbacks may perform final, non-idempotent database operations (such as inserting Events) + // - Will execute after all batch messages have been processed and any PreFinalize callbacks have succeeded + // - Will execute inside database RunAsGroup + // - If any Finalize callback errors out, batch will be aborted and retried (small chance of duplicate execution here) + Finalize []func(ctx context.Context) error + + // PendingConfirms are messages that are pending confirmation after already being processed in this batch + PendingConfirms map[fftypes.UUID]*Message + + // ConfirmedDIDClaims are DID claims locked in within this batch + ConfirmedDIDClaims []string +} + +func (bs *BatchState) AddPreFinalize(action func(ctx context.Context) error) { + bs.PreFinalize = append(bs.PreFinalize, action) +} + +func (bs *BatchState) AddFinalize(action func(ctx context.Context) error) { + bs.Finalize = append(bs.Finalize, action) +} + +func (bs *BatchState) AddPendingConfirm(id *fftypes.UUID, message *Message) { + bs.PendingConfirms[*id] = message +} + +func (bs *BatchState) AddConfirmedDIDClaim(did string) { + bs.ConfirmedDIDClaims = append(bs.ConfirmedDIDClaims, did) +} + +func (bs *BatchState) RunPreFinalize(ctx context.Context) error { + for _, action := range bs.PreFinalize { + if err := action(ctx); err != nil { + return err + } + } + return nil +} + +func (bs *BatchState) RunFinalize(ctx context.Context) error { + for _, action := range bs.Finalize { + if err := action(ctx); err != nil { + return err + } + } + return nil +} diff --git a/pkg/core/batch_state_test.go b/pkg/core/batch_state_test.go new file mode 100644 index 0000000000..cf8c68c59e --- /dev/null +++ b/pkg/core/batch_state_test.go @@ -0,0 +1,68 @@ +// Copyright © 2022 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 core + +import ( + "context" + "fmt" + "testing" + + "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/stretchr/testify/assert" +) + +func TestBatchStateFinalizers(t *testing.T) { + bs := BatchState{} + + run1 := false + bs.AddPreFinalize(func(ctx context.Context) error { run1 = true; return nil }) + run2 := false + bs.AddFinalize(func(ctx context.Context) error { run2 = true; return nil }) + + bs.RunPreFinalize(context.Background()) + bs.RunFinalize(context.Background()) + assert.True(t, run1) + assert.True(t, run2) +} + +func TestBatchStateFinalizerErrors(t *testing.T) { + bs := BatchState{} + + bs.AddPreFinalize(func(ctx context.Context) error { return fmt.Errorf("pop") }) + bs.AddFinalize(func(ctx context.Context) error { return fmt.Errorf("pop") }) + + err := bs.RunPreFinalize(context.Background()) + assert.EqualError(t, err, "pop") + err = bs.RunFinalize(context.Background()) + assert.EqualError(t, err, "pop") +} + +func TestBatchStateIdentities(t *testing.T) { + bs := BatchState{ + PendingConfirms: make(map[fftypes.UUID]*Message), + } + + id := fftypes.NewUUID() + msg := &Message{} + bs.AddPendingConfirm(id, msg) + + did := "did:firefly:id1" + bs.AddConfirmedDIDClaim(did) + + assert.Equal(t, msg, bs.PendingConfirms[*id]) + assert.Equal(t, bs.ConfirmedDIDClaims[0], did) +}