diff --git a/docs/reference/types/event.md b/docs/reference/types/event.md index 2e5c20b2c6..6fbc38dbd4 100644 --- a/docs/reference/types/event.md +++ b/docs/reference/types/event.md @@ -41,7 +41,7 @@ nav_order: 2 |------------|-------------|------| | `id` | The UUID assigned to this event by your local FireFly node | [`UUID`](simpletypes#uuid) | | `sequence` | A sequence indicating the order in which events are delivered to your application. Assure to be unique per event in your local FireFly database (unlike the created timestamp) | `int64` | -| `type` | All interesting activity in FireFly is emitted as a FireFly event, of a given type. The 'type' combined with the 'reference' can be used to determine how to process the event within your application | `FFEnum`:
`"transaction_submitted"`
`"message_confirmed"`
`"message_rejected"`
`"datatype_confirmed"`
`"identity_confirmed"`
`"identity_updated"`
`"token_pool_confirmed"`
`"token_pool_op_failed"`
`"token_transfer_confirmed"`
`"token_transfer_op_failed"`
`"token_approval_confirmed"`
`"token_approval_op_failed"`
`"contract_interface_confirmed"`
`"contract_api_confirmed"`
`"blockchain_event_received"`
`"blockchain_invoke_op_succeeded"`
`"blockchain_invoke_op_failed"` | +| `type` | All interesting activity in FireFly is emitted as a FireFly event, of a given type. The 'type' combined with the 'reference' can be used to determine how to process the event within your application | `FFEnum`:
`"transaction_submitted"`
`"message_confirmed"`
`"message_rejected"`
`"datatype_confirmed"`
`"identity_confirmed"`
`"identity_updated"`
`"token_pool_confirmed"`
`"token_pool_op_failed"`
`"token_transfer_confirmed"`
`"token_transfer_op_failed"`
`"token_approval_confirmed"`
`"token_approval_op_failed"`
`"contract_interface_confirmed"`
`"contract_api_confirmed"`
`"blockchain_event_received"`
`"blockchain_invoke_op_succeeded"`
`"blockchain_invoke_op_failed"`
`"blockchain_contract_deploy_op_succeeded"`
`"blockchain_contract_deploy_op_failed"` | | `namespace` | The namespace of the event. Your application must subscribe to events within a namespace | `string` | | `reference` | The UUID of an resource that is the subject of this event. The event type determines what type of resource is referenced, and whether this field might be unset | [`UUID`](simpletypes#uuid) | | `correlator` | For message events, this is the 'header.cid' field from the referenced message. For certain other event types, a secondary object is referenced such as a token pool | [`UUID`](simpletypes#uuid) | diff --git a/docs/reference/types/message.md b/docs/reference/types/message.md index cfd55a5ce1..3e1defe598 100644 --- a/docs/reference/types/message.md +++ b/docs/reference/types/message.md @@ -72,7 +72,7 @@ nav_order: 15 | `id` | The UUID of the message. Unique to each message | [`UUID`](simpletypes#uuid) | | `cid` | The correlation ID of the message. Set this when a message is a response to another message | [`UUID`](simpletypes#uuid) | | `type` | The type of the message | `FFEnum`:
`"definition"`
`"broadcast"`
`"private"`
`"groupinit"`
`"transfer_broadcast"`
`"transfer_private"` | -| `txtype` | The type of transaction used to order/deliver this message | `FFEnum`:
`"none"`
`"unpinned"`
`"batch_pin"`
`"network_action"`
`"token_pool"`
`"token_transfer"`
`"contract_invoke"`
`"token_approval"`
`"data_publish"` | +| `txtype` | The type of transaction used to order/deliver this message | `FFEnum`:
`"none"`
`"unpinned"`
`"batch_pin"`
`"network_action"`
`"token_pool"`
`"token_transfer"`
`"contract_deploy"`
`"contract_invoke"`
`"token_approval"`
`"data_publish"` | | `author` | The DID of identity of the submitter | `string` | | `key` | The on-chain signing key used to sign the transaction | `string` | | `created` | The creation time of the message | [`FFTime`](simpletypes#fftime) | diff --git a/docs/reference/types/operation.md b/docs/reference/types/operation.md index c182b7c7ac..d9dc7d54e8 100644 --- a/docs/reference/types/operation.md +++ b/docs/reference/types/operation.md @@ -47,7 +47,7 @@ nav_order: 7 | `id` | The UUID of the operation | [`UUID`](simpletypes#uuid) | | `namespace` | The namespace of the operation | `string` | | `tx` | The UUID of the FireFly transaction the operation is part of | [`UUID`](simpletypes#uuid) | -| `type` | The type of the operation | `FFEnum`:
`"blockchain_pin_batch"`
`"blockchain_network_action"`
`"blockchain_invoke"`
`"sharedstorage_upload_batch"`
`"sharedstorage_upload_blob"`
`"sharedstorage_upload_value"`
`"sharedstorage_download_batch"`
`"sharedstorage_download_blob"`
`"dataexchange_send_batch"`
`"dataexchange_send_blob"`
`"token_create_pool"`
`"token_activate_pool"`
`"token_transfer"`
`"token_approval"` | +| `type` | The type of the operation | `FFEnum`:
`"blockchain_pin_batch"`
`"blockchain_network_action"`
`"blockchain_deploy"`
`"blockchain_invoke"`
`"sharedstorage_upload_batch"`
`"sharedstorage_upload_blob"`
`"sharedstorage_upload_value"`
`"sharedstorage_download_batch"`
`"sharedstorage_download_blob"`
`"dataexchange_send_batch"`
`"dataexchange_send_blob"`
`"token_create_pool"`
`"token_activate_pool"`
`"token_transfer"`
`"token_approval"` | | `status` | The current status of the operation | `OpStatus` | | `plugin` | The plugin responsible for performing the operation | `string` | | `input` | The input to this operation | [`JSONObject`](simpletypes#jsonobject) | diff --git a/docs/reference/types/transaction.md b/docs/reference/types/transaction.md index d7a20531b5..87b931491f 100644 --- a/docs/reference/types/transaction.md +++ b/docs/reference/types/transaction.md @@ -40,7 +40,7 @@ nav_order: 6 |------------|-------------|------| | `id` | The UUID of the FireFly transaction | [`UUID`](simpletypes#uuid) | | `namespace` | The namespace of the FireFly transaction | `string` | -| `type` | The type of the FireFly transaction | `FFEnum`:
`"none"`
`"unpinned"`
`"batch_pin"`
`"network_action"`
`"token_pool"`
`"token_transfer"`
`"contract_invoke"`
`"token_approval"`
`"data_publish"` | +| `type` | The type of the FireFly transaction | `FFEnum`:
`"none"`
`"unpinned"`
`"batch_pin"`
`"network_action"`
`"token_pool"`
`"token_transfer"`
`"contract_deploy"`
`"contract_invoke"`
`"token_approval"`
`"data_publish"` | | `created` | The time the transaction was created on this node. Note the transaction is individually created with the same UUID on each participant in the FireFly transaction | [`FFTime`](simpletypes#fftime) | | `blockchainIds` | The blockchain transaction ID, in the format specific to the blockchain involved in the transaction. Not all FireFly transactions include a blockchain. FireFly transactions are extensible to support multiple blockchain transactions | `string[]` | diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index a82c1428b8..d8ca727643 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -673,6 +673,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -746,6 +747,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -1978,6 +1980,207 @@ paths: description: "" tags: - Default Namespace + /contracts/deploy: + post: + description: Deploy a new smart contract + operationId: postContractDeploy + parameters: + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + example: "true" + type: string + - description: Server-side request timeout (milliseconds, or set a custom suffix + like 10s) + in: header + name: Request-Timeout + schema: + default: 2m0s + type: string + requestBody: + content: + application/json: + schema: + properties: + contract: + description: The smart contract to deploy. This should be pre-compiled + if required by the blockchain connector + definition: + description: The definition of the smart contract + input: + description: An optional array of inputs passed to the smart contract's + constructor, if applicable + items: + description: An optional array of inputs passed to the smart contract's + constructor, if applicable + type: array + key: + description: The blockchain signing key that will be used to deploy + the contract. Defaults to the first signing key of the organization + that operates the node + type: string + options: + additionalProperties: + description: A map of named inputs that will be passed through + to the blockchain connector + description: A map of named inputs that will be passed through to + the blockchain connector + type: object + type: object + responses: + "200": + content: + application/json: + schema: + properties: + created: + description: The time the operation was created + format: date-time + type: string + error: + description: Any error reported back from the plugin for this + operation + type: string + id: + description: The UUID of the operation + format: uuid + type: string + input: + additionalProperties: + description: The input to this operation + description: The input to this operation + type: object + namespace: + description: The namespace of the operation + type: string + output: + additionalProperties: + description: Any output reported back from the plugin for this + operation + description: Any output reported back from the plugin for this + operation + type: object + plugin: + description: The plugin responsible for performing the operation + type: string + retry: + description: If this operation was initiated as a retry to a previous + operation, this field points to the UUID of the operation being + retried + format: uuid + type: string + status: + description: The current status of the operation + type: string + tx: + description: The UUID of the FireFly transaction the operation + is part of + format: uuid + type: string + type: + description: The type of the operation + enum: + - blockchain_pin_batch + - blockchain_network_action + - blockchain_deploy + - blockchain_invoke + - sharedstorage_upload_batch + - sharedstorage_upload_blob + - sharedstorage_upload_value + - sharedstorage_download_batch + - sharedstorage_download_blob + - dataexchange_send_batch + - dataexchange_send_blob + - token_create_pool + - token_activate_pool + - token_transfer + - token_approval + type: string + updated: + description: The last update time of the operation + format: date-time + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + created: + description: The time the operation was created + format: date-time + type: string + error: + description: Any error reported back from the plugin for this + operation + type: string + id: + description: The UUID of the operation + format: uuid + type: string + input: + additionalProperties: + description: The input to this operation + description: The input to this operation + type: object + namespace: + description: The namespace of the operation + type: string + output: + additionalProperties: + description: Any output reported back from the plugin for this + operation + description: Any output reported back from the plugin for this + operation + type: object + plugin: + description: The plugin responsible for performing the operation + type: string + retry: + description: If this operation was initiated as a retry to a previous + operation, this field points to the UUID of the operation being + retried + format: uuid + type: string + status: + description: The current status of the operation + type: string + tx: + description: The UUID of the FireFly transaction the operation + is part of + format: uuid + type: string + type: + description: The type of the operation + enum: + - blockchain_pin_batch + - blockchain_network_action + - blockchain_deploy + - blockchain_invoke + - sharedstorage_upload_batch + - sharedstorage_upload_blob + - sharedstorage_upload_value + - sharedstorage_download_batch + - sharedstorage_download_blob + - dataexchange_send_batch + - dataexchange_send_blob + - token_create_pool + - token_activate_pool + - token_transfer + - token_approval + type: string + updated: + description: The last update time of the operation + format: date-time + type: string + type: object + description: Success + default: + description: "" + tags: + - Default Namespace /contracts/interfaces: get: description: Gets a list of contract interfaces that have been published @@ -3305,6 +3508,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -3378,6 +3582,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -4978,6 +5183,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -5796,6 +6002,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object type: array @@ -5906,6 +6114,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object description: Success @@ -7380,6 +7590,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -7623,6 +7834,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -7929,6 +8141,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object type: array @@ -7995,6 +8209,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8101,6 +8316,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8208,6 +8424,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8336,6 +8553,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8512,6 +8730,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8625,6 +8844,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8759,6 +8979,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -8931,6 +9152,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -9124,6 +9346,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -10021,6 +10244,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -10094,6 +10318,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -11509,6 +11734,214 @@ paths: description: "" tags: - Non-Default Namespace + /namespaces/{ns}/contracts/deploy: + post: + description: Deploy a new smart contract + operationId: postContractDeployNamespace + parameters: + - description: The namespace which scopes this request + in: path + name: ns + required: true + schema: + example: default + type: string + - description: When true the HTTP request blocks until the message is confirmed + in: query + name: confirm + schema: + example: "true" + type: string + - description: Server-side request timeout (milliseconds, or set a custom suffix + like 10s) + in: header + name: Request-Timeout + schema: + default: 2m0s + type: string + requestBody: + content: + application/json: + schema: + properties: + contract: + description: The smart contract to deploy. This should be pre-compiled + if required by the blockchain connector + definition: + description: The definition of the smart contract + input: + description: An optional array of inputs passed to the smart contract's + constructor, if applicable + items: + description: An optional array of inputs passed to the smart contract's + constructor, if applicable + type: array + key: + description: The blockchain signing key that will be used to deploy + the contract. Defaults to the first signing key of the organization + that operates the node + type: string + options: + additionalProperties: + description: A map of named inputs that will be passed through + to the blockchain connector + description: A map of named inputs that will be passed through to + the blockchain connector + type: object + type: object + responses: + "200": + content: + application/json: + schema: + properties: + created: + description: The time the operation was created + format: date-time + type: string + error: + description: Any error reported back from the plugin for this + operation + type: string + id: + description: The UUID of the operation + format: uuid + type: string + input: + additionalProperties: + description: The input to this operation + description: The input to this operation + type: object + namespace: + description: The namespace of the operation + type: string + output: + additionalProperties: + description: Any output reported back from the plugin for this + operation + description: Any output reported back from the plugin for this + operation + type: object + plugin: + description: The plugin responsible for performing the operation + type: string + retry: + description: If this operation was initiated as a retry to a previous + operation, this field points to the UUID of the operation being + retried + format: uuid + type: string + status: + description: The current status of the operation + type: string + tx: + description: The UUID of the FireFly transaction the operation + is part of + format: uuid + type: string + type: + description: The type of the operation + enum: + - blockchain_pin_batch + - blockchain_network_action + - blockchain_deploy + - blockchain_invoke + - sharedstorage_upload_batch + - sharedstorage_upload_blob + - sharedstorage_upload_value + - sharedstorage_download_batch + - sharedstorage_download_blob + - dataexchange_send_batch + - dataexchange_send_blob + - token_create_pool + - token_activate_pool + - token_transfer + - token_approval + type: string + updated: + description: The last update time of the operation + format: date-time + type: string + type: object + description: Success + "202": + content: + application/json: + schema: + properties: + created: + description: The time the operation was created + format: date-time + type: string + error: + description: Any error reported back from the plugin for this + operation + type: string + id: + description: The UUID of the operation + format: uuid + type: string + input: + additionalProperties: + description: The input to this operation + description: The input to this operation + type: object + namespace: + description: The namespace of the operation + type: string + output: + additionalProperties: + description: Any output reported back from the plugin for this + operation + description: Any output reported back from the plugin for this + operation + type: object + plugin: + description: The plugin responsible for performing the operation + type: string + retry: + description: If this operation was initiated as a retry to a previous + operation, this field points to the UUID of the operation being + retried + format: uuid + type: string + status: + description: The current status of the operation + type: string + tx: + description: The UUID of the FireFly transaction the operation + is part of + format: uuid + type: string + type: + description: The type of the operation + enum: + - blockchain_pin_batch + - blockchain_network_action + - blockchain_deploy + - blockchain_invoke + - sharedstorage_upload_batch + - sharedstorage_upload_blob + - sharedstorage_upload_value + - sharedstorage_download_batch + - sharedstorage_download_blob + - dataexchange_send_batch + - dataexchange_send_blob + - token_create_pool + - token_activate_pool + - token_transfer + - token_approval + type: string + updated: + description: The last update time of the operation + format: date-time + type: string + type: object + description: Success + default: + description: "" + tags: + - Non-Default Namespace /namespaces/{ns}/contracts/interfaces: get: description: Gets a list of contract interfaces that have been published @@ -12878,6 +13311,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -12951,6 +13385,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -14628,6 +15063,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -15488,6 +15924,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object type: array @@ -15605,6 +16043,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object description: Success @@ -17149,6 +17589,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -17399,6 +17840,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -17719,6 +18161,8 @@ paths: - blockchain_event_received - blockchain_invoke_op_succeeded - blockchain_invoke_op_failed + - blockchain_contract_deploy_op_succeeded + - blockchain_contract_deploy_op_failed type: string type: object type: array @@ -17792,6 +18236,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -17943,6 +18388,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18056,6 +18502,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18190,6 +18637,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18373,6 +18821,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18486,6 +18935,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18620,6 +19070,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18799,6 +19250,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -18992,6 +19444,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -20862,6 +21315,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -20966,6 +21420,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -21075,6 +21530,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -24045,6 +24501,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -24484,6 +24941,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -25701,6 +26159,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -26170,6 +26629,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -26300,6 +26760,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -26492,6 +26953,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -28590,6 +29052,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -28687,6 +29150,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -28789,6 +29253,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob @@ -31654,6 +32119,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -32071,6 +32537,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -33253,6 +33720,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -33708,6 +34176,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -33831,6 +34300,7 @@ paths: - network_action - token_pool - token_transfer + - contract_deploy - contract_invoke - token_approval - data_publish @@ -34009,6 +34479,7 @@ paths: enum: - blockchain_pin_batch - blockchain_network_action + - blockchain_deploy - blockchain_invoke - sharedstorage_upload_batch - sharedstorage_upload_blob diff --git a/internal/apiserver/route_post_contract_deploy.go b/internal/apiserver/route_post_contract_deploy.go new file mode 100644 index 0000000000..d2d1013455 --- /dev/null +++ b/internal/apiserver/route_post_contract_deploy.go @@ -0,0 +1,52 @@ +// 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 apiserver + +import ( + "net/http" + "strings" + + "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" +) + +var postContractDeploy = &ffapi.Route{ + Name: "postContractDeploy", + Path: "contracts/deploy", + Method: http.MethodPost, + PathParams: nil, + QueryParams: []*ffapi.QueryParam{ + {Name: "confirm", Description: coremsgs.APIConfirmQueryParam, IsBool: true, Example: "true"}, + }, + Description: coremsgs.APIEndpointsPostContractDeploy, + JSONInputValue: func() interface{} { return &core.ContractDeployRequest{} }, + JSONOutputValue: func() interface{} { return &core.Operation{} }, + JSONOutputCodes: []int{http.StatusOK, http.StatusAccepted}, + Extensions: &coreExtensions{ + EnabledIf: func(or orchestrator.Orchestrator) bool { + return or.Contracts() != nil + }, + CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { + waitConfirm := strings.EqualFold(r.QP["confirm"], "true") + r.SuccessStatus = syncRetcode(waitConfirm) + req := r.Input.(*core.ContractDeployRequest) + return cr.or.Contracts().DeployContract(cr.ctx, req, waitConfirm) + }, + }, +} diff --git a/internal/apiserver/route_post_contract_deploy_test.go b/internal/apiserver/route_post_contract_deploy_test.go new file mode 100644 index 0000000000..85ec40835f --- /dev/null +++ b/internal/apiserver/route_post_contract_deploy_test.go @@ -0,0 +1,49 @@ +// 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 apiserver + +import ( + "bytes" + "encoding/json" + "net/http/httptest" + "testing" + + "github.com/hyperledger/firefly/mocks/contractmocks" + "github.com/hyperledger/firefly/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestPostContractDeploy(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mcm := &contractmocks.Manager{} + o.On("Contracts").Return(mcm) + input := core.Datatype{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/deploy", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mcm.On("DeployContract", mock.Anything, mock.MatchedBy(func(req *core.ContractDeployRequest) bool { + return true + }), false).Return("banana", nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/routes.go b/internal/apiserver/routes.go index 30514d745e..3a43852b6b 100644 --- a/internal/apiserver/routes.go +++ b/internal/apiserver/routes.go @@ -127,6 +127,7 @@ var routes = append( postContractAPIQuery, postContractAPIListeners, postContractInterfaceGenerate, + postContractDeploy, postContractInvoke, postContractQuery, postData, diff --git a/internal/apiserver/swagger_generate_test.go b/internal/apiserver/swagger_generate_test.go index ca69b07c2a..a313a2ab11 100644 --- a/internal/apiserver/swagger_generate_test.go +++ b/internal/apiserver/swagger_generate_test.go @@ -44,7 +44,7 @@ func TestDownloadSwaggerYAML(t *testing.T) { defer s.Close() res, err := http.Get(fmt.Sprintf("http://%s/api/swagger.yaml", s.Listener.Addr())) - assert.NoError(t, err) + assert.Nil(t, err) b, _ := ioutil.ReadAll(res.Body) assert.Equal(t, 200, res.StatusCode, string(b)) doc, err := openapi3.NewLoader().LoadFromData(b) diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go index 95ec901593..5ccf8716de 100644 --- a/internal/blockchain/ethereum/ethereum.go +++ b/internal/blockchain/ethereum/ethereum.go @@ -545,6 +545,10 @@ func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, if signingKey != "" { body["from"] = signingKey } + return e.applyOptions(ctx, body, options) +} + +func (e *Ethereum) applyOptions(ctx context.Context, body, options map[string]interface{}) (map[string]interface{}, error) { for k, v := range options { // Set the new field if it's not already set. Do not allow overriding of existing fields if _, ok := body[k]; !ok { @@ -679,6 +683,45 @@ func (e *Ethereum) SubmitNetworkAction(ctx context.Context, nsOpID string, signi return e.invokeContractMethod(ctx, ethLocation.Address, signingKey, method, nsOpID, input, nil) } +func (e *Ethereum) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error { + if e.metrics.IsMetricsEnabled() { + e.metrics.BlockchainContractDeployment() + } + headers := EthconnectMessageHeaders{ + Type: "DeployContract", + ID: nsOpID, + } + body := map[string]interface{}{ + "headers": headers, + "from": signingKey, + "params": input, + "definition": definition, + "contract": contract, + } + if signingKey != "" { + body["from"] = signingKey + } + body, err := e.applyOptions(ctx, body, options) + if err != nil { + return err + } + + client := e.fftmClient + if client == nil { + client = e.client + } + var resErr ethError + res, err := client.R(). + SetContext(ctx). + SetBody(body). + SetError(&resErr). + Post("/") + if err != nil || !res.IsSuccess() { + return wrapError(ctx, &resErr, res, err) + } + return nil +} + func (e *Ethereum) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error { ethereumLocation, err := parseContractLocation(ctx, location) if err != nil { diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go index 0369b0585c..d7e2655624 100644 --- a/internal/blockchain/ethereum/ethereum_test.go +++ b/internal/blockchain/ethereum/ethereum_test.go @@ -85,6 +85,7 @@ func newTestEthereum() (*Ethereum, func()) { mm := &metricsmocks.Manager{} mm.On("IsMetricsEnabled").Return(true) mm.On("BlockchainTransaction", mock.Anything, mock.Anything).Return(nil) + mm.On("BlockchainContractDeployment", mock.Anything, mock.Anything).Return(nil) mm.On("BlockchainQuery", mock.Anything, mock.Anything).Return(nil) e := &Ethereum{ ctx: ctx, @@ -2225,6 +2226,102 @@ func TestHandleMessageContractEventError(t *testing.T) { em.AssertExpectations(t) } +func TestDeployContractOK(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + signingKey := ethHexFormatB32(fftypes.NewRandB32()) + input := []interface{}{ + float64(1), + "1000000000000000000000000", + } + options := map[string]interface{}{ + "customOption": "customValue", + } + definitionBytes, err := json.Marshal([]interface{}{}) + contractBytes, err := json.Marshal("0x123456") + assert.NoError(t, err) + httpmock.RegisterResponder("POST", `http://localhost:12345/`, + func(req *http.Request) (*http.Response, error) { + var body map[string]interface{} + json.NewDecoder(req.Body).Decode(&body) + params := body["params"].([]interface{}) + headers := body["headers"].(map[string]interface{}) + assert.Equal(t, "DeployContract", headers["type"]) + assert.Equal(t, float64(1), params[0]) + assert.Equal(t, "1000000000000000000000000", params[1]) + assert.Equal(t, body["customOption"].(string), "customValue") + return httpmock.NewJsonResponderOrPanic(200, "")(req) + }) + err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options) + assert.NoError(t, err) +} + +func TestDeployContractInvalidOption(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + signingKey := ethHexFormatB32(fftypes.NewRandB32()) + input := []interface{}{ + float64(1), + "1000000000000000000000000", + } + options := map[string]interface{}{ + "contract": "not really a contract", + } + definitionBytes, err := json.Marshal([]interface{}{}) + contractBytes, err := json.Marshal("0x123456") + assert.NoError(t, err) + httpmock.RegisterResponder("POST", `http://localhost:12345/`, + func(req *http.Request) (*http.Response, error) { + var body map[string]interface{} + json.NewDecoder(req.Body).Decode(&body) + params := body["params"].([]interface{}) + headers := body["headers"].(map[string]interface{}) + assert.Equal(t, "DeployContract", headers["type"]) + assert.Equal(t, float64(1), params[0]) + assert.Equal(t, "1000000000000000000000000", params[1]) + assert.Equal(t, body["customOption"].(string), "customValue") + return httpmock.NewJsonResponderOrPanic(400, "pop")(req) + }) + err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options) + assert.Regexp(t, "FF10398", err) +} + +func TestDeployContractError(t *testing.T) { + e, cancel := newTestEthereum() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + signingKey := ethHexFormatB32(fftypes.NewRandB32()) + input := []interface{}{ + float64(1), + "1000000000000000000000000", + } + options := map[string]interface{}{ + "customOption": "customValue", + } + definitionBytes, err := json.Marshal([]interface{}{}) + contractBytes, err := json.Marshal("0x123456") + assert.NoError(t, err) + httpmock.RegisterResponder("POST", `http://localhost:12345/`, + func(req *http.Request) (*http.Response, error) { + var body map[string]interface{} + json.NewDecoder(req.Body).Decode(&body) + params := body["params"].([]interface{}) + headers := body["headers"].(map[string]interface{}) + assert.Equal(t, "DeployContract", headers["type"]) + assert.Equal(t, float64(1), params[0]) + assert.Equal(t, "1000000000000000000000000", params[1]) + assert.Equal(t, body["customOption"].(string), "customValue") + return httpmock.NewJsonResponderOrPanic(400, "pop")(req) + }) + err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options) + assert.Regexp(t, "FF10111", err) +} + func TestInvokeContractOK(t *testing.T) { e, cancel := newTestEthereum() defer cancel() diff --git a/internal/blockchain/fabric/fabric.go b/internal/blockchain/fabric/fabric.go index 1c3527d6f2..d184783947 100644 --- a/internal/blockchain/fabric/fabric.go +++ b/internal/blockchain/fabric/fabric.go @@ -718,6 +718,10 @@ func (f *Fabric) buildFabconnectRequestBody(ctx context.Context, channel, chainc return body, nil } +func (f *Fabric) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error { + return i18n.NewError(ctx, coremsgs.MsgNotSupportedByBlockchainPlugin) +} + func (f *Fabric) InvokeContract(ctx context.Context, nsOpID string, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error { fabricOnChainLocation, err := parseContractLocation(ctx, location) if err != nil { diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go index e28ab97177..3e63d61221 100644 --- a/internal/blockchain/fabric/fabric_test.go +++ b/internal/blockchain/fabric/fabric_test.go @@ -1992,6 +1992,38 @@ func TestInvokeContractOK(t *testing.T) { assert.NoError(t, err) } +func TestDeployContractOK(t *testing.T) { + e, cancel := newTestFabric() + defer cancel() + httpmock.ActivateNonDefault(e.client.GetClient()) + defer httpmock.DeactivateAndReset() + signingKey := fftypes.NewRandB32().String() + input := []interface{}{ + float64(1), + "1000000000000000000000000", + } + options := map[string]interface{}{ + "contract": "not really a contract", + } + definitionBytes, err := json.Marshal([]interface{}{}) + contractBytes, err := json.Marshal("0x123456") + assert.NoError(t, err) + httpmock.RegisterResponder("POST", `http://localhost:12345/`, + func(req *http.Request) (*http.Response, error) { + var body map[string]interface{} + json.NewDecoder(req.Body).Decode(&body) + params := body["params"].([]interface{}) + headers := body["headers"].(map[string]interface{}) + assert.Equal(t, "DeployContract", headers["type"]) + assert.Equal(t, float64(1), params[0]) + assert.Equal(t, "1000000000000000000000000", params[1]) + assert.Equal(t, body["customOption"].(string), "customValue") + return httpmock.NewJsonResponderOrPanic(400, "pop")(req) + }) + err = e.DeployContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(definitionBytes), fftypes.JSONAnyPtrBytes(contractBytes), input, options) + assert.Regexp(t, "FF10429", err) +} + func TestInvokeContractBadSchema(t *testing.T) { e, cancel := newTestFabric() defer cancel() diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index 42288c977b..9207fefe48 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -44,6 +44,7 @@ type Manager interface { GetFFIs(ctx context.Context, filter database.AndFilter) ([]*fftypes.FFI, *database.FilterResult, error) ResolveFFI(ctx context.Context, ffi *fftypes.FFI) error + DeployContract(ctx context.Context, req *core.ContractDeployRequest, waitConfirm bool) (interface{}, 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) @@ -98,6 +99,7 @@ func NewContractManager(ctx context.Context, ns string, di database.Plugin, bi b om.RegisterHandler(ctx, cm, []core.OpType{ core.OpTypeBlockchainInvoke, + core.OpTypeBlockchainContractDeploy, }) return cm, nil @@ -176,12 +178,58 @@ func (cm *contractManager) writeInvokeTransaction(ctx context.Context, req *core cm.namespace, txid, core.OpTypeBlockchainInvoke) - if err = addBlockchainInvokeInputs(op, req); err == nil { + if err = addBlockchainReqInputs(op, req); err == nil { err = cm.operations.AddOrReuseOperation(ctx, op) } return op, err } +func (cm *contractManager) writeDeployTransaction(ctx context.Context, req *core.ContractDeployRequest) (*core.Operation, error) { + txid, err := cm.txHelper.SubmitNewTransaction(ctx, core.TransactionTypeContractDeploy) + if err != nil { + return nil, err + } + + op := core.NewOperation( + cm.blockchain, + cm.namespace, + txid, + core.OpTypeBlockchainContractDeploy) + if err = addBlockchainReqInputs(op, req); err == nil { + err = cm.operations.AddOrReuseOperation(ctx, op) + } + return op, err +} + +func (cm *contractManager) DeployContract(ctx context.Context, req *core.ContractDeployRequest, waitConfirm bool) (res interface{}, err error) { + req.Key, err = cm.identity.NormalizeSigningKey(ctx, req.Key, identity.KeyNormalizationBlockchainPlugin) + if err != nil { + return nil, err + } + + var op *core.Operation + err = cm.database.RunAsGroup(ctx, func(ctx context.Context) (err error) { + op, err = cm.writeDeployTransaction(ctx, req) + if err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + + send := func(ctx context.Context) error { + _, err := cm.operations.RunOperation(ctx, opBlockchainContractDeploy(op, req)) + return err + } + if waitConfirm { + return cm.syncasync.WaitForDeployOperation(ctx, op.ID, send) + } + err = send(ctx) + return op, err +} + func (cm *contractManager) InvokeContract(ctx context.Context, req *core.ContractCallRequest, waitConfirm bool) (res interface{}, err error) { req.Key, err = cm.identity.NormalizeSigningKey(ctx, req.Key, identity.KeyNormalizationBlockchainPlugin) if err != nil { diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index cab669030d..ec844ca171 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -18,6 +18,7 @@ package contracts import ( "context" + "errors" "fmt" "testing" "time" @@ -1317,6 +1318,125 @@ func TestGetFFIs(t *testing.T) { assert.NoError(t, err) } +func TestDeployContract(t *testing.T) { + cm := newTestContractManager() + mim := cm.identity.(*identitymanagermocks.Manager) + mdi := cm.database.(*databasemocks.Plugin) + mth := cm.txHelper.(*txcommonmocks.Helper) + mom := cm.operations.(*operationmocks.Manager) + signingKey := "0x2468" + req := &core.ContractDeployRequest{ + Key: signingKey, + Definition: fftypes.JSONAnyPtr("[]"), + Contract: fftypes.JSONAnyPtr("\"0x123456\""), + Input: []interface{}{"one", "two", "three"}, + } + + mth.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeContractDeploy).Return(fftypes.NewUUID(), nil) + mim.On("NormalizeSigningKey", mock.Anything, signingKey, identity.KeyNormalizationBlockchainPlugin).Return("key-resolved", nil) + mom.On("AddOrReuseOperation", mock.Anything, mock.MatchedBy(func(op *core.Operation) bool { + return op.Namespace == "ns1" && op.Type == core.OpTypeBlockchainContractDeploy && op.Plugin == "mockblockchain" + })).Return(nil) + mom.On("RunOperation", mock.Anything, mock.MatchedBy(func(op *core.PreparedOperation) bool { + data := op.Data.(blockchainContractDeployData) + return op.Type == core.OpTypeBlockchainContractDeploy && data.Request == req + })).Return(nil, nil) + + _, err := cm.DeployContract(context.Background(), req, false) + + assert.NoError(t, err) + + mth.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mom.AssertExpectations(t) +} + +func TestDeployContractSync(t *testing.T) { + cm := newTestContractManager() + mim := cm.identity.(*identitymanagermocks.Manager) + mdi := cm.database.(*databasemocks.Plugin) + mth := cm.txHelper.(*txcommonmocks.Helper) + mom := cm.operations.(*operationmocks.Manager) + sam := cm.syncasync.(*syncasyncmocks.Bridge) + signingKey := "0x2468" + req := &core.ContractDeployRequest{ + Key: signingKey, + Definition: fftypes.JSONAnyPtr("[]"), + Contract: fftypes.JSONAnyPtr("\"0x123456\""), + Input: []interface{}{"one", "two", "three"}, + } + + mth.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeContractDeploy).Return(fftypes.NewUUID(), nil) + mim.On("NormalizeSigningKey", mock.Anything, signingKey, identity.KeyNormalizationBlockchainPlugin).Return("key-resolved", nil) + mom.On("AddOrReuseOperation", mock.Anything, mock.MatchedBy(func(op *core.Operation) bool { + return op.Namespace == "ns1" && op.Type == core.OpTypeBlockchainContractDeploy && op.Plugin == "mockblockchain" + })).Return(nil) + + sam.On("WaitForDeployOperation", mock.Anything, mock.Anything, mock.Anything).Return(&core.Operation{Status: core.OpStatusSucceeded}, nil) + + _, err := cm.DeployContract(context.Background(), req, true) + + assert.NoError(t, err) + + mth.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mom.AssertExpectations(t) +} + +func TestDeployContractNormalizeSigningKeyFail(t *testing.T) { + cm := newTestContractManager() + mim := cm.identity.(*identitymanagermocks.Manager) + mdi := cm.database.(*databasemocks.Plugin) + mth := cm.txHelper.(*txcommonmocks.Helper) + mom := cm.operations.(*operationmocks.Manager) + signingKey := "0x2468" + req := &core.ContractDeployRequest{ + Key: signingKey, + Definition: fftypes.JSONAnyPtr("[]"), + Contract: fftypes.JSONAnyPtr("\"0x123456\""), + Input: []interface{}{"one", "two", "three"}, + } + + mim.On("NormalizeSigningKey", mock.Anything, signingKey, identity.KeyNormalizationBlockchainPlugin).Return("", errors.New("pop")) + _, err := cm.DeployContract(context.Background(), req, false) + + assert.Regexp(t, "pop", err) + + mth.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mom.AssertExpectations(t) +} + +func TestDeployContractSubmitNewTransactionFail(t *testing.T) { + cm := newTestContractManager() + mim := cm.identity.(*identitymanagermocks.Manager) + mdi := cm.database.(*databasemocks.Plugin) + mth := cm.txHelper.(*txcommonmocks.Helper) + mom := cm.operations.(*operationmocks.Manager) + signingKey := "0x2468" + req := &core.ContractDeployRequest{ + Key: signingKey, + Definition: fftypes.JSONAnyPtr("[]"), + Contract: fftypes.JSONAnyPtr("\"0x123456\""), + Input: []interface{}{"one", "two", "three"}, + } + + mth.On("SubmitNewTransaction", mock.Anything, core.TransactionTypeContractDeploy).Return(nil, errors.New("pop")) + mim.On("NormalizeSigningKey", mock.Anything, signingKey, identity.KeyNormalizationBlockchainPlugin).Return("key-resolved", nil) + + _, err := cm.DeployContract(context.Background(), req, false) + + assert.Regexp(t, "pop", err) + + mth.AssertExpectations(t) + mim.AssertExpectations(t) + mdi.AssertExpectations(t) + mom.AssertExpectations(t) +} + func TestInvokeContract(t *testing.T) { cm := newTestContractManager() mim := cm.identity.(*identitymanagermocks.Manager) diff --git a/internal/contracts/operations.go b/internal/contracts/operations.go index 889860c99c..25823586ab 100644 --- a/internal/contracts/operations.go +++ b/internal/contracts/operations.go @@ -30,7 +30,11 @@ type blockchainInvokeData struct { Request *core.ContractCallRequest `json:"request"` } -func addBlockchainInvokeInputs(op *core.Operation, req *core.ContractCallRequest) (err error) { +type blockchainContractDeployData struct { + Request *core.ContractDeployRequest `json:"request"` +} + +func addBlockchainReqInputs(op *core.Operation, req interface{}) (err error) { var reqJSON []byte if reqJSON, err = json.Marshal(req); err == nil { err = json.Unmarshal(reqJSON, &op.Input) @@ -38,6 +42,15 @@ func addBlockchainInvokeInputs(op *core.Operation, req *core.ContractCallRequest return err } +func retrieveBlockchainDeployInputs(ctx context.Context, op *core.Operation) (*core.ContractDeployRequest, error) { + var req core.ContractDeployRequest + s := op.Input.String() + if err := json.Unmarshal([]byte(s), &req); err != nil { + return nil, i18n.WrapError(ctx, err, i18n.MsgJSONObjectParseFailed, s) + } + return &req, nil +} + func retrieveBlockchainInvokeInputs(ctx context.Context, op *core.Operation) (*core.ContractCallRequest, error) { var req core.ContractCallRequest s := op.Input.String() @@ -55,7 +68,12 @@ func (cm *contractManager) PrepareOperation(ctx context.Context, op *core.Operat return nil, err } return opBlockchainInvoke(op, req), nil - + case core.OpTypeBlockchainContractDeploy: + req, err := retrieveBlockchainDeployInputs(ctx, op) + if err != nil { + return nil, err + } + return opBlockchainContractDeploy(op, req), nil default: return nil, i18n.NewError(ctx, coremsgs.MsgOperationNotSupported, op.Type) } @@ -67,14 +85,18 @@ func (cm *contractManager) RunOperation(ctx context.Context, op *core.PreparedOp req := data.Request return nil, false, cm.blockchain.InvokeContract(ctx, op.NamespacedIDString(), req.Key, req.Location, req.Method, req.Input, req.Options) + case blockchainContractDeployData: + req := data.Request + return nil, false, cm.blockchain.DeployContract(ctx, op.NamespacedIDString(), req.Key, req.Definition, req.Contract, req.Input, req.Options) default: return nil, false, i18n.NewError(ctx, coremsgs.MsgOperationDataIncorrect, op.Data) } } func (cm *contractManager) OnOperationUpdate(ctx context.Context, op *core.Operation, update *core.OperationUpdate) error { - // Special handling for OpTypeBlockchainInvoke, which writes an event when it succeeds or fails - if op.Type == core.OpTypeBlockchainInvoke { + // Special handling for blockchain operations, which writes an event when it succeeds or fails + switch op.Type { + case core.OpTypeBlockchainInvoke: if update.Status == core.OpStatusSucceeded { event := core.NewEvent(core.EventTypeBlockchainInvokeOpSucceeded, op.Namespace, op.ID, op.Transaction, "") if err := cm.database.InsertEvent(ctx, event); err != nil { @@ -87,6 +109,19 @@ func (cm *contractManager) OnOperationUpdate(ctx context.Context, op *core.Opera return err } } + case core.OpTypeBlockchainContractDeploy: + if update.Status == core.OpStatusSucceeded { + event := core.NewEvent(core.EventTypeBlockchainContractDeployOpSucceeded, op.Namespace, op.ID, op.Transaction, "") + if err := cm.database.InsertEvent(ctx, event); err != nil { + return err + } + } + if update.Status == core.OpStatusFailed { + event := core.NewEvent(core.EventTypeBlockchainContractDeployOpFailed, op.Namespace, op.ID, op.Transaction, "") + if err := cm.database.InsertEvent(ctx, event); err != nil { + return err + } + } } return nil } @@ -100,3 +135,13 @@ func opBlockchainInvoke(op *core.Operation, req *core.ContractCallRequest) *core Data: blockchainInvokeData{Request: req}, } } + +func opBlockchainContractDeploy(op *core.Operation, req *core.ContractDeployRequest) *core.PreparedOperation { + return &core.PreparedOperation{ + ID: op.ID, + Namespace: op.Namespace, + Plugin: op.Plugin, + Type: op.Type, + Data: blockchainContractDeployData{Request: req}, + } +} diff --git a/internal/contracts/operations_test.go b/internal/contracts/operations_test.go index d37932d0ba..5972dd0ea7 100644 --- a/internal/contracts/operations_test.go +++ b/internal/contracts/operations_test.go @@ -46,7 +46,7 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) { "value": "1", }, } - err := addBlockchainInvokeInputs(op, req) + err := addBlockchainReqInputs(op, req) assert.NoError(t, err) mbi := cm.blockchain.(*blockchainmocks.Plugin) @@ -68,6 +68,39 @@ func TestPrepareAndRunBlockchainInvoke(t *testing.T) { mbi.AssertExpectations(t) } +func TestPrepareAndRunBlockchainContractDeploy(t *testing.T) { + cm := newTestContractManager() + + op := &core.Operation{ + Type: core.OpTypeBlockchainContractDeploy, + ID: fftypes.NewUUID(), + Namespace: "ns1", + } + signingKey := "0x2468" + req := &core.ContractDeployRequest{ + Key: signingKey, + Definition: fftypes.JSONAnyPtr("[]"), + Contract: fftypes.JSONAnyPtr("\"0x123456\""), + Input: []interface{}{"one", "two", "three"}, + } + err := addBlockchainReqInputs(op, req) + assert.NoError(t, err) + + mbi := cm.blockchain.(*blockchainmocks.Plugin) + mbi.On("DeployContract", context.Background(), "ns1:"+op.ID.String(), signingKey, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + po, err := cm.PrepareOperation(context.Background(), op) + assert.NoError(t, err) + assert.Equal(t, req, po.Data.(blockchainContractDeployData).Request) + + _, complete, err := cm.RunOperation(context.Background(), po) + + assert.False(t, complete) + assert.NoError(t, err) + + mbi.AssertExpectations(t) +} + func TestPrepareOperationNotSupported(t *testing.T) { cm := newTestContractManager() @@ -77,6 +110,18 @@ func TestPrepareOperationNotSupported(t *testing.T) { assert.Regexp(t, "FF10371", err) } +func TestPrepareOperationBlockchainDeployBadInput(t *testing.T) { + cm := newTestContractManager() + + op := &core.Operation{ + Type: core.OpTypeBlockchainContractDeploy, + Input: fftypes.JSONObject{"input": "bad"}, + } + + _, err := cm.PrepareOperation(context.Background(), op) + assert.Regexp(t, "FF00127", err) +} + func TestPrepareOperationBlockchainInvokeBadInput(t *testing.T) { cm := newTestContractManager() @@ -107,6 +152,50 @@ func TestOperationUpdate(t *testing.T) { assert.NoError(t, err) } +func TestOperationUpdateDeploySucceed(t *testing.T) { + cm := newTestContractManager() + + op := &core.Operation{ + ID: fftypes.NewUUID(), + Type: core.OpTypeBlockchainContractDeploy, + } + update := &core.OperationUpdate{ + Status: core.OpStatusSucceeded, + } + + mdi := cm.database.(*databasemocks.Plugin) + mdi.On("InsertEvent", context.Background(), mock.MatchedBy(func(event *core.Event) bool { + return event.Type == core.EventTypeBlockchainContractDeployOpSucceeded && *event.Reference == *op.ID + })).Return(fmt.Errorf("pop")) + + err := cm.OnOperationUpdate(context.Background(), op, update) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + +func TestOperationUpdateDeployFail(t *testing.T) { + cm := newTestContractManager() + + op := &core.Operation{ + ID: fftypes.NewUUID(), + Type: core.OpTypeBlockchainContractDeploy, + } + update := &core.OperationUpdate{ + Status: core.OpStatusFailed, + } + + mdi := cm.database.(*databasemocks.Plugin) + mdi.On("InsertEvent", context.Background(), mock.MatchedBy(func(event *core.Event) bool { + return event.Type == core.EventTypeBlockchainContractDeployOpFailed && *event.Reference == *op.ID + })).Return(fmt.Errorf("pop")) + + err := cm.OnOperationUpdate(context.Background(), op, update) + assert.EqualError(t, err, "pop") + + mdi.AssertExpectations(t) +} + func TestOperationUpdateInvokeSucceed(t *testing.T) { cm := newTestContractManager() diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go index 5ba86caf80..dea99d3da5 100644 --- a/internal/coremsgs/en_api_translations.go +++ b/internal/coremsgs/en_api_translations.go @@ -143,6 +143,7 @@ var ( APIEndpointsGetVerifierByHash = ffm("api.endpoints.getVerifierByHash", "Gets a verifier by its hash") APIEndpointsGetVerifiers = ffm("api.endpoints.getVerifiers", "Gets a list of verifiers") APIEndpointsPatchUpdateIdentity = ffm("api.endpoints.patchUpdateIdentity", "Updates an identity") + APIEndpointsPostContractDeploy = ffm("api.endpoints.postContractDeploy", "Deploy a new smart contract") APIEndpointsPostContractAPIInvoke = ffm("api.endpoints.postContractAPIInvoke", "Invokes a method on a smart contract API. Performs a blockchain transaction.") APIEndpointsPostContractAPIQuery = ffm("api.endpoints.postContractAPIQuery", "Queries a method on a smart contract API. Performs a read-only query.") APIEndpointsPostContractInterfaceGenerate = ffm("api.endpoints.postContractInterfaceGenerate", "A convenience method to convert a blockchain specific smart contract format into a FireFly Interface format. The specific blockchain plugin in use must support this functionality.") diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go index af1d6b79b2..4597b015fc 100644 --- a/internal/coremsgs/en_error_messages.go +++ b/internal/coremsgs/en_error_messages.go @@ -267,4 +267,5 @@ var ( MsgCacheConfigKeyMismatchInternal = ffe("FF10426", "could not initialize cache - '%s' and '%s' do not have identical prefix, mismatching prefixes are: '%s','%s'") MsgCacheUnexpectedSizeKeyNameInternal = ffe("FF10427", "could not initialize cache - '%s' is not an expected size configuration key suffix. Expected values are: 'size', 'limit'") MsgUnknownVerifierType = ffe("FF10428", "Unknown verifier type", 400) + MsgNotSupportedByBlockchainPlugin = ffe("FF10429", "Not supported by blockchain plugin", 400) ) diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go index 903b43453d..6895f21820 100644 --- a/internal/coremsgs/en_struct_descriptions.go +++ b/internal/coremsgs/en_struct_descriptions.go @@ -593,6 +593,13 @@ var ( TransactionStatusDetailsError = ffm("TransactionStatusDetails.error", "If an error occurred related to the detail entry, it is included here") TransactionStatusDetailsInfo = ffm("TransactionStatusDetails.info", "Output details for this entry") + // ContractDeployRequest field descriptions + ContractDeployRequestKey = ffm("ContractDeployRequest.key", "The blockchain signing key that will be used to deploy the contract. Defaults to the first signing key of the organization that operates the node") + ContractDeployRequestInput = ffm("ContractDeployRequest.input", "An optional array of inputs passed to the smart contract's constructor, if applicable") + ContractDeployRequestDefinition = ffm("ContractDeployRequest.definition", "The definition of the smart contract") + ContractDeployRequestContract = ffm("ContractDeployRequest.contract", "The smart contract to deploy. This should be pre-compiled if required by the blockchain connector") + ContractDeployRequestOptions = ffm("ContractDeployRequest.options", "A map of named inputs that will be passed through to the blockchain connector") + // ContractCallRequest field descriptions ContractCallRequestType = ffm("ContractCallRequest.type", "Invocations cause transactions on the blockchain. Whereas queries simply execute logic in your local node to query data at a given current/historical block") ContractCallRequestInterface = ffm("ContractCallRequest.interface", "The UUID of a method within a pre-configured FireFly interface (FFI) definition for a smart contract. Required if the 'method' is omitted. Also see Contract APIs as a way to configure a dedicated API for your FFI, including all methods and an OpenAPI/Swagger interface") diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index ba56910872..0ac1162bfb 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -35,6 +35,7 @@ type Manager interface { MessageConfirmed(msg *core.Message, eventType fftypes.FFEnum) TransferSubmitted(transfer *core.TokenTransfer) TransferConfirmed(transfer *core.TokenTransfer) + BlockchainContractDeployment() BlockchainTransaction(location, methodName string) BlockchainQuery(location, methodName string) BlockchainEvent(location, signature string) @@ -129,6 +130,10 @@ func (mm *metricsManager) TransferConfirmed(transfer *core.TokenTransfer) { } } +func (mm *metricsManager) BlockchainContractDeployment() { + BlockchainTransactionsCounter.WithLabelValues("", "").Inc() +} + func (mm *metricsManager) BlockchainTransaction(location, methodName string) { BlockchainTransactionsCounter.WithLabelValues(location, methodName).Inc() } diff --git a/internal/syncasync/sync_async_bridge.go b/internal/syncasync/sync_async_bridge.go index a7bc3908d9..c559d074b4 100644 --- a/internal/syncasync/sync_async_bridge.go +++ b/internal/syncasync/sync_async_bridge.go @@ -57,6 +57,8 @@ type Bridge interface { WaitForTokenApproval(ctx context.Context, id *fftypes.UUID, send SendFunction) (*core.TokenApproval, error) // WaitForInvokeOperation waits for an operation with the supplied ID WaitForInvokeOperation(ctx context.Context, id *fftypes.UUID, send SendFunction) (*core.Operation, error) + // WaitForDeployOperation waits for an operation with the supplied ID + WaitForDeployOperation(ctx context.Context, id *fftypes.UUID, send SendFunction) (*core.Operation, error) } // Sender interface may be implemented by other types that wish to provide generic sync/async capabilities @@ -78,6 +80,7 @@ const ( tokenTransferConfirm tokenApproveConfirm invokeOperationConfirm + deployOperationConfirm ) type inflightRequest struct { @@ -433,7 +436,7 @@ func (sa *syncAsyncBridge) handleApprovalOpFailedEvent(event *core.EventDelivery return nil } -func (sa *syncAsyncBridge) handleOperationSuccededEvent(event *core.EventDelivery) error { +func (sa *syncAsyncBridge) handleOperationSucceededEvent(event *core.EventDelivery) error { // See if this is a failure of an inflight invoke operation inflight := sa.getInFlight(event.Namespace, invokeOperationConfirm, event.Reference) if inflight == nil { @@ -450,6 +453,40 @@ func (sa *syncAsyncBridge) handleOperationSuccededEvent(event *core.EventDeliver return nil } +func (sa *syncAsyncBridge) handleContractDeployOperationSucceededEvent(event *core.EventDelivery) error { + // See if this is a failure of an inflight invoke operation + inflight := sa.getInFlight(event.Namespace, deployOperationConfirm, event.Reference) + if inflight == nil { + return nil + } + + op, err := sa.getOperationFromEvent(event) + if err != nil || op == nil { + return err + } + + go sa.resolveSuccessfulOperation(inflight, "deploy", op) + + return nil +} + +func (sa *syncAsyncBridge) handleContractDeployOperationFailedEvent(event *core.EventDelivery) error { + // See if this is a failure of an inflight deploy operation + inflight := sa.getInFlight(event.Namespace, deployOperationConfirm, event.Reference) + if inflight == nil { + return nil + } + + op, err := sa.getOperationFromEvent(event) + if err != nil || op == nil { + return err + } + + go sa.resolveFailedOperation(inflight, "deploy", op) + + return nil +} + func (sa *syncAsyncBridge) handleOperationFailedEvent(event *core.EventDelivery) error { // See if this is a failure of an inflight invoke operation inflight := sa.getInFlight(event.Namespace, invokeOperationConfirm, event.Reference) @@ -506,10 +543,16 @@ func (sa *syncAsyncBridge) eventCallback(event *core.EventDelivery) error { return sa.handleApprovalOpFailedEvent(event) case core.EventTypeBlockchainInvokeOpSucceeded: - return sa.handleOperationSuccededEvent(event) + return sa.handleOperationSucceededEvent(event) case core.EventTypeBlockchainInvokeOpFailed: return sa.handleOperationFailedEvent(event) + + case core.EventTypeBlockchainContractDeployOpSucceeded: + return sa.handleContractDeployOperationSucceededEvent(event) + + case core.EventTypeBlockchainContractDeployOpFailed: + return sa.handleContractDeployOperationFailedEvent(event) } return nil @@ -660,3 +703,11 @@ func (sa *syncAsyncBridge) WaitForInvokeOperation(ctx context.Context, id *fftyp } return reply.(*core.Operation), err } + +func (sa *syncAsyncBridge) WaitForDeployOperation(ctx context.Context, id *fftypes.UUID, send SendFunction) (*core.Operation, error) { + reply, err := sa.sendAndWait(ctx, sa.namespace, id, deployOperationConfirm, send) + if err != nil { + return nil, err + } + return reply.(*core.Operation), err +} diff --git a/internal/syncasync/sync_async_bridge_test.go b/internal/syncasync/sync_async_bridge_test.go index c00f118868..b7cb0d5151 100644 --- a/internal/syncasync/sync_async_bridge_test.go +++ b/internal/syncasync/sync_async_bridge_test.go @@ -255,6 +255,8 @@ func TestEventCallbackNotInflight(t *testing.T) { core.EventTypeIdentityConfirmed, core.EventTypeBlockchainInvokeOpSucceeded, core.EventTypeBlockchainInvokeOpFailed, + core.EventTypeBlockchainContractDeployOpSucceeded, + core.EventTypeBlockchainContractDeployOpFailed, } { err := sa.eventCallback(&core.EventDelivery{ EnrichedEvent: core.EnrichedEvent{ @@ -1423,6 +1425,144 @@ func TestAwaitIdentityFail(t *testing.T) { assert.Regexp(t, "pop", err) } +func TestAwaitDeployOpSucceeded(t *testing.T) { + + sa, cancel := newTestSyncAsyncBridge(t) + defer cancel() + + requestID := fftypes.NewUUID() + op := &core.Operation{ + ID: requestID, + Status: core.OpStatusSucceeded, + } + + mse := sa.sysevents.(*systemeventmocks.EventInterface) + mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) + + mom := sa.operations.(*operationmocks.Manager) + mom.On("GetOperationByIDCached", sa.ctx, op.ID).Return(op, nil) + + ret, err := sa.WaitForDeployOperation(sa.ctx, requestID, func(ctx context.Context) error { + go func() { + sa.eventCallback(&core.EventDelivery{ + EnrichedEvent: core.EnrichedEvent{ + Event: core.Event{ + ID: fftypes.NewUUID(), + Type: core.EventTypeBlockchainContractDeployOpSucceeded, + Reference: requestID, + Namespace: "ns1", + }, + }, + }) + }() + return nil + }) + assert.NoError(t, err) + assert.Equal(t, ret, op) +} + +func TestAwaitDeployOpSucceededLookupFail(t *testing.T) { + + sa, cancel := newTestSyncAsyncBridge(t) + defer cancel() + + requestID := fftypes.NewUUID() + sa.inflight = map[string]map[fftypes.UUID]*inflightRequest{ + "ns1": { + *requestID: &inflightRequest{ + reqType: deployOperationConfirm, + }, + }, + } + + mse := sa.sysevents.(*systemeventmocks.EventInterface) + mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) + + mom := sa.operations.(*operationmocks.Manager) + mom.On("GetOperationByIDCached", sa.ctx, requestID).Return(nil, fmt.Errorf("pop")) + + err := sa.eventCallback(&core.EventDelivery{ + EnrichedEvent: core.EnrichedEvent{ + Event: core.Event{ + ID: fftypes.NewUUID(), + Type: core.EventTypeBlockchainContractDeployOpSucceeded, + Reference: requestID, + Namespace: "ns1", + }, + }, + }) + assert.EqualError(t, err, "pop") +} + +func TestAwaitDeployOpFailed(t *testing.T) { + + sa, cancel := newTestSyncAsyncBridge(t) + defer cancel() + + requestID := fftypes.NewUUID() + op := &core.Operation{ + ID: requestID, + Status: core.OpStatusFailed, + Error: "pop", + } + + mse := sa.sysevents.(*systemeventmocks.EventInterface) + mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) + + mom := sa.operations.(*operationmocks.Manager) + mom.On("GetOperationByIDCached", sa.ctx, op.ID).Return(op, nil) + + _, err := sa.WaitForDeployOperation(sa.ctx, requestID, func(ctx context.Context) error { + go func() { + sa.eventCallback(&core.EventDelivery{ + EnrichedEvent: core.EnrichedEvent{ + Event: core.Event{ + ID: fftypes.NewUUID(), + Type: core.EventTypeBlockchainContractDeployOpFailed, + Reference: requestID, + Namespace: "ns1", + }, + }, + }) + }() + return nil + }) + assert.EqualError(t, err, "pop") +} + +func TestAwaitDeployOpFailedLookupFail(t *testing.T) { + + sa, cancel := newTestSyncAsyncBridge(t) + defer cancel() + + requestID := fftypes.NewUUID() + sa.inflight = map[string]map[fftypes.UUID]*inflightRequest{ + "ns1": { + *requestID: &inflightRequest{ + reqType: deployOperationConfirm, + }, + }, + } + + mse := sa.sysevents.(*systemeventmocks.EventInterface) + mse.On("AddSystemEventListener", "ns1", mock.Anything).Return(nil) + + mom := sa.operations.(*operationmocks.Manager) + mom.On("GetOperationByIDCached", sa.ctx, requestID).Return(nil, fmt.Errorf("pop")) + + err := sa.eventCallback(&core.EventDelivery{ + EnrichedEvent: core.EnrichedEvent{ + Event: core.Event{ + ID: fftypes.NewUUID(), + Type: core.EventTypeBlockchainContractDeployOpFailed, + Reference: requestID, + Namespace: "ns1", + }, + }, + }) + assert.EqualError(t, err, "pop") +} + func TestAwaitInvokeOpSucceeded(t *testing.T) { sa, cancel := newTestSyncAsyncBridge(t) diff --git a/mocks/blockchainmocks/plugin.go b/mocks/blockchainmocks/plugin.go index a1c5a5e900..e143c2dd0b 100644 --- a/mocks/blockchainmocks/plugin.go +++ b/mocks/blockchainmocks/plugin.go @@ -89,6 +89,20 @@ func (_m *Plugin) DeleteContractListener(ctx context.Context, subscription *core return r0 } +// DeployContract provides a mock function with given fields: ctx, nsOpID, signingKey, definition, contract, input, options +func (_m *Plugin) DeployContract(ctx context.Context, nsOpID string, signingKey string, definition *fftypes.JSONAny, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error { + ret := _m.Called(ctx, nsOpID, signingKey, definition, contract, input, options) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *fftypes.JSONAny, *fftypes.JSONAny, []interface{}, map[string]interface{}) error); ok { + r0 = rf(ctx, nsOpID, signingKey, definition, contract, input, options) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // GenerateEventSignature provides a mock function with given fields: ctx, event func (_m *Plugin) GenerateEventSignature(ctx context.Context, event *fftypes.FFIEventDefinition) string { ret := _m.Called(ctx, event) @@ -366,13 +380,13 @@ func (_m *Plugin) Start() error { return r0 } -// SubmitBatchPin provides a mock function with given fields: ctx, nsOpID, remtoeNamespace, signingKey, batch, location -func (_m *Plugin) SubmitBatchPin(ctx context.Context, nsOpID string, remtoeNamespace string, signingKey string, batch *blockchain.BatchPin, location *fftypes.JSONAny) error { - ret := _m.Called(ctx, nsOpID, remtoeNamespace, signingKey, batch, location) +// SubmitBatchPin provides a mock function with given fields: ctx, nsOpID, networkNamespace, signingKey, batch, location +func (_m *Plugin) SubmitBatchPin(ctx context.Context, nsOpID string, networkNamespace string, signingKey string, batch *blockchain.BatchPin, location *fftypes.JSONAny) error { + ret := _m.Called(ctx, nsOpID, networkNamespace, signingKey, batch, location) var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *blockchain.BatchPin, *fftypes.JSONAny) error); ok { - r0 = rf(ctx, nsOpID, remtoeNamespace, signingKey, batch, location) + r0 = rf(ctx, nsOpID, networkNamespace, signingKey, batch, location) } else { r0 = ret.Error(0) } diff --git a/mocks/contractmocks/manager.go b/mocks/contractmocks/manager.go index 0b0125bbbb..bd38194a5f 100644 --- a/mocks/contractmocks/manager.go +++ b/mocks/contractmocks/manager.go @@ -79,6 +79,29 @@ func (_m *Manager) DeleteContractListenerByNameOrID(ctx context.Context, nameOrI return r0 } +// DeployContract provides a mock function with given fields: ctx, req, waitConfirm +func (_m *Manager) DeployContract(ctx context.Context, req *core.ContractDeployRequest, waitConfirm bool) (interface{}, error) { + ret := _m.Called(ctx, req, waitConfirm) + + var r0 interface{} + if rf, ok := ret.Get(0).(func(context.Context, *core.ContractDeployRequest, bool) interface{}); ok { + r0 = rf(ctx, req, waitConfirm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *core.ContractDeployRequest, bool) error); ok { + r1 = rf(ctx, req, waitConfirm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GenerateFFI provides a mock function with given fields: ctx, generationRequest func (_m *Manager) GenerateFFI(ctx context.Context, generationRequest *fftypes.FFIGenerationRequest) (*fftypes.FFI, error) { ret := _m.Called(ctx, generationRequest) diff --git a/mocks/metricsmocks/manager.go b/mocks/metricsmocks/manager.go index 06b48037f4..af86016210 100644 --- a/mocks/metricsmocks/manager.go +++ b/mocks/metricsmocks/manager.go @@ -21,6 +21,11 @@ func (_m *Manager) AddTime(id string) { _m.Called(id) } +// BlockchainContractDeployment provides a mock function with given fields: +func (_m *Manager) BlockchainContractDeployment() { + _m.Called() +} + // BlockchainEvent provides a mock function with given fields: location, signature func (_m *Manager) BlockchainEvent(location string, signature string) { _m.Called(location, signature) diff --git a/mocks/syncasyncmocks/bridge.go b/mocks/syncasyncmocks/bridge.go index 08b12900f1..9430505358 100644 --- a/mocks/syncasyncmocks/bridge.go +++ b/mocks/syncasyncmocks/bridge.go @@ -25,6 +25,29 @@ func (_m *Bridge) Init(sysevents system.EventInterface) { _m.Called(sysevents) } +// WaitForDeployOperation provides a mock function with given fields: ctx, id, send +func (_m *Bridge) WaitForDeployOperation(ctx context.Context, id *fftypes.UUID, send syncasync.SendFunction) (*core.Operation, error) { + ret := _m.Called(ctx, id, send) + + var r0 *core.Operation + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.UUID, syncasync.SendFunction) *core.Operation); ok { + r0 = rf(ctx, id, send) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*core.Operation) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *fftypes.UUID, syncasync.SendFunction) error); ok { + r1 = rf(ctx, id, send) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // WaitForIdentity provides a mock function with given fields: ctx, id, send func (_m *Bridge) WaitForIdentity(ctx context.Context, id *fftypes.UUID, send syncasync.SendFunction) (*core.Identity, error) { ret := _m.Called(ctx, id, send) diff --git a/pkg/blockchain/plugin.go b/pkg/blockchain/plugin.go index 26c44cce4e..80642144dd 100644 --- a/pkg/blockchain/plugin.go +++ b/pkg/blockchain/plugin.go @@ -64,6 +64,9 @@ type Plugin interface { // SubmitNetworkAction writes a special "BatchPin" event which signals the plugin to take an action SubmitNetworkAction(ctx context.Context, nsOpID, signingKey string, action core.NetworkActionType, location *fftypes.JSONAny) error + // DeployContract submits a new transaction to deploy a new instance of a smart contract + DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) error + // InvokeContract submits a new transaction to be executed by custom on-chain logic InvokeContract(ctx context.Context, nsOpID, signingKey string, location *fftypes.JSONAny, method *fftypes.FFIMethod, input map[string]interface{}, options map[string]interface{}) error diff --git a/pkg/core/contracts.go b/pkg/core/contracts.go index 69a6289c9c..c3f2988b03 100644 --- a/pkg/core/contracts.go +++ b/pkg/core/contracts.go @@ -42,6 +42,14 @@ type ContractCallRequest struct { Options map[string]interface{} `ffstruct:"ContractCallRequest" json:"options"` } +type ContractDeployRequest struct { + Key string `ffstruct:"ContractDeployRequest" json:"key,omitempty"` + Input []interface{} `ffstruct:"ContractDeployRequest" json:"input"` + Definition *fftypes.JSONAny `ffstruct:"ContractDeployRequest" json:"definition"` + Contract *fftypes.JSONAny `ffstruct:"ContractDeployRequest" json:"contract"` + Options map[string]interface{} `ffstruct:"ContractDeployRequest" json:"options"` +} + type ContractURLs struct { OpenAPI string `ffstruct:"ContractURLs" json:"openapi"` UI string `ffstruct:"ContractURLs" json:"ui"` diff --git a/pkg/core/event.go b/pkg/core/event.go index daf82405f3..b96e2d7263 100644 --- a/pkg/core/event.go +++ b/pkg/core/event.go @@ -57,6 +57,10 @@ var ( EventTypeBlockchainInvokeOpSucceeded = fftypes.FFEnumValue("eventtype", "blockchain_invoke_op_succeeded") // EventTypeBlockchainInvokeOpFailed occurs when a blockchain "invoke" request has failed EventTypeBlockchainInvokeOpFailed = fftypes.FFEnumValue("eventtype", "blockchain_invoke_op_failed") + // EventTypeBlockchainContractDeployOpSucceeded occurs when a contract deployment request has succeeded + EventTypeBlockchainContractDeployOpSucceeded = fftypes.FFEnumValue("eventtype", "blockchain_contract_deploy_op_succeeded") + // EventTypeBlockchainContractDeployOpFailed occurs when a contract deployment request has failed + EventTypeBlockchainContractDeployOpFailed = fftypes.FFEnumValue("eventtype", "blockchain_contract_deploy_op_failed") ) // Event is an activity in the system, delivered reliably to applications, that indicates something has happened in the network diff --git a/pkg/core/operation.go b/pkg/core/operation.go index c6824b7d47..589aa19846 100644 --- a/pkg/core/operation.go +++ b/pkg/core/operation.go @@ -35,6 +35,8 @@ var ( OpTypeBlockchainPinBatch = fftypes.FFEnumValue("optype", "blockchain_pin_batch") // OpTypeBlockchainNetworkAction is an administrative action on a multiparty blockchain network OpTypeBlockchainNetworkAction = fftypes.FFEnumValue("optype", "blockchain_network_action") + // OpTypeBlockchainContractDeploy is a smart contract deploy + OpTypeBlockchainContractDeploy = fftypes.FFEnumValue("optype", "blockchain_deploy") // OpTypeBlockchainInvoke is a smart contract invoke OpTypeBlockchainInvoke = fftypes.FFEnumValue("optype", "blockchain_invoke") // OpTypeSharedStorageUploadBatch is a shared storage operation to upload broadcast data diff --git a/pkg/core/transaction.go b/pkg/core/transaction.go index 7ab5b54a03..d107b0a8a8 100644 --- a/pkg/core/transaction.go +++ b/pkg/core/transaction.go @@ -35,6 +35,8 @@ var ( TransactionTypeTokenPool = fftypes.FFEnumValue("txtype", "token_pool") // TransactionTypeTokenTransfer represents a token transfer TransactionTypeTokenTransfer = fftypes.FFEnumValue("txtype", "token_transfer") + // TransactionTypeContractDeploy is a smart contract deployment + TransactionTypeContractDeploy = fftypes.FFEnumValue("txtype", "contract_deploy") // TransactionTypeContractInvoke is a smart contract invoke TransactionTypeContractInvoke = fftypes.FFEnumValue("txtype", "contract_invoke") // TransactionTypeTokenTransfer represents a token approval