diff --git a/docs/tutorials/chaincode-lifecycle.md b/docs/tutorials/chaincode-lifecycle.md new file mode 100644 index 0000000000..670337bfe2 --- /dev/null +++ b/docs/tutorials/chaincode-lifecycle.md @@ -0,0 +1,540 @@ +This tutorial illustrates how to handle the lifecycle of your chaincode. The installation, updating, and the starting of chaincode has had been changed with Hyperledger Fabric 2.0 and fabric-client 2.0. + +For more information on: +* getting started with Hyperledger Fabric see +[Building your first network](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html). +* the configuration of a channel in Hyperledger Fabric and the internal +process of creating and updating see +[Hyperledger Fabric channel configuration](http://hyperledger-fabric.readthedocs.io/en/latest/configtx.html) +* [Chaincode Lifecycle](https://hyperledger-fabric.readthedocs.io/en/latest/discovery-overview.html) + +The following assumes an understanding of the Hyperledger Fabric network +(orderers and peers), +and of Node application development, including the use of the +Javascript `promise` and `async await`. + +### Overview +This discussion will focus on the steps that are required by an application +program that will be managing the install, updating and starting of chaincodes +on your Hyperledger Fabric network. The existing api's for managing the lifecycle +of your pre 2.0 chaincodes will still be available in fabric-client, however it +will not be discussed here. + +The steps to manage chaincode: +* `setup` - create the necessary application objects +* `package` - package the chaincode source artifacts +* `install` - push to the network +* `define for organization` - each organization will define a specific chaincode definition +* `define for the channel` - the channel members will agree to run a specific chaincode definition +* `initialize` - start the chaincode container and initialize the chaincode + +#### New Class +A new class {@link Chaincode} has been added to the fabric-client to encapsulate +a chaincode definition. +A {@link Chaincode} instance will be created by a client instance's +{@link Client#newChaincode newChaincode()} method. +Then using the new instance, you will be able to build up a chaincode definition +with the following methods. +* {@link Chaincode#setEndorsementPolicy setEndorsementPolicy} - Provide the endorsement policy for this chaincode. +* {@link Chaincode#setCollectionConfig setCollectionConfig} - Provide the collection configuration for this chaincode. +* {@link Chaincode#setSequence setSequence} - Provide the modification number for this chaincode. +* {@link Chaincode#setPackage setPackage} - Provide the package when not packaging this chaincode locally. +* {@link Chaincode#setHash setHash} - Provide the package hash when not doing an install locally of this chaincode. + +The chaincode instance will allow for the packaging and installing of chaincode +to a peer within your organization with the following methods. +* {@link Chaincode#package package} Package the files at the locations provided. +* {@link Chaincode#install install} Install the package on the specified peers. + +Once the chaincode definition has all the necessary attributes, it may be used +by a channel instance to be defined both for an organization and channel wide. + +#### New methods on Channel +The {@link Channel} class has been enhanced to include new methods to define +a chaincode for an organization and for the channel wide use. + +* {@link Channel#defineChaincodeForOrg defineChaincodeForOrg} - Define +the chaincode for an organization. +* {@link Channel#defineChaincode defineChaincode} - Define +the chaincode for a channel. + +#### New method on Client +The {@link Client} class has been enhanced to include new method to create +a {@link Chaincode} instance. + +* {@link Client#newChaincode newChaincode} - Create a {@link Chaincode} instance. + + +### Step 1: Setup +In this step we will be building +the application objects needed to perform the operational steps that follow. +An fabric-client operational environment is required before any of the +following steps may be performed. A client instance is needed +that has a user store, crypto suite, and a user assigned. The target peers, +orderer, and channel instance objects will also be required prior to doing +the any of the following steps. + +The following sample code assumes that all of the normal fabric-client +setup has been completed and only shows the new chaincode lifecycle +related calls. + +``` +// get the chaincode instance associated with the client +const mychaincode = client.newChaincode('mychaincode', 'version1'); + +// The endorsement policy - required. +const policy_def = { + identities: [ + {role: {name: 'member', mspId: 'org1'}}, + {role: {name: 'member', mspId: 'org2'}} + ], + policy: { + '1-of': [{'signed-by': 0}, {'signed-by': 1}] + } +}; +mychaincode.setEndorsementPolicy(policy_def); + +// The collection configuration - optional. +const config_def = [{ + name: 'detailCol', + policy: { + identities: [ + {role: {name: 'member', mspId: 'Org1MSP'}}, + {role: {name: 'member', mspId: 'Org2MSP'}} + ], + policy: { + '1-of': [{'signed-by': 0}, {'signed-by': 1}] + } + }, + requiredPeerCount: 1, + maxPeerCount: 1, + blockToLive: 100 +}]; +mychaincode.setCollectionConfig(config_def)); + +// set the sequence (modification) number - required. +mychaincode.setSequence(1); //set to one for a new chaincode +``` + +### Step 2: Package +This step will only be required by a single organization. The organization will +take the source artifacts, chaincode source code and metadata files, and have +the fabric-client package them. This package may then be sent to other +organizations or administrators of your fabric network to be installed on +the network. + +The following example is for the organization that packages the code +and then will send to other organizations to be installed. + +``` +// package the source code +const packge_request = { + chaincodeType: 'golang', + goPath: '/gopath', + chaincodePath: '/path/to/code', + metadataPath: '/path/to/metadat' +} +const package = await mychaincode.package(package_request); +``` + +The following example is for the organization that has not done the +packaging, but will do the install. + +``` +// use an existing package +mychaincode.setPackage(package); +``` + +### Step 3: Install +This step will be required by all organizations that will be running the chaincode +(executing transactions). The install will take the packaged source code artifacts +and send it to a peer or peers in your organization. Installing chaincode requires +admin authority for the organization. The peer will return a hash value, a unique +identifer for this chaincode package. The hash value will be needed later when +the chaincode is defined. The chaincode object will also store the value for +the next step. + +The following sample assumes that the chaincode object being used has +been setup and packaged or an error would be thrown. + +``` +// install chaincode package on peers + const install_request = { + targets: [peer1, peer2], + request_timeout: 20000 // give the peers some extra time + } +const hash = await mychaincode.install(install_request); + +// hash value is stored +const same_hash = mychaincode.getHash(); +``` + +For organizations that will not be running the chaincode and are +still required to approve the chaincode the following example +shows how they would by pass the install and just assign the +hash value to the chaincode instance. The hash value must be +the value that was returned by the peer when the chaincode was installed. + +``` +// set hash value instead of doing an install +mychaincode.setHash(hash); +``` + +### Step 4: Define for your organization +This step wlll define a chaincode for your organization. +The defining action will be required by enough organizations +to satisfy the channel's chaincode lifecycle system policy. +By default this will be a majority of the +organizations on the channel. Each of these organizations will endorse and +commit a transaction that defines a chaincode and it's operational settings. +This may be thought of not only as a definition of the chaincode but a vote +to authorize the running of the chaincode with these settings. +These are separate organizational chaincode definitions +transactions submitted by each organizations and each committed to the +ledger. This definition is for a specific organization, specific settings, +and only on this channel. +The organizational chaincode definition transaction may be submitted at any +time, but must be submitted prior to being able to execute the chaincode +on a peer within the organization. This is how a new organization may start +running an existing chaincode other organization are currently running. +The transactions must include the exact same chaincode definition. +The definition does not include the package, it includes the hash value +that uniquely identifies the chaincode source artifacts. +An organization will be able to submit the organizational chaincode definition +transaction without having installed the package, but has received the hash +value from an organization that did install the package. + +The following sample assumes that the chaincode object being used has +been setup and installed or an error will be thrown. +``` +// send a define chaincode for organization transaction +const tx_id = client.newTransactionID(); +const request = { + target: peer1, + chaincode: mychaincode, + txId: tx_id +} +// send to the peer to be endorsed +const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); +``` + +### Step 5: Define for the channel +This step will define a chaincode for your channel. The defining action will +not change the chaincode or it's settings, but rather confirm the +organizational chaincode definition for the channel. +The channel chaincode definition transaction will +be submitted by only one organization. +A successful transaction must be endorsed by enough +organizations to satisfy the channel's chaincode lifecycle system policy +and there must be enough committed organizational chaincode definition +transactions committed that also satisfies the +channel's chaincode lifecycle system policy. +Think of this step as the tallying of the votes to run the chaincode. +The action to actually count the votes must be approved by enough members +and then there must be enough votes before the chaincode will +be allowed to run. +When only a chaincode setting has been changed, like an endorsement policy, +a successful commit of the channel chaincode definition transaction will +enable the new policy for this chaincode. The initialize step will not be +required as the chaincode container will not have to change. If this is +for a new chaincode or an update to the chaincode code, then the initialize +step will be required. + +``` +// send a define chaincode for channel transaction +const tx_id = client.newTransactionID(); +const request = { + targets: [peer1, peer3], + chaincode: mychaincode, + txId: tx_id +} +// send to the peers to be endorsed +const {proposalResponses, proposal} = await mychannel.defineChaincode(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); +``` + +### Step 6: Initialize +This step will start new chaincode on your channel. +This will be the last step before the chaincode may be used for invokes and +queries. +This step will... +The initialize transaction will start the container and then call the +`init` method of the chaincode with the provided arguments. + +``` +// initialize the chaincode +const tx_id = client.newTransactionID(); +const request = { + chaincodeId : chaincodeId, + fcn: 'init', + args: args, + txId: tx_id +} +// starting the container will take longer than the normal request-timeout +const init_results = await mychannel.sendTransaction(request, 20000); +const orderer_request = { + proposalResponses: init_results[0], + proposal: init_results[1] +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); +``` + + +### Sample situations +The following samples will show the important snippets needed to perform the +different situations. + +#### New chaincode +When installing chaincode for the first time, all 6 steps must be run. +The following sample shows the code needed when the organization +will be packaging the chaincode, installing it, and being the organization +to define it for the entire channel and initialize it. + +``` +// step 1: +const mychaincode = client.newChaincode('mychaincode', 'version1'); +const policy_def = { ... }; +mychaincode.setEndorsementPolicy(policy_def); +mychaincode.setSequence(1); //set to one for a new chaincode + +// step 2: +const packge_request = { + chaincodeType: 'golang', + goPath: '/gopath', + chaincodePath: '/path/to/code', + metadataPath: '/path/to/metadat' +} +const package = await mychaincode.package(package_request); + +// step 3: + const install_request = { + targets: [peer1, peer2], + request_timeout: 20000 // give the peers some extra time + } +const hash = await mychaincode.install(install_request); + +// step 4: +const tx_id = client.newTransactionID(); +const request = { + target: peer1, + chaincode: mychaincode, + txId: tx_id +} +const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +const results = await mychannel.sendTransaction(orderer_request); + +//step 5: +const tx_id = client.newTransactionID(); +const request = { + targets: [peer1, peer3], + chaincode: mychaincode, + txId: tx_id +} +const {proposalResponses, proposal} = await mychannel.defineChaincode(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +const results = await mychannel.sendTransaction(orderer_request); + +// step 6: +const tx_id = client.newTransactionID(); +const request = { + chaincodeId : chaincodeId, + fcn: 'init', + args: args, + txId: tx_id +} +const init_results = await mychannel.sendTransaction(request, 20000); +const orderer_request = { + proposalResponses: init_results[0], + proposal: init_results[1] +} +const results = await mychannel.sendTransaction(orderer_request); +``` + +#### Update the chaincode code +When updating the chaincode all 6 steps must be performed and care must be +taken in setting the sequence number to be sure it reflects the current +modification number of the chaincode definition. In this case no other +changes have been done to the chaincode definition since it was first +installed, so the sequence number is 2. + +The following sample shows the code needed when the organization +will be packaging the chaincode, installing it, and being the organization +to define it for the entire channel and initialize it. +``` +// step 1: +const mychaincode = client.newChaincode('mychaincode', 'version2'); +const policy_def = { ... }; +mychaincode.setEndorsementPolicy(policy_def); +mychaincode.setSequence(2); + +// step 2: +// package the source code +const packge_request = { + chaincodeType: 'golang', + goPath: '/gopath', + chaincodePath: '/path/to/code', + metadataPath: '/path/to/metadat' +} +const package = await mychaincode.package(package_request); + +// step 3: + const install_request = { + targets: [peer1, peer2], + request_timeout: 20000 // give the peers some extra time + } +const hash = await mychaincode.install(install_request); + +// step 4: +const tx_id = client.newTransactionID(); +const request = { + target: peer1, + chaincode: mychaincode, + txId: tx_id +} +const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +const results = await mychannel.sendTransaction(orderer_request); + +//step 5: +const tx_id = client.newTransactionID(); +const request = { + targets: [peer1, peer3], + chaincode: mychaincode, + txId: tx_id +} +// send to the peers to be endorsed +const {proposalResponses, proposal} = await mychannel.defineChaincode(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); + +// step 6: +// initialize the chaincode +const tx_id = client.newTransactionID(); +const request = { + chaincodeId : chaincodeId, + fcn: 'init', + args: args, + txId: tx_id +} +// starting the container will take longer than the normal request-timeout +const init_results = await mychannel.sendTransaction(request, 20000); +const orderer_request = { + proposalResponses: init_results[0], + proposal: init_results[1] +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); +``` + +#### Modify the Endorsement policy + +When updating the endorsement policy only 4 steps must be performed and care must be +taken in setting the sequence number to be sure it reflects the current +modification number of the chaincode definition. In this case let us assume +that the chaincode has been updated once, so the sequence number is 3. +step 2 maybe skipped as there will not be a new package. It might +seem that we can also skip step 3, but we still need the hash value +to uniquely identify the chaincode source that was installed earlier and has +not been changed. + +The following sample shows the code needed when the organization +is redefining it and the organization +to define it for the entire channel. +``` +// step 1: +const mychaincode = client.newChaincode('mychaincode', 'version2'); +const new_policy_def = { ... }; +mychaincode.setEndorsementPolicy(new_policy_def); +mychaincode.setSequence(3); + +// step 3: +mychaincode.setHash(hash); + +// step 4: +const tx_id = client.newTransactionID(); +const request = { + target: peer1, + chaincode: mychaincode, + txId: tx_id +} +const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +const results = await mychannel.sendTransaction(orderer_request); + +//step 5: +const tx_id = client.newTransactionID(); +const request = { + targets: [peer1, peer3], + chaincode: mychaincode, + txId: tx_id +} +// send to the peers to be endorsed +const {proposalResponses, proposal} = await mychannel.defineChaincode(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +// send to the orderer to be committed +const results = await mychannel.sendTransaction(orderer_request); +``` + +#### New organization needs to run the chaincode + +When a new organization wishes to run an existing chaincode it will have to +perform a few of the steps with the existing values. +``` +// step 1: +const mychaincode = client.newChaincode('mychaincode', 'version2'); +const policy_def = { ... }; +mychaincode.setEndorsementPolicy(policy_def); +mychaincode.setSequence(3); + +// step 3: +mychaincode.setHash(hash); + +// step 4: +const tx_id = client.newTransactionID(); +const request = { + target: peer1, // this peer is in my org + chaincode: mychaincode, + txId: tx_id +} +const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); +const orderer_request = { + proposalResponses: proposalResponses, + proposal, proposal +} +const results = await mychannel.sendTransaction(orderer_request); +``` + +Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. + diff --git a/docs/tutorials/tutorials.json b/docs/tutorials/tutorials.json index 455eacdf90..c9e65be3e2 100644 --- a/docs/tutorials/tutorials.json +++ b/docs/tutorials/tutorials.json @@ -40,5 +40,8 @@ }, "handlers": { "title": "fabric-client: How to use the endorsement and commit handlers" + }, + "chaincode-lifecycle": { + "title": "fabric-client: How to install and start your chaincode" } } diff --git a/fabric-client/lib/Chaincode.js b/fabric-client/lib/Chaincode.js new file mode 100644 index 0000000000..71daacffe6 --- /dev/null +++ b/fabric-client/lib/Chaincode.js @@ -0,0 +1,576 @@ +/* + * 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. + */ + +'use strict'; +const {format} = require('util'); + +const utils = require('./utils.js'); +const logger = utils.getLogger('Chaincode.js'); +const Policy = require('./Policy.js'); +const CollectionConfig = require('./SideDB.js'); +const fabprotos = require('fabric-protos').protos; + + + + +/** + * @classdesc + * The Chaincode class represents an Chaincode definition in the target + * blockchain network. + *

+ * see the tutorial {@tutorial chaincode-lifecycle} + *

+ * This class allows an application to contain all chaincode attributes and + * artifacts in one place during runtime. This will assist the administration + * of the chaincode's lifecycle. + * + * From your {@link Client} instance use the {@link Client#newChaincode} method. + * This will return a Chaincode object instance that has been associated with + * that client. This will provide access to user credentials used for signing + * requests, access to peer, orderer, and channel information. + * + * + * From the new chaincode object you will be able to use it to help you create + * a chaincode package. This is a local client operation and does not make an + * outbound request. The package may be sent to other organizations to be + * installed on their peers. + * This package may then be installed on your organization's peers. + * The peer will return a hash value representing the package that it + * has installed. This unique value will identify your chaincode across + * the fabric network and must match all other peers that have installed + * the chaincode. + *
+ *
+ * Now that we have a hash value we can define a chaincode that our organization + * would like to run. The definition will include the hash, the name, the version, + * an endorsement policy, the channel and your organization. + * The definition must be endorsed by a peer + * in your organization and sent to be orderer to be committed to the ledger. + * You may want to think of this definition as a vote that your organization + * has agreed to run this particular chaincode on this channel. + * Once enough organization have also voted by endorsing a chaincode organization + * definition and committing that transaction the Chaincode channel definition + * may now be processed. + *
+ *
+ * When enough organizations have agreed to run this unique chaincode definition, + * it may be defined to run on the channel. This is an endorsement sent by a + * single client to enough organization's peers to satisfy the chaincode life-cycle + * policy. + * Once the client gets enough endorsements it will send the transaction to the + * orderer to be committed to the ledger. + * The chaincode may now be used for endorsements. + * + * + * @example + * // create chaincode object + * const mychaincode = client.newChaincode('mychaincode', 'version1'); + * + * // package the source code + * // - or - + * // use an existing package + * const packge_request = { + * chaincodeType: 'golang', + * goPath: '/gopath', + * chaincodePath: '/path/to/code', + * metadataPath: '/path/to/metadat' + * } + * await mychaincode.package(package_request); + * + * // send the package to the other organizations to install + * const package = mychaincode.getPackage(); + * ... < code to send > + * // The other organizations will make the package call, they + * // will use the setPackage() method to apply an existing chaincode + * // package to the chaincode instance object. + * mychaincode.setPackage(package); + * + * // install on my peers + * // This step is only required for peers that will execute the + * // the chaincode during an endorsement or chaincode query operation. + * const install_request = { + * targets: [peer1, peer2], + * request_timeout: 20000 // give the peers some extra time + * } + * // The hash value of the package is returned by the peer + * // The chaincode instance object will also contain this value. + * const hash = await mychaincode.install(install_request); + * + * // set the endorsement policy and collection config + * // The endorsement policy - required. + * mychaincode.setEndorsementPolicy(policy_def); + * // The collection configuration - optional. + * mychaincode.setCollectionConfig(config_def)); + * + * // set the sequence of the definition + * mychaincode.setSequence(1); + * + * // define for my organization + * // Each organization will define the chaincode. Think of + * // of this step as both defining a chaincode for the organization + * // to use and the organization casting a vote for this definition + * // the chaincode to be allowed to be executed on the channel. + * // Note that an organization that is just voting to allow the chaincode + * // on the channel and not planning on actually executing the chaincode + * // will only have to an chaincode instance object with + * // the name, the version, the hash value, and the sequence number + * // attributes set. The package and installing the package will not be + * // required. + * const tx_id = client.newTransactionID(); + * const request = { + * target: peer1, + * chaincode: mychaincode, + * txId: tx_id + * } + * const {proposalResponses, proposal} = await mychannel.defineChaincodeForOrg(request); + * const orderer_request = { + * proposalResponses: proposalResponses, + * proposal, proposal + * } + * const results = await mychannel.sendTransaction(orderer_request); + * + * + * // define the chaincode for the channel + * // One organization will create a transaction that will define this + * // chaincode for the channel. The transaction must be endorsed by enough + * // organizations on the channel to satisfy the chaincode lifecycle policy. + * // This action will not succeed until enough organizations have voted + * // (define for organization) for this chaincode definition to run on this + * // channel to satisfy the chaincode lifecycle endorsement policy. + * const tx_id = client.newTransactionID(); + * const request = { + * targets: [peer1, peer3], + * chaincode: mychaincode, + * txId: tx_id + * } + * const {proposalResponses, proposal} = await mychannel.defineChaincode(request); + * const orderer_request = { + * proposalResponses: proposalResponses, + * proposal, proposal + * } + * const results = await mychannel.sendTransaction(orderer_request); + * + * // initialize the chaincode + * // This action will start the chaincode container and run the 'init' method + * // of the chaincode with the provided arguments. + * // This action will only be required when the code package is new or + * // has changed and a new chaincode container must be initialized. + * const tx_id = client.newTransactionID(); + * const request = { + * chaincodeId : chaincodeId, + * fcn: 'init', + * args: args, + * txId: tx_id + * } + * const init_results = await mychannel.sendTransaction(request); + * const orderer_request = { + * proposalResponses: init_results[0], + * proposal: init_results[1] + * } + * const results = await mychannel.sendTransaction(orderer_request); + * + * @class + */ +const Chaincode = class { + + /** + * Construct a Chaincode object. + * + * @param {string} name - The name of this chaincode + * @param {string} version - The version of this chaincode + * @param {Client} client - The Client instance. + * @returns {Chaincode} The Chaincode instance. + */ + constructor(name, version, client) { + logger.debug('Chaincode.const'); + if (!name) { + throw new Error('Missing name parameter'); + } + if (!version) { + throw new Error('Missing version parameter'); + } + if (!client) { + throw new Error('Missing client parameter'); + } + this._name = name; + this._version = version; + this._client = client; + + this._sequence = null; + this._package = null; + this._hash = null; + this._endorsement_policy_proto = null; + this._endorsement_policy_json = null; + this._collection_config_proto = null; + this._collection_config_json = null; + } + + /** + * Gets the name of this chaincode. + * + * @returns {string} The name of this chaincode + */ + getName() { + return this._name; + } + + /** + * Gets the version of this chaincode. + * + * @returns {string} The version of this chaincode + */ + getVersion() { + return this._version; + } + + /** + * Gets the modification sequence of the chaincode definition. + * + * @returns {number} The sequence of this chaincode + */ + getSequence() { + return this._sequence; + } + + /** + * Sets the modification sequence of the chaincode definition. + * The sequence value gives a unique number to a set of attributes for the + * the chaincode. When a attribute changes for a chaincode, the sequence + * value must be incremented and all organizations must again run + * the defineChaincodeForOrg() method to agree to the new definition. + * The default is 1, new chaincode. + * + * @param {number} sequence - sequence of this chaincode + */ + setSequence(sequence) { + if (!Number.isInteger(sequence) || sequence < 1) { + throw new Error('Sequence value must be an integer greater than zero'); + } + this._sequence = sequence; + + return this; + } + + /** + * Gets the source code package + * + * @returns {number} The package of this chaincode + */ + getPackage() { + + return this._package; + } + + /** + * Sets the chaincode package + * + * @param {byte[]} package The source package + */ + setPackage(packaged_chaincode) { + this._package = packaged_chaincode; + + return this; + } + + /** + * @typedef {Object} ChaincodeInstallRequest + * @property {string} chaincodeType - Required. Type of chaincode. One of + * 'golang', 'car', 'node' or 'java'. + * @property {string} chaincodePath - Required. The path to the location of + * the source code of the chaincode. If the chaincode type is golang, + * then this path is the fully qualified package name, such as + * 'mycompany.com/myproject/mypackage/mychaincode' + * @property {string} metadataPath - Optional. The path to the top-level + * directory containing metadata descriptors. + * @property {string} goPath - Optional. The path to be used with the golang + * chaincode. + */ + + /** + * Package the files at the locations provided. + * This method will both return the package and set the + * package on this instance. + * + * @async + * @param {ChaincodePackageRequest} request - Required. The parameters to build the + * chaincode package. + */ + + + async package(request) { + const method = 'package'; + logger.debug('%s - start', method); + + this._package = null; + this._hash = null; + + if (!request) { + throw new Error('ChaincodeInstallRequest object parameter is required'); + } + + if (!request.chaincodeType) { + throw new Error('Chaincode package "chaincodeType" parameter is required'); + } + + const _type = Chaincode.translateCCType(request.chaincodeType); + if (!_type) { + throw new Error(format('Chaincode package "chaincodeType" parameter is not a known type %s', request.chaincodeType)); + } + + + return this._package; + } + + /** + * Method to check if this chaincode instance has a chaincode package assigned. + * @returns {boolean} indicates if this chaincode instance has a package + */ + hasPackage() { + const method = 'hasPackage'; + if (this._package) { + logger.debug('%s - contains a package', method); + return true; + } else { + logger.debug('%s - does not contains a package', method); + return false; + } + } + + /** + * Gets the package hash value + * + * @returns {string} The hash value as generated by the peer when the + * package was installed + */ + getHash() { + + return this._hash; + } + + /** + * Sets the chaincode package hash + * + * @param {string} hash The source package hash value + */ + setHash(hash) { + this._hash = hash; + + return this; + } + + /** + * Method to check if this chaincode package hash has been assigned. + * The hash value is the unique identifer of this chaincode source package + * returned by the peer that installed the chaincode package. + * When this chaincode instance has a hash value assigned it will mean + * this chaincode has been installed. It also could mean that another + * organization did the install and this organization only wants to define + * (allow) this chaincode and will not install the package at this time. + * + * @returns {boolean} indicates if this chaincode instance has the hash value + * and this chaincode instance may be used for the chaincode define actions. + */ + hasHash() { + const method = 'hasHash'; + if (this._hash) { + logger.debug('%s - contains a package hash', method); + return true; + } else { + logger.debug('%s - does not contains a package hash', method); + return false; + } + } + + // TODO ispackageinstalled + // will query the peer to see if this chaincode is installed + // should be able to check the hash + // TODO isRunning on Channel + // will query the peer to see what is running and get info + // should be able to verify the hash and sequence + // TODO ... is there a way to check the endorsement policy + + /** + * @typedef {Object} ChaincodeInstallRequest + * @property {Buffer} target Required. The peer to use for this request + * @property {number} request_timeout Optional. The amount of time for the + * to respond. The default will be the system configuration + * value of 'request-timeout'. + */ + + /** + * Install the package on the specified peers. + * This method will send the package to the peers provided. + * Each peer will return a hash value of the installed + * package. When this method is called again and within this call, the hash value + * returnd from the peer must be equal to the pervious install. + * + * @async + * @param {ChaincodeInstallRequest} request - The request object with the + * install attributes and settings. + * @returns {string} The hash value as calculated by the target peer(s). + */ + async install(request) { + const method = 'install'; + logger.debug('%s - start'); + + if (!request) { + throw new Error('Install operation requires a ChaincodeInstallRequest object parameter'); + } + + if (!this._package) { + throw new Error('Install operation requires a chaincode package be assigned to this chaincode'); + } + + const peers = request.targets; // TODO validate the targets + + // loop on each peer in the target list + for (const peer of peers) { + const hash = 'somehash'; // TODO put the install call here to the peer + logger.debug('%s - working with peer %s', method, peer); + + // TODO install process here + + if (this._hash) { + if (hash === this._hash) { + logger.debug('%s - hash values are the same :: %s', method, hash); + } else { + const msg = utils.format('The install for chaincode: %s version: ' + + '%s did not return the same hash value of %s, value was %s', + this._name, this._version, this._hash, hash); + logger.error(msg); + throw new Error(msg); + } + } else { + logger.debug('%s - first install of package returned hash of %s', method, hash); + this._hash = hash; + } + } + + return this._hash; + } + + /** + * Provide the endorsement policy for this chaincode. The input is a JSON object. + * + * @example Endorsement policy: "Signed by any member from one of the organizations" + * { + * identities: [ + * { role: {name: "member", mspId: "org1"}}, + * { role: {name: "member", mspId: "org2"}} + * ], + * policy: { + * "1-of": [{"signed-by": 0}, {"signed-by": 1}] + * } + * } + * @example Endorsement policy: "Signed by admin of the ordererOrg and any member from one of the peer organizations" + * { + * identities: [ + * {role: {name: "member", mspId: "peerOrg1"}}, + * {role: {name: "member", mspId: "peerOrg2"}}, + * {role: {name: "admin", mspId: "ordererOrg"}} + * ], + * policy: { + * "2-of": [ + * {"signed-by": 2}, + * {"1-of": [{"signed-by": 0}, {"signed-by": 1}]} + * ] + * } + * } + * @param {string} policy - The JSON representation of an fabric endorsement policy. + */ + setEndorsementPolicy(policy) { + const method = 'setEndorsementPolicy'; + logger.debug('%s - start'); + + if (policy instanceof Object) { + logger.debug('%s - have a policy object %j', method, policy); + this._endorsement_policy_json = policy; + } else { + throw new Error('A JSON policy parameter is required'); + } + + this._endorsement_policy_proto = Policy.buildPolicy(null, policy); + + return this; + } + + /** + * Provide the collection configuration for this chaincode. The input is a JSON object. + * + * @example Collection config + * [{ + * name: "detailCol", + * policy: { + * identities: [ + * {role: {name: "member", mspId: "Org1MSP"}}, + * {role: {name: "member", mspId: "Org2MSP"}} + * ], + * policy: { + * 1-of: [ + * {signed-by: 0}, + * {signed-by: 1} + * ] + * } + * }, + * requiredPeerCount: 1, + * maxPeerCount: 1, + * blockToLive: 100 + * }] + * @param {string} config - The JSON representation of a fabric collection configuration definition. + */ + setCollectionConfig(config) { + const method = 'setCollectionConfig'; + logger.debug('%s - start'); + + if (config instanceof Object) { + logger.debug('%s - have a config object %j', method, config); + this._collection_config_json = config; + } else { + throw new Error('A JSON config parameter is required'); + } + + this._colletion_config_proto = CollectionConfig.buildCollectionConfigPackage(config); + + return this; + } + + /** + * return a printable representation of this object + */ + toString() { + return 'Chaincode : {' + + 'name : ' + this._name + + ', version : ' + this._version + + ', sequence : ' + this._sequence + + ', hash : ' + this._hash + + '}'; + } + + static translateCCType(type) { + const chaincodeType = type.toLowerCase(); + + const map = { + golang: fabprotos.ChaincodeSpec.Type.GOLANG, + car: fabprotos.ChaincodeSpec.Type.CAR, + java: fabprotos.ChaincodeSpec.Type.JAVA, + node: fabprotos.ChaincodeSpec.Type.NODE + }; + const value = map[chaincodeType]; + + return value; + } + +}; + +module.exports = Chaincode; diff --git a/fabric-client/lib/Channel.js b/fabric-client/lib/Channel.js index 1c3d1c1e60..421cc9a85f 100755 --- a/fabric-client/lib/Channel.js +++ b/fabric-client/lib/Channel.js @@ -2450,6 +2450,7 @@ const Channel = class { * committed to the channel's ledger on the peers, the chaincode is then considered * activated and the peers are ready to take requests to process transactions. * + * @deprecated * @param {ChaincodeInstantiateUpgradeRequest} request * @param {Number} timeout - A number indicating milliseconds to wait on the * response before rejecting the promise with a @@ -2471,6 +2472,7 @@ const Channel = class { * Similar to instantiating a chaincode, upgrading chaincodes is also a full transaction * operation. * + * @deprecated * @param {ChaincodeInstantiateUpgradeRequest} request * @param {Number} timeout - A number indicating milliseconds to wait on the * response before rejecting the promise with a @@ -2581,15 +2583,102 @@ const Channel = class { const responses = await client_utils.sendPeersProposal(peers, signed_proposal, timeout); return [responses, proposal]; } + /** + * @typedef {Object} ChaincodeDefineRequest + * This object contains many properties that will be when defining + * a chaincode on the channel for an organization or channel wide + * @property {Peer[] | string[]} targets - Optional. The peers that will + * receive the define request. When not provided, peers that have been + * added to the channel with the 'endorser' role. + * @property {object} chaincode - Required. The chaincode object containing + * all the chaincode information required by the define chaincode fabric + * network action. see {@link Chaincode} + */ + + /* + * Internal method to check the incoming request object to be sure all the + * chaincode settings are available + * + * @param {object} request - the {@link Chaincode} object to be checked + * @throws error when there are issues with the incoming request object + */ + _verifyChaincodeRequest(request) { + const method = '_verifyChaincodeRequest'; + logger.debug('%s - start', method); + + if (!request) { + throw new Error('Missing required request parameter'); + } + if (!request.chaincode) { + throw new Error('Missing required request parameter "chaincode"'); + } + if (!request.chaincode.hasHash()) { + throw new Error('Chaincode definition must include the chaincode hash value'); + } + + logger.debug('%s - verify successfully completed', method); + } + + /** + * This method will build and send an "allow chaincode for organization for channel" + * transaction to the fabric lifecycle system chaincode. + * see {@link Chaincode} + * + * @async + * @param {ChaincodeDefineRequest} request - Required. + * @param {Number} timeout - Optional. Timeout specific for this request. + * + * @return {Object} Return object will contain the proposalResponses and the proposal + */ + async allowChaincodeForOrg(request, timeout) { + const method = 'allowChaincodeForOrg'; + logger.debug('%s - start', method); + + this._verifyChaincodeRequest(request); + + // TODO - build send the define for org transaction to the lifecycle chaincode. + + const proposal = {}; + + const proposal_responses = []; + + return {proposalResponses: proposal_responses, proposal: proposal}; + } + + /** + * This method will build and send a "commit chaincode for channel" + * transaction to the fabric lifecycle system chaincode. + * see {@link Chaincode} + * + * @async + * @param {ChaincodeDefineRequest} request - Required. + * @param {Number} timeout - Optional. Timeout specific for this request. + * + * @return {Object} Return object will contain the proposalResponses and the proposal + */ + async CommitChaincode(request, timeout) { + const method = 'CommitChaincode'; + logger.debug('%s - start', method); + + this._verifyChaincodeRequest(request); + + // TODO - build send the define for org transaction to the lifecycle chaincode. + + const proposal = {}; + + const proposal_responses = []; + + return {proposalResponses: proposal_responses, proposal: proposal}; + } /** * @typedef {Object} ChaincodeInvokeRequest * This object contains many properties that will be used by the Discovery service. * @property {Peer[] | string[]} targets - Optional. The peers that will receive this request, * when not provided the list of peers added to this channel object will - * be used. When this channel has been initialized using the discovery - * service the proposal will be sent to the peers on the list provided - * discovery service if no targets are specified. + * be used. When this channel has been initialized to use the discovery + * service, the proposal will be sent to the peers on the list provided + * by the discovery service if no targets are specified. * @property {string} chaincodeId - Required. The id of the chaincode to process * the transaction proposal * @property {DiscoveryChaincodeIntereset} endorsement_hint - Optional. A diff --git a/fabric-client/lib/Client.js b/fabric-client/lib/Client.js index fa16b67ae8..4355b7bd34 100644 --- a/fabric-client/lib/Client.js +++ b/fabric-client/lib/Client.js @@ -13,6 +13,7 @@ const clientUtils = require('./client-utils.js'); const {KeyValueStore} = require('fabric-common'); const BaseClient = require('./BaseClient.js'); const User = require('./User.js'); +const Chaincode = require('./Chaincode.js'); const Channel = require('./Channel.js'); const Package = require('./Package.js'); const Peer = require('./Peer.js'); @@ -260,6 +261,28 @@ const Client = class extends BaseClient { this._devMode = devMode; } + /** + * Returns a {@link Chaincode} instance with the given name. This represents + * an administrative object to be used when an application is maintaining + * the lifecycle of chaincode. + * + * @param {string} name + * @param {string} version + * @returns {Chaincode} a new instance of {@link Chaincode}. + */ + newChaincode(name, version) { + if (!name) { + throw new Error('Name is a required parameter'); + } + if (!version) { + throw new Error('Version is a required parameter'); + } + + const chaincode = new Chaincode(name, version, this); + + return chaincode; + } + /** * Returns a {@link Channel} instance with the given name. This represents a channel and its associated ledger. * @@ -581,7 +604,6 @@ const Client = class extends BaseClient { return (identity && identity.getMSPId()) || this._clientConfigMspid; } - /** * Returns a new {@link TransactionID} object. Fabric transaction ids are constructed * as a hash of a nonce concatenated with the signing identity's serialized bytes. The @@ -1027,7 +1049,7 @@ const Client = class extends BaseClient { } /** - * @typedef {Object} ChaincodeInstallRequest + * @typedef {Object} Deprecated_ChaincodeInstallRequest * @property {Peer[] | string[]} targets - Optional. An array of Peer objects or peer names * where the chaincode will be installed. When excluded, the peers assigned * to this client's organization will be used as defined in the @@ -1083,7 +1105,8 @@ const Client = class extends BaseClient { * performed on a peer-by-peer basis. Only the peer organization's ADMIN * identities are allowed to perform this operation. * - * @param {ChaincodeInstallRequest} request - The request object + * @deprecated + * @param {Deprecated_ChaincodeInstallRequest} request - The request object * @param {Number} timeout - A number indicating milliseconds to wait on the * response before rejecting the promise with a timeout error. This * overrides the default timeout of the Peer instance and the global diff --git a/fabric-client/lib/protos/peer/chaincode_shim.proto b/fabric-client/lib/protos/peer/chaincode_shim.proto new file mode 100644 index 0000000000..d892266489 --- /dev/null +++ b/fabric-client/lib/protos/peer/chaincode_shim.proto @@ -0,0 +1,184 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +syntax = "proto3"; + +package protos; +option java_package = "org.hyperledger.fabric.protos.peer"; +option go_package = "github.com/hyperledger/fabric/protos/peer"; +import "chaincode_event.proto"; +import "proposal.proto"; +import "../google/protobuf/timestamp.proto"; + + +message ChaincodeMessage { + + enum Type { + UNDEFINED = 0; + REGISTER = 1; + REGISTERED = 2; + INIT = 3; + READY = 4; + TRANSACTION = 5; + COMPLETED = 6; + ERROR = 7; + GET_STATE = 8; + PUT_STATE = 9; + DEL_STATE = 10; + INVOKE_CHAINCODE = 11; + RESPONSE = 13; + GET_STATE_BY_RANGE = 14; + GET_QUERY_RESULT = 15; + QUERY_STATE_NEXT = 16; + QUERY_STATE_CLOSE = 17; + KEEPALIVE = 18; + GET_HISTORY_FOR_KEY = 19; + GET_STATE_METADATA = 20; + PUT_STATE_METADATA = 21; + GET_PRIVATE_DATA_HASH = 22; + } + + Type type = 1; + google.protobuf.Timestamp timestamp = 2; + bytes payload = 3; + string txid = 4; + + SignedProposal proposal = 5; + + //event emitted by chaincode. Used only with Init or Invoke. + // This event is then stored (currently) + //with Block.NonHashData.TransactionResult + ChaincodeEvent chaincode_event = 6; + + //channel id + string channel_id = 7; +} + +// TODO: We need to finalize the design on chaincode container +// compatibility upon upgrade, see FAB-5777. + +// GetState is the payload of a ChaincodeMessage. It contains a key which +// is to be fetched from the ledger. If the collection is specified, the key +// would be fetched from the collection (i.e., private state) +message GetState { + string key = 1; + string collection = 2; +} + +message GetStateMetadata { + string key = 1; + string collection = 2; +} + +// PutState is the payload of a ChaincodeMessage. It contains a key and value +// which needs to be written to the transaction's write set. If the collection is +// specified, the key and value would be written to the transaction's private +// write set. +message PutState { + string key = 1; + bytes value = 2; + string collection = 3; +} + +message PutStateMetadata { + string key = 1; + string collection = 3; + StateMetadata metadata = 4; +} + +// DelState is the payload of a ChaincodeMessage. It contains a key which +// needs to be recorded in the transaction's write set as a delete operation. +// If the collection is specified, the key needs to be recorded in the +// transaction's private write set as a delete operation. +message DelState { + string key = 1; + string collection = 2; +} + +// GetStateByRange is the payload of a ChaincodeMessage. It contains a start key and +// a end key required to execute range query. If the collection is specified, +// the range query needs to be executed on the private data. The metadata hold +// the byte representation of QueryMetadata. +message GetStateByRange { + string startKey = 1; + string endKey = 2; + string collection = 3; + bytes metadata = 4; +} + +// GetQueryResult is the payload of a ChaincodeMessage. It contains a query +// string in the form that is supported by the underlying state database. +// If the collection is specified, the query needs to be executed on the +// private data. The metadata hold the byte representation of QueryMetadata. +message GetQueryResult { + string query = 1; + string collection = 2; + bytes metadata = 3; +} + +// QueryMetadata is the metadata of a GetStateByRange and GetQueryResult. +// It contains a pageSize which denotes the number of records to be fetched +// and a bookmark. +message QueryMetadata { + int32 pageSize = 1; + string bookmark = 2; +} + +// GetHistoryForKey is the payload of a ChaincodeMessage. It contains a key +// for which the historical values need to be retrieved. +message GetHistoryForKey { + string key = 1; +} + +message QueryStateNext { + string id = 1; +} + +message QueryStateClose { + string id = 1; +} + +// QueryResultBytes hold the byte representation of a record returned by the peer. +message QueryResultBytes { + bytes resultBytes = 1; +} + +// QueryResponse is returned by the peer as a result of a GetStateByRange, +// GetQueryResult, and GetHistoryForKey. It holds a bunch of records in +// results field, a flag to denote whether more results need to be fetched from +// the peer in has_more field, transaction id in id field, and a QueryResponseMetadata +// in metadata field. +message QueryResponse { + repeated QueryResultBytes results = 1; + bool has_more = 2; + string id = 3; + bytes metadata = 4; +} + +// QueryResponseMetadata is the metadata of a QueryResponse. It contains a count +// which denotes the number of records fetched from the ledger and a bookmark. +message QueryResponseMetadata { + int32 fetched_records_count = 1; + string bookmark = 2; +} + +message StateMetadata { + string metakey = 1; + bytes value = 2; +} + +message StateMetadataResult { + repeated StateMetadata entries = 1; +} + +// Interface that provides support to chaincode execution. ChaincodeContext +// provides the context necessary for the server to respond appropriately. +service ChaincodeSupport { + + rpc Register(stream ChaincodeMessage) returns (stream ChaincodeMessage) {} + + +} diff --git a/fabric-client/test/Chaincode.js b/fabric-client/test/Chaincode.js new file mode 100644 index 0000000000..e9b7152581 --- /dev/null +++ b/fabric-client/test/Chaincode.js @@ -0,0 +1,333 @@ +/* + * 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. + */ + +'use strict'; + +const rewire = require('rewire'); + +const Chaincode = rewire('../lib/Chaincode'); +const Client = require('../lib/Client'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinon = require('sinon'); +const should = chai.should(); +chai.use(chaiAsPromised); + +function propertiesToBeEqual(obj, properties, value) { + properties.forEach((prop) => { + if (obj.hasOwnProperty(prop)) { + should.equal(obj[prop], value); + } else { + should.fail(); + } + }); +} + +function propertiesToBeNull(obj, properties) { + return propertiesToBeEqual(obj, properties, null); +} + +function propertiesToBeInstanceOf(obj, properties, clazz) { + properties.forEach((prop) => { + if (obj.hasOwnProperty(prop)) { + obj[prop].should.be.instanceof(clazz); + } else { + should.fail(); + } + }); +} + +describe('Chaincode', () => { + let sandbox; + let revert; + let FakeLogger; + + const ENDORSEMENT_POLICY = { + identities: [ + {role: {name: 'member', mspId: 'org1'}}, + {role: {name: 'member', mspId: 'org2'}} + ], + policy: { + '1-of': [{'signed-by': 0}, {'signed-by': 1}] + } + }; + + const COLLECTION_CONFIG = + [{ + name: 'detailCol', + policy: { + identities: [ + {role: {name: 'member', mspId: 'Org1MSP'}}, + {role: {name: 'member', mspId: 'Org2MSP'}} + ], + policy: { + '1-of': [ + {'signed-by': 0}, + {'signed-by': 1} + ] + } + }, + requiredPeerCount: 1, + maxPeerCount: 1, + blockToLive: 100 + }]; + + beforeEach(() => { + revert = []; + sandbox = sinon.createSandbox(); + + FakeLogger = { + debug: () => { }, + error: () => { } + }; + sandbox.stub(FakeLogger); + revert.push(Chaincode.__set__('logger', FakeLogger)); + }); + + afterEach(() => { + if (revert.length) { + revert.forEach(Function.prototype.call, Function.prototype.call); + } + sandbox.restore(); + }); + + describe('#constructor', () => { + it('should create an instance and define the correct properties', () => { + const client = new Client(); + const chaincode = new Chaincode('mychaincode', 'v1', client); + propertiesToBeNull(chaincode, ['_package', '_hash', '_endorsement_policy_proto', '_endorsement_policy_json', '_collection_config_proto', '_collection_config_json']); + propertiesToBeInstanceOf(chaincode, ['_client'], Client); + chaincode._name.should.equal('mychaincode'); + chaincode._version.should.equal('v1'); + }); + }); + + describe('#toString', () => { + it('should get the object contents in string form', () => { + const client = new Client(); + const chaincode = new Chaincode('mychaincode', 'v1', client); + const value = chaincode.toString(); + should.equal(value, 'Chaincode : {name : mychaincode, version : v1, sequence : null, hash : null}'); + }); + }); + + + describe('#...Getters and Setters and Has-ers', () => { + const client = new Client(); + let chaincode; + + beforeEach(() => { + chaincode = new Chaincode('mychaincode', 'v1', client); + }); + + it('should get the name', () => { + const value = chaincode.getName(); + should.equal(value, 'mychaincode'); + }); + + it('should get the version', () => { + const value = chaincode.getVersion(); + should.equal(value, 'v1'); + }); + + it('should get the sequence', () => { + const value = chaincode.getSequence(); + should.equal(value, null); + }); + + it('should set the sequence', () => { + chaincode.setSequence(9); + chaincode._sequence.should.equal(9); + }); + + it('should get error on empty sequence', () => { + (() => { + chaincode.setSequence(); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on null sequence', () => { + (() => { + chaincode.setSequence(null); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on character sequence', () => { + (() => { + chaincode.setSequence('aa'); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on zero sequence', () => { + (() => { + chaincode.setSequence(0); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on negative sequence', () => { + (() => { + chaincode.setSequence(-1); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on floating point sequence', () => { + (() => { + chaincode.setSequence(2.2); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get error on character numbers sequence', () => { + (() => { + chaincode.setSequence('1'); + }).should.throw('Sequence value must be an integer greater than zero'); + }); + + it('should get the package', () => { + const value = chaincode.getPackage(); + should.equal(value, null); + }); + + it('should set the package', () => { + chaincode.setPackage('DUMMY'); + chaincode._package.should.equal('DUMMY'); + }); + + it('check the hasPackage true', () => { + chaincode._package = {}; + const check = chaincode.hasPackage(); + should.equal(check, true); + }); + + it('check the hasPackage false', () => { + const check = chaincode.hasPackage(); + should.equal(check, false); + }); + }); + + describe('#setEndorsementPolicy', () => { + const client = new Client(); + let chaincode; + + beforeEach(() => { + chaincode = new Chaincode('mychaincode', 'v1', client); + }); + + it('should require a policy', () => { + (() => { + chaincode.setEndorsementPolicy(); + }).should.throw('A JSON policy parameter is required'); + }); + + it('should require a valid policy', () => { + (() => { + chaincode.setEndorsementPolicy({}); + }).should.throw('Invalid policy, missing the "identities" property'); + }); + + it('should set the endorsement policy using an object', () => { + chaincode.setEndorsementPolicy(ENDORSEMENT_POLICY); + chaincode._endorsement_policy_json.should.equal(ENDORSEMENT_POLICY); + }); + }); + + describe('#setCollectionConfig', () => { + const client = new Client(); + let chaincode; + + beforeEach(() => { + chaincode = new Chaincode('mychaincode', 'v1', client); + }); + + it('should require a config', () => { + (() => { + chaincode.setCollectionConfig(); + }).should.throw('A JSON config parameter is required'); + }); + + it('should require a valid config', () => { + (() => { + chaincode.setCollectionConfig({}); + }).should.throw('Expect collections config of type Array'); + }); + + it('should set the collection config using an object', () => { + chaincode.setCollectionConfig(COLLECTION_CONFIG); + chaincode._collection_config_json.should.equal(COLLECTION_CONFIG); + }); + }); + + describe('#install', () => { + const client = new Client(); + let chaincode; + + beforeEach(() => { + chaincode = new Chaincode('mychaincode', 'v1', client); + }); + + it('should require a package', async () => { + try { + await chaincode.install(); + should.fail(); + } catch (err) { + err.message.should.equal('Install operation requires a ChaincodeInstallRequest object parameter'); + } + }); + + it('should require a package', async () => { + try { + await chaincode.install({}); + should.fail(); + } catch (err) { + err.message.should.equal('Install operation requires a chaincode package be assigned to this chaincode'); + } + }); + }); + + describe('#package', () => { + const client = new Client(); + let chaincode; + + beforeEach(() => { + chaincode = new Chaincode('mychaincode', 'v1', client); + }); + + it('should require a package request object parameter', async () => { + try { + await chaincode.package(); + should.fail(); + } catch (err) { + err.message.should.equal('ChaincodeInstallRequest object parameter is required'); + } + }); + + it('should require a package request chaincodeType parameter', async () => { + try { + await chaincode.package({}); + should.fail(); + } catch (err) { + err.message.should.equal('Chaincode package "chaincodeType" parameter is required'); + } + }); + + it('should require a package request chaincodeType parameter', async () => { + try { + await chaincode.package({chaincodeType: 'bad'}); + should.fail(); + } catch (err) { + err.message.should.equal('Chaincode package "chaincodeType" parameter is not a known type bad'); + } + }); + }); +}); diff --git a/fabric-client/test/Channel.js b/fabric-client/test/Channel.js index f8b36b2952..2508fbda79 100644 --- a/fabric-client/test/Channel.js +++ b/fabric-client/test/Channel.js @@ -20,12 +20,13 @@ const rewire = require('rewire'); const chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); const expect = chai.expect; -chai.should(); +const should = chai.should(); const Long = require('long'); const Channel = require('fabric-client/lib/Channel'); const ChannelRewire = rewire('fabric-client/lib/Channel'); const ChannelEventHub = require('fabric-client/lib/ChannelEventHub'); +const Chaincode = require('fabric-client/lib/Chaincode'); const Client = require('fabric-client/lib/Client'); const {Identity, SigningIdentity} = require('fabric-client/lib/msp/identity'); const MSP = require('fabric-client/lib/msp/msp'); @@ -2370,6 +2371,89 @@ describe('Channel', () => { describe('#_sendChaincodeProposal', () => {}); + describe('#_verifyChaincodeRequest', () => { + const chaincode = sinon.createStubInstance(Chaincode); + + it('should check for a request input object parameter', async () => { + try { + channel._verifyChaincodeRequest(); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter'); + } + try { + channel._verifyChaincodeRequest({}); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter "chaincode"'); + } + try { + chaincode.hasHash.returns(false); + channel._verifyChaincodeRequest({chaincode: chaincode}); + should.fail(); + } catch (err) { + err.message.should.equal('Chaincode definition must include the chaincode hash value'); + } + }); + + }); + + describe('#allowChaincodeForOrg', () => { + it('should require a request object parameter', async () => { + try { + await channel.allowChaincodeForOrg(); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter'); + } + }); + it('should require a request.chaincode object parameter', async () => { + try { + await channel.allowChaincodeForOrg({}); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter "chaincode"'); + } + }); + it('should require a request.chaincode._hash object parameter', async () => { + try { + const chaincode = client.newChaincode('mychaincode', 'v1'); + await channel.allowChaincodeForOrg({chaincode: chaincode}); + should.fail(); + } catch (err) { + err.message.should.equal('Chaincode definition must include the chaincode hash value'); + } + }); + }); + + describe('#CommitChaincode', () => { + it('should require a request object parameter', async () => { + try { + await channel.CommitChaincode(); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter'); + } + }); + it('should require a request.chaincode object parameter', async () => { + try { + await channel.CommitChaincode({}); + should.fail(); + } catch (err) { + err.message.should.equal('Missing required request parameter "chaincode"'); + } + }); + it('should require a request.chaincode._hash object parameter', async () => { + try { + const chaincode = client.newChaincode('mychaincode', 'v1'); + await channel.CommitChaincode({chaincode: chaincode}); + should.fail(); + } catch (err) { + err.message.should.equal('Chaincode definition must include the chaincode hash value'); + } + }); + }); + describe('#sendTransactionProposal', () => {}); describe('sendTransactionProposal', () => {}); diff --git a/fabric-client/test/Client.js b/fabric-client/test/Client.js index 9e73036665..311712e777 100644 --- a/fabric-client/test/Client.js +++ b/fabric-client/test/Client.js @@ -3205,4 +3205,15 @@ describe('Client', () => { networkConfig.should.deep.equal(new MockNetworkConfig()); }); }); + + describe('#newChaincode', () => { + it('should create and return a new chaincode instance', () => { + const chaincodeStub = sinon.stub(); + revert.push(Client.__set__('Chaincode', chaincodeStub)); + const client = new Client(); + const chaincode = client.newChaincode('mychaincode', 'v1'); + sinon.assert.calledWith(chaincodeStub, 'mychaincode', 'v1', client); + chaincode.should.deep.equal(new chaincodeStub('mychaincode', 'v1', client)); + }); + }); });