diff --git a/commercial-paper/.gitignore b/commercial-paper/.gitignore new file mode 100644 index 0000000000..14c40052e2 --- /dev/null +++ b/commercial-paper/.gitignore @@ -0,0 +1,8 @@ +organization/magnetocorp/application/node_modules/ +organization/magnetocorp/contract/node_modules/ +organization/magnetocorp/identity/user/ +organization/digibank/application/node_modules/ +organization/digibank/contract/node_modules/ +organization/digibank/identity/user/ +package-lock.json +.vscode diff --git a/commercial-paper/application/application.js b/commercial-paper/application/application.js deleted file mode 100644 index af11620c85..0000000000 --- a/commercial-paper/application/application.js +++ /dev/null @@ -1,76 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -/* - * This application has 6 basic steps: - * 1. Select an identity from a wallet - * 2. Connect to network gateway - * 3. Access PaperNet network - * 4. Construct request to issue commercial paper - * 5. Submit transaction - * 6. Process response - */ - -'use strict'; - -// Bring key classes into scope, most importantly Fabric SDK network class -const file = require("fs"); -const yaml = require('js-yaml'); -const { FileSystemWallet, Gateway } = require('fabric-network'); -const { CommercialPaper } = require('./paper.js'); - -// A wallet stores a collection of identities for use -const wallet = new FileSystemWallet('./wallet'); - -// A gateway defines the peers used to access Fabric networks -const gateway = new Gateway(); - -// Main try/catch/finally block -try { - - // Load connection profile; will be used to locate a gateway - connectionProfile = yaml.safeLoad(file.readFileSync('./gateway/connectionProfile.yaml', 'utf8')); - - // Set connection options; use 'admin' identity from application wallet - let connectionOptions = { - identity: 'isabella.the.issuer@magnetocorp.com', - wallet: wallet, - commitTimeout: 100, - strategy: MSPID_SCOPE_ANYFORTX, - commitNotifyStrategy: WAIT_FOR_ALL_CHANNEL_PEER - } - - // Connect to gateway using application specified parameters - await gateway.connect(connectionProfile, connectionOptions); - - console.log('Connected to Fabric gateway.') - - // Get addressability to PaperNet network - const network = await gateway.getNetwork('PaperNet'); - - // Get addressability to commercial paper contract - const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper'); - - console.log('Submit commercial paper issue transaction.') - - // issue commercial paper - const response = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000'); - - let paper = CommercialPaper.deserialize(response); - - console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully issued for value ${paper.faceValue}`); - - console.log('Transaction complete.') - -} catch (error) { - - console.log(`Error processing transaction. ${error}`); - -} finally { - - // Disconnect from the gateway - console.log('Disconnect from Fabric gateway.') - gateway.disconnect(); - -} \ No newline at end of file diff --git a/commercial-paper/contract/index.js b/commercial-paper/contract/index.js deleted file mode 100644 index ddc4f2c6fb..0000000000 --- a/commercial-paper/contract/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -'use strict'; - -module.exports.contracts = require('./lib/cpcontract.js'); diff --git a/commercial-paper/contract/lib/cpcontract.js b/commercial-paper/contract/lib/cpcontract.js deleted file mode 100644 index ed793fd16f..0000000000 --- a/commercial-paper/contract/lib/cpcontract.js +++ /dev/null @@ -1,113 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -'use strict'; - -// Smart contract API brought into scope -const {Contract} = require('fabric-contract-api'); - -// Commercial paper classes brought into scope -const {CommercialPaper, CommercialPaperList} = require('./cpstate.js'); - -/** - * Define the commercial paper smart contract extending Fabric Contract class - */ -class CommercialPaperContract extends Contract { - - /** - * Each smart contract can have a unique namespace; useful when multiple - * smart contracts per file. - * Use transaction context (ctx) to access list of all commercial papers. - */ - constructor() { - super('org.papernet.commercialpaper'); - - this.setBeforeFn = (ctx)=>{ - ctx.cpList = new CommercialPaperList(ctx, 'COMMERCIALPAPER'); - return ctx; - }; - } - - /** - * Issue commercial paper - * @param {TxContext} ctx the transaction context - * @param {String} issuer commercial paper issuer - * @param {Integer} paperNumber paper number for this issuer - * @param {String} issueDateTime paper issue date - * @param {String} maturityDateTime paper maturity date - * @param {Integer} faceValue face value of paper - */ - async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { - - let cp = new CommercialPaper(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue); - - // {issuer:"MagnetoCorp", paperNumber:"00001", "May31 2020", "Nov 30 2020", "5M USD"} - - await ctx.cpList.addPaper(cp); - } - - /** - * Buy commercial paper - * @param {TxContext} ctx the transaction context - * @param {String} issuer commercial paper issuer - * @param {Integer} paperNumber paper number for this issuer - * @param {String} currentOwner current owner of paper - * @param {String} newOwner new owner of paper - * @param {Integer} price price paid for this paper - * @param {String} purchaseDateTime time paper was purchased (i.e. traded) - */ - async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) { - - let cpKey = CommercialPaper.createKey(issuer, paperNumber); - let cp = await ctx.cpList.getPaper(cpKey); - - if (cp.getOwner() !== currentOwner) { - throw new Error('Paper '+issuer+paperNumber+' is not owned by '+currentOwner); - } - // First buy moves state from ISSUED to TRADING - if (cp.isIssued()) { - cp.setTrading(); - } - // Check paper is TRADING, not REDEEMED - if (cp.IsTrading()) { - cp.setOwner(newOwner); - } else { - throw new Error('Paper '+issuer+paperNumber+' is not trading. Current state = '+cp.getCurrentState()); - } - - await ctx.cpList.updatePaper(cp); - } - - /** - * Redeem commercial paper - * @param {TxContext} ctx the transaction context - * @param {String} issuer commercial paper issuer - * @param {Integer} paperNumber paper number for this issuer - * @param {String} redeemingOwner redeeming owner of paper - * @param {String} redeemDateTime time paper was redeemed - */ - async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) { - - let cpKey = CommercialPaper.createKey(issuer, paperNumber); - let cp = await ctx.cpList.getPaper(cpKey); - - // Check paper is TRADING, not REDEEMED - if (cp.IsRedeemed()) { - throw new Error('Paper '+issuer+paperNumber+' already redeemed'); - } - - // Verify that the redeemer owns the commercial paper before redeeming it - if (cp.getOwner() === redeemingOwner) { - cp.setOwner(cp.getIssuer()); - cp.setRedeemed(); - } else { - throw new Error('Redeeming owner does not own paper'+issuer+paperNumber); - } - - await ctx.cpList.updatePaper(cp); - } - -} - -module.exports = CommericalPaperContract; diff --git a/commercial-paper/contract/lib/cpstate.js b/commercial-paper/contract/lib/cpstate.js deleted file mode 100644 index ad5222cde2..0000000000 --- a/commercial-paper/contract/lib/cpstate.js +++ /dev/null @@ -1,148 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -'use strict'; - -// Helpful utilities class -const Utils = require('./utils.js'); - -// Enumeration of commercial paper state values -const cpState = { - ISSUED: 1, - TRADING: 2, - REDEEMED: 3 -}; - -/** - * CommercialPaper class defines a commercial paper state - */ -class CommercialPaper { - - /** - * Construct a commercial paper. Initial state is issued. - */ - constructor(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { - this.issuer = issuer; - this.paperNumber = paperNumber; - this.owner = issuer; - this.issueDateTime = issueDateTime; - this.maturityDateTime = maturityDateTime; - this.faceValue = faceValue; - this.currentState = cpState.ISSUED; - this.key = CommercialPaper.createKey(issuer, paperNumber); - } - - /** - * The commercial paper is uniquely identified by its key. - * The key is a simple composite of issuer and paper number as strings. - */ - static createKey(issuer, paperNumber) { - return JSON.stringify(issuer) + JSON.stringify(paperNumber); - } - - /** - * Basic getters and setters - */ - getKey() { - return this.key; - } - - getIssuer() { - return this.issuer; - } - - setIssuer(newIssuer) { - this.issuer = newIssuer; - } - - getOwner() { - return this.owner; - } - - setOwner(newOwner) { - this.owner = newOwner; - } - - /** - * Useful methods to encapsulate commercial paper states - */ - setTrading() { - this.currentState = cpState.TRADING; - } - - setRedeemed() { - this.currentState = cpState.REDEEMED; - } - - isTrading() { - return this.currentState === cpState.TRADING; - } - - isRedeemed() { - return this.currentState === cpState.REDEEMED; - } - -} - -/** - * CommercialPaperList provides a virtual container to access all - * commercial papers. Each paper has unique key which associates it - * with the container, rather than the container containing a link to - * the paper. This is important in Fabric becuase it minimizes - * collisions for parallel transactions on different papers. - */ -class CommercialPaperList { - - /** - * For this sample, it is sufficient to create a commercial paper list - * using a fixed container prefix. The transaction context is saved to - * access Fabric APIs when required. - */ - constructor(ctx, prefix) { - this.api = ctx.stub; - this.prefix = prefix; - } - - /** - * Add a paper to the list. Creates a new state in worldstate with - * appropriate composite key. Note that paper defines its own key. - * Paper object is serialized before writing. - */ - async addPaper(cp) { - let key = this.api.createCompositeKey(this.prefix, [cp.getKey()]); - let data = Utils.serialize(cp); - await this.api.putState(key, data); - } - - /** - * Get a paper from the list using issuer and paper number. Forms composite - * keys to retrieve data from world state. State data is deserialized - * into paper object before being returned. - */ - async getPaper(key) { - let key = this.api.createCompositeKey(this.prefix, [key]); - let data = await this.api.getState(key); - let cp = Utils.deserialize(data); - return cp; - } - - /** - * Update a paper in the list. Puts the new state in world state with - * appropriate composite key. Note that paper defines its own key. - * Paper object is serialized before writing. Logic is very similar to - * addPaper() but kept separate becuase it is semantically distinct, and - * may change. - */ - async updatePaper(cp) { - let key = this.api.createCompositeKey(this.prefix, [cp.getKey()]); - let data = Utils.serialize(cp); - await this.api.putState(key, data); - } - -} - -module.exports = { - CommercialPaper, - CommercialPaperList -}; diff --git a/commercial-paper/contract/lib/utils.js b/commercial-paper/contract/lib/utils.js deleted file mode 100644 index 2a2c08ad70..0000000000 --- a/commercial-paper/contract/lib/utils.js +++ /dev/null @@ -1,34 +0,0 @@ -/* -SPDX-License-Identifier: Apache-2.0 -*/ - -'use strict'; - -/** - * Utility class for data, object mapulation, e.g. serialization - */ - -class Utils { - - /** - * Convert object to buffer containing JSON data serialization - * Typically used before putState() ledger API - * @param {Object} object object to serialize - * @return {buffer} buffer with the data to store - */ - static serialize(object){ - return Buffer.from(JSON.stringify(object)); - } - - /** - * Deserialize object, i.e. Covert serialized data to JSON object - * Typically used after getState() ledger API - * @param {Object} data object to deserialize - * @return {json} json with the data to store - */ - static deserialize(data){ - return JSON.parse(data); - } -} - -module.exports = Utils; diff --git a/commercial-paper/organization/digibank/application/.eslintrc.js b/commercial-paper/organization/digibank/application/.eslintrc.js new file mode 100644 index 0000000000..22fbefc85c --- /dev/null +++ b/commercial-paper/organization/digibank/application/.eslintrc.js @@ -0,0 +1,37 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +module.exports = { + env: { + node: true, + mocha: true + }, + parserOptions: { + ecmaVersion: 8, + sourceType: 'script' + }, + extends: "eslint:recommended", + rules: { + indent: ['error', 4], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'no-unused-vars': ['error', { args: 'none' }], + 'no-console': 'off', + curly: 'error', + eqeqeq: 'error', + 'no-throw-literal': 'error', + strict: 'error', + 'no-var': 'error', + 'dot-notation': 'error', + 'no-tabs': 'error', + 'no-trailing-spaces': 'error', + 'no-use-before-define': 'error', + 'no-useless-call': 'error', + 'no-with': 'error', + 'operator-linebreak': 'error', + yoda: 'error', + 'quote-props': ['error', 'as-needed'] + } +}; \ No newline at end of file diff --git a/commercial-paper/organization/digibank/application/addToWallet.js b/commercial-paper/organization/digibank/application/addToWallet.js new file mode 100644 index 0000000000..b2b4415f9c --- /dev/null +++ b/commercial-paper/organization/digibank/application/addToWallet.js @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +// Bring key classes into scope, most importantly Fabric SDK network class +const fs = require('fs'); +const { FileSystemWallet, X509WalletMixin } = require('fabric-network'); +const path = require('path'); + +const fixtures = path.resolve(__dirname, '../../../../basic-network'); + +// A wallet stores a collection of identities +const wallet = new FileSystemWallet('../identity/user/balaji/wallet'); + +async function main() { + + // Main try/catch block + try { + + // Identity to credentials to be stored in the wallet + const credPath = path.join(fixtures, '/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com'); + const cert = fs.readFileSync(path.join(credPath, '/msp/signcerts/Admin@org1.example.com-cert.pem')).toString(); + const key = fs.readFileSync(path.join(credPath, '/msp/keystore/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec_sk')).toString(); + + // Load credentials into wallet + const identityLabel = 'Admin@org1.example.com'; + const identity = X509WalletMixin.createIdentity('Org1MSP', cert, key); + + await wallet.import(identityLabel, identity); + + } catch (error) { + console.log(`Error adding to wallet. ${error}`); + console.log(error.stack); + } +} + +main().then(() => { + console.log('done'); +}).catch((e) => { + console.log(e); + console.log(e.stack); + process.exit(-1); +}); \ No newline at end of file diff --git a/commercial-paper/organization/digibank/application/buy.js b/commercial-paper/organization/digibank/application/buy.js new file mode 100644 index 0000000000..45267dc051 --- /dev/null +++ b/commercial-paper/organization/digibank/application/buy.js @@ -0,0 +1,102 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +/* + * This application has 6 basic steps: + * 1. Select an identity from a wallet + * 2. Connect to network gateway + * 3. Access PaperNet network + * 4. Construct request to issue commercial paper + * 5. Submit transaction + * 6. Process response + */ + +'use strict'; + +// Bring key classes into scope, most importantly Fabric SDK network class +const fs = require('fs'); +const yaml = require('js-yaml'); +const { FileSystemWallet, Gateway } = require('fabric-network'); +const CommercialPaper = require('../contract/lib/paper.js'); + +// A wallet stores a collection of identities for use +const wallet = new FileSystemWallet('../identity/user/balaji/wallet'); + +// Main program function +async function main() { + + // A gateway defines the peers used to access Fabric networks + const gateway = new Gateway(); + + // Main try/catch block + try { + + // Specify userName for network access + // const userName = 'isabella.issuer@magnetocorp.com'; + const userName = 'Admin@org1.example.com'; + + // Load connection profile; will be used to locate a gateway + let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/networkConnection.yaml', 'utf8')); + + // Set connection options; identity and wallet + let connectionOptions = { + identity: userName, + wallet: wallet, + discovery: { enabled:false, asLocalhost: true } + + }; + + // Connect to gateway using application specified parameters + console.log('Connect to Fabric gateway.'); + + await gateway.connect(connectionProfile, connectionOptions); + + // Access PaperNet network + console.log('Use network channel: mychannel.'); + + const network = await gateway.getNetwork('mychannel'); + + // Get addressability to commercial paper contract + console.log('Use org.papernet.commercialpaper smart contract.'); + + const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper'); + + // buy commercial paper + console.log('Submit commercial paper buy transaction.'); + + const buyResponse = await contract.submitTransaction('buy', 'MagnetoCorp', '00001', 'MagnetoCorp', 'DigiBank', '4900000', '2020-05-31'); + + // process response + console.log('Process buy transaction response.'); + + let paper = CommercialPaper.fromBuffer(buyResponse); + + console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully purchased by ${paper.owner}`); + console.log('Transaction complete.'); + + } catch (error) { + + console.log(`Error processing transaction. ${error}`); + console.log(error.stack); + + } finally { + + // Disconnect from the gateway + console.log('Disconnect from Fabric gateway.') + gateway.disconnect(); + + } +} +main().then(() => { + + console.log('Buy program complete.'); + +}).catch((e) => { + + console.log('Buy program exception.'); + console.log(e); + console.log(e.stack); + process.exit(-1); + +}); \ No newline at end of file diff --git a/commercial-paper/organization/digibank/application/package.json b/commercial-paper/organization/digibank/application/package.json new file mode 100644 index 0000000000..b31c669a5b --- /dev/null +++ b/commercial-paper/organization/digibank/application/package.json @@ -0,0 +1,20 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "buy.js", + "scripts": { + "test": "rm -rf _idwallet && node addToWallet.js && node buy.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "fabric-network": "^1.4.0-beta", + "fabric-client": "^1.4.0-beta", + "js-yaml": "^3.12.0" + }, + "devDependencies": { + "eslint": "^5.6.0" + } +} diff --git a/commercial-paper/organization/digibank/application/redeem.js b/commercial-paper/organization/digibank/application/redeem.js new file mode 100644 index 0000000000..3f3ffd45ae --- /dev/null +++ b/commercial-paper/organization/digibank/application/redeem.js @@ -0,0 +1,101 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +/* + * This application has 6 basic steps: + * 1. Select an identity from a wallet + * 2. Connect to network gateway + * 3. Access PaperNet network + * 4. Construct request to issue commercial paper + * 5. Submit transaction + * 6. Process response + */ + +'use strict'; + +// Bring key classes into scope, most importantly Fabric SDK network class +const fs = require('fs'); +const yaml = require('js-yaml'); +const { FileSystemWallet, Gateway } = require('fabric-network'); +const CommercialPaper = require('../contract/lib/paper.js'); + +// A wallet stores a collection of identities for use +const wallet = new FileSystemWallet('../identity/user/balaji/wallet'); + +// Main program function +async function main() { + + // A gateway defines the peers used to access Fabric networks + const gateway = new Gateway(); + + // Main try/catch block + try { + + // Specify userName for network access + // const userName = 'isabella.issuer@magnetocorp.com'; + const userName = 'Admin@org1.example.com'; + + // Load connection profile; will be used to locate a gateway + let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/networkConnection.yaml', 'utf8')); + + // Set connection options; identity and wallet + let connectionOptions = { + identity: userName, + wallet: wallet, + discovery: { enabled:false, asLocalhost: true } + }; + + // Connect to gateway using application specified parameters + console.log('Connect to Fabric gateway.'); + + await gateway.connect(connectionProfile, connectionOptions); + + // Access PaperNet network + console.log('Use network channel: mychannel.'); + + const network = await gateway.getNetwork('mychannel'); + + // Get addressability to commercial paper contract + console.log('Use org.papernet.commercialpaper smart contract.'); + + const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper'); + + // redeem commercial paper + console.log('Submit commercial paper redeem transaction.'); + + const redeemResponse = await contract.submitTransaction('redeem', 'MagnetoCorp', '00001', 'DigiBank', '2020-11-30'); + + // process response + console.log('Process redeem transaction response.'); + + let paper = CommercialPaper.fromBuffer(redeemResponse); + + console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully redeemed with ${paper.owner}`); + console.log('Transaction complete.'); + + } catch (error) { + + console.log(`Error processing transaction. ${error}`); + console.log(error.stack); + + } finally { + + // Disconnect from the gateway + console.log('Disconnect from Fabric gateway.') + gateway.disconnect(); + + } +} +main().then(() => { + + console.log('Redeem program complete.'); + +}).catch((e) => { + + console.log('Redeem program exception.'); + console.log(e); + console.log(e.stack); + process.exit(-1); + +}); \ No newline at end of file diff --git a/commercial-paper/organization/digibank/configuration/cli/docker-compose.yml b/commercial-paper/organization/digibank/configuration/cli/docker-compose.yml new file mode 100644 index 0000000000..554bdda30b --- /dev/null +++ b/commercial-paper/organization/digibank/configuration/cli/docker-compose.yml @@ -0,0 +1,38 @@ +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# +version: '2' + +networks: + basic: + external: + name: net_basic + +services: + cliDigiBank: + container_name: cliDigiBank + image: hyperledger/fabric-tools + tty: true + environment: + - GOPATH=/opt/gopath + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_LOGGING_LEVEL=info + - CORE_PEER_ID=cli + - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 + - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + - CORE_CHAINCODE_KEEPALIVE=10 + working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer + command: /bin/bash + volumes: + - /var/run/:/host/var/run/ + - ./../../../../organization/digibank:/opt/gopath/src/github.com/ + - ./../../../../../basic-network/crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ + networks: + - basic + #depends_on: + # - orderer.example.com + # - peer0.org1.example.com + # - couchdb diff --git a/commercial-paper/organization/digibank/configuration/cli/monitordocker.sh b/commercial-paper/organization/digibank/configuration/cli/monitordocker.sh new file mode 100755 index 0000000000..355acf0706 --- /dev/null +++ b/commercial-paper/organization/digibank/configuration/cli/monitordocker.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# This script uses the logspout and http stream tools to let you watch the docker containers +# in action. +# +# More information at https://github.com/gliderlabs/logspout/tree/master/httpstream + +if [ -z "$1" ]; then + DOCKER_NETWORK=basicnetwork_basic +else + DOCKER_NETWORK="$1" +fi + +echo Starting monitoring on all containers on the network ${DOCKER_NETWORK} + +docker kill logspout 2> /dev/null 1>&2 || true +docker rm logspout 2> /dev/null 1>&2 || true + +docker run -d --name="logspout" \ + --volume=/var/run/docker.sock:/var/run/docker.sock \ + --publish=127.0.0.1:8000:80 \ + --network ${DOCKER_NETWORK} \ + gliderlabs/logspout +sleep 3 +curl http://127.0.0.1:8000/logs \ No newline at end of file diff --git a/commercial-paper/contract/.editorconfig b/commercial-paper/organization/digibank/contract/.editorconfig similarity index 100% rename from commercial-paper/contract/.editorconfig rename to commercial-paper/organization/digibank/contract/.editorconfig diff --git a/commercial-paper/contract/.eslintignore b/commercial-paper/organization/digibank/contract/.eslintignore similarity index 100% rename from commercial-paper/contract/.eslintignore rename to commercial-paper/organization/digibank/contract/.eslintignore diff --git a/commercial-paper/contract/.eslintrc.js b/commercial-paper/organization/digibank/contract/.eslintrc.js similarity index 100% rename from commercial-paper/contract/.eslintrc.js rename to commercial-paper/organization/digibank/contract/.eslintrc.js diff --git a/commercial-paper/contract/.npmignore b/commercial-paper/organization/digibank/contract/.npmignore similarity index 100% rename from commercial-paper/contract/.npmignore rename to commercial-paper/organization/digibank/contract/.npmignore diff --git a/commercial-paper/organization/digibank/contract/index.js b/commercial-paper/organization/digibank/contract/index.js new file mode 100644 index 0000000000..b957b5e3f9 --- /dev/null +++ b/commercial-paper/organization/digibank/contract/index.js @@ -0,0 +1,8 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +const cpcontract = require('./lib/papercontract.js'); +module.exports.contracts = [cpcontract]; \ No newline at end of file diff --git a/commercial-paper/organization/digibank/contract/ledger-api/state.js b/commercial-paper/organization/digibank/contract/ledger-api/state.js new file mode 100644 index 0000000000..df2fbe57c6 --- /dev/null +++ b/commercial-paper/organization/digibank/contract/ledger-api/state.js @@ -0,0 +1,98 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +/** + * State class. States have a class, unique key, and a lifecycle current state + * the current state is determined by the specific subclass + */ +class State { + + /** + * @param {String|Object} class An indentifiable class of the instance + * @param {keyParts[]} elements to pull together to make a key for the objects + */ + constructor(stateClass, keyParts) { + this.class = stateClass; + this.key = State.makeKey(keyParts); + this.currentState = null; + } + + getClass() { + return this.class; + } + + getKey() { + return this.key; + } + + getSplitKey(){ + return State.splitKey(this.key); + } + + getCurrentState(){ + return this.currentState; + } + + serialize() { + return State.serialize(this); + } + + /** + * Convert object to buffer containing JSON data serialization + * Typically used before putState()ledger API + * @param {Object} JSON object to serialize + * @return {buffer} buffer with the data to store + */ + static serialize(object) { + return Buffer.from(JSON.stringify(object)); + } + + /** + * Deserialize object into one of a set of supported JSON classes + * i.e. Covert serialized data to JSON object + * Typically used after getState() ledger API + * @param {data} data to deserialize into JSON object + * @param (supportedClasses) the set of classes data can be serialized to + * @return {json} json with the data to store + */ + static deserialize(data, supportedClasses) { + let json = JSON.parse(data.toString()); + let objClass = supportedClasses[json.class]; + if (!objClass) { + throw new Error(`Unknown class of ${json.class}`); + } + let object = new (objClass)(json); + + return object; + } + + /** + * Deserialize object into specific object class + * Typically used after getState() ledger API + * @param {data} data to deserialize into JSON object + * @return {json} json with the data to store + */ + static deserializeClass(data, objClass) { + let json = JSON.parse(data.toString()); + let object = new (objClass)(json); + return object; + } + + /** + * Join the keyParts to make a unififed string + * @param (String[]) keyParts + */ + static makeKey(keyParts) { + return keyParts.map(part => JSON.stringify(part)).join(':'); + } + + static splitKey(key){ + return key.split(':'); + } + +} + +module.exports = State; \ No newline at end of file diff --git a/commercial-paper/contract/lib/ledgerutils.js b/commercial-paper/organization/digibank/contract/ledger-api/statelist.js similarity index 55% rename from commercial-paper/contract/lib/ledgerutils.js rename to commercial-paper/organization/digibank/contract/ledger-api/statelist.js index b18f60ea2f..3c39671a83 100644 --- a/commercial-paper/contract/lib/ledgerutils.js +++ b/commercial-paper/organization/digibank/contract/ledger-api/statelist.js @@ -3,32 +3,7 @@ SPDX-License-Identifier: Apache-2.0 */ 'use strict'; - -/** - * Utility class for data, object mapulation, e.g. serialization - */ -class Utils { - - /** - * Convert object to buffer containing JSON data serialization - * Typically used before putState()ledger API - * @param {Object} JSON object to serialize - * @return {buffer} buffer with the data to store - */ - static serialize(object) { - return Buffer.from(JSON.stringify(object)); - } - - /** - * Deserialize object, i.e. Covert serialized data to JSON object - * Typically used after getState() ledger API - * @param {data} data to deserialize into JSON object - * @return {json} json with the data to store - */ - static deserialize(data) { - return JSON.parse(data); - } -} +const State = require('./state.js'); /** * StateList provides a named virtual container for a set of ledger states. @@ -44,6 +19,8 @@ class StateList { constructor(ctx, listName) { this.ctx = ctx; this.name = listName; + this.supportedClasses = {}; + } /** @@ -52,8 +29,8 @@ class StateList { * State object is serialized before writing. */ async addState(state) { - let key = this.ctx.stub.createCompositeKey(this.name, [state.getKey()]); - let data = Utils.serialize(state); + let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey()); + let data = State.serialize(state); await this.ctx.stub.putState(key, data); } @@ -62,10 +39,10 @@ class StateList { * keys to retrieve state from world state. State data is deserialized * into JSON object before being returned. */ - async getState([keys]) { - let key = this.ctx.stub.createCompositeKey(this.name, [keys]); - let data = await this.ctx.stub.getState(key); - let state = Utils.deserialize(data); + async getState(key) { + let ledgerKey = this.ctx.stub.createCompositeKey(this.name, State.splitKey(key)); + let data = await this.ctx.stub.getState(ledgerKey); + let state = State.deserialize(data, this.supportedClasses); return state; } @@ -76,13 +53,16 @@ class StateList { * addState() but kept separate becuase it is semantically distinct. */ async updateState(state) { - let key = this.ctx.stub.createCompositeKey(this.name, [state.getKey()]); - let data = Utils.serialize(state); + let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey()); + let data = State.serialize(state); await this.ctx.stub.putState(key, data); } + /** Stores the class for future deserialization */ + use(stateClass) { + this.supportedClasses[stateClass.getClass()] = stateClass; + } + } -module.exports = { - StateList -}; +module.exports = StateList; \ No newline at end of file diff --git a/commercial-paper/contract/lib/paper.js b/commercial-paper/organization/digibank/contract/lib/paper.js similarity index 55% rename from commercial-paper/contract/lib/paper.js rename to commercial-paper/organization/digibank/contract/lib/paper.js index db770cfbe3..9d76adacd3 100644 --- a/commercial-paper/contract/lib/paper.js +++ b/commercial-paper/organization/digibank/contract/lib/paper.js @@ -4,6 +4,9 @@ SPDX-License-Identifier: Apache-2.0 'use strict'; +// Utility class for ledger state +const State = require('./../ledger-api/state.js'); + // Enumerate commercial paper state values const cpState = { ISSUED: 1, @@ -11,45 +14,15 @@ const cpState = { REDEEMED: 3 }; -/** - * State class. States have a type, unique key, and a lifecycle current state - */ -class State { - constructor(type, [keyParts]) { - this.type = JSON.stringify(type); - this.key = makeKey([keyParts]); - this.currentState = null; - } - - getType() { - return this.type; - } - - static makeKey([keyParts]) { - return keyParts.map(part => JSON.stringify(part)).join(''); - } - - getKey() { - return this.key; - } - -} - /** * CommercialPaper class extends State class * Class will be used by application and smart contract to define a paper */ class CommercialPaper extends State { - constructor(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { - super(`org.papernet.commercialpaper`, [issuer, paperNumber]); - - this.issuer = issuer; - this.paperNumber = paperNumber; - this.owner = issuer; - this.issueDateTime = issueDateTime; - this.maturityDateTime = maturityDateTime; - this.faceValue = faceValue; + constructor(obj) { + super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]); + Object.assign(this, obj); } /** @@ -98,20 +71,32 @@ class CommercialPaper extends State { return this.currentState === cpState.REDEEMED; } - /** - * Serialize/deserialize commercial paper - **/ + static fromBuffer(buffer) { + return CommercialPaper.deserialize(Buffer.from(JSON.parse(buffer))); + } - serialize() { + toBuffer() { return Buffer.from(JSON.stringify(this)); } + /** + * Deserialize a state data to commercial paper + * @param {Buffer} data to form back into the object + */ static deserialize(data) { - return Object.create(new CommercialPaper, JSON.parse(data)); + return State.deserializeClass(data, CommercialPaper); } + /** + * Factory method to create a commercial paper object + */ + static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { + return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue }); + } + + static getClass() { + return 'org.papernet.commercialpaper'; + } } -module.exports = { - CommercialPaper, -}; +module.exports = CommercialPaper; diff --git a/commercial-paper/contract/lib/papercontract.js b/commercial-paper/organization/digibank/contract/lib/papercontract.js similarity index 53% rename from commercial-paper/contract/lib/papercontract.js rename to commercial-paper/organization/digibank/contract/lib/papercontract.js index a4963279bb..c0b132bf79 100644 --- a/commercial-paper/contract/lib/papercontract.js +++ b/commercial-paper/organization/digibank/contract/lib/papercontract.js @@ -8,25 +8,25 @@ SPDX-License-Identifier: Apache-2.0 const { Contract, Context } = require('fabric-contract-api'); // PaperNet specifc classes -const { CommercialPaper } = require('./paper.js'); - -// Utility classes -const { StateList } = require('./ledgerutils.js'); +const CommercialPaper = require('./paper.js'); +const PaperList = require('./paperlist.js'); /** - * Define custom context for commercial paper by extending Fabric Context class + * A custom context provides easy access to list of all commercial papers */ -class CommericalPaperContext extends Context { +class CommercialPaperContext extends Context { constructor() { - // All papers held ins a list of Fabric states - this.cpList = new StateList(this, 'org.papernet.commercialpaperlist'); + super(); + // All papers are held in a list of papers + this.paperList = new PaperList(this); } } /** * Define commercial paper smart contract by extending Fabric Contract class + * */ class CommercialPaperContract extends Contract { @@ -35,20 +35,27 @@ class CommercialPaperContract extends Contract { super('org.papernet.commercialpaper'); } - // This method is called when a smart contract is instantiated - // Often used to set up the ledger main transactions are called - instantiate() { - + /** + * Define a custom context for commercial paper + */ + createContext() { + return new CommercialPaperContext(); } - // A custom context provides easy access to the list of commercial papers - createContext() { - return new CommericalPaperContext(); + /** + * Instantiate to perform any setup of the ledger that might be required. + * @param {Context} ctx the transaction context + */ + async instantiate(ctx) { + // No implementation required with this example + // It could be where data migration is performed, if necessary + console.log('Instantiate the contract'); } /** * Issue commercial paper - * @param {TxContext} ctx the transaction context + * + * @param {Context} ctx the transaction context * @param {String} issuer commercial paper issuer * @param {Integer} paperNumber paper number for this issuer * @param {String} issueDateTime paper issue date @@ -57,20 +64,26 @@ class CommercialPaperContract extends Contract { */ async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { - let cp = new CommercialPaper(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue); + // create an instance of the paper + let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue); // Smart contract, rather than paper, moves paper into ISSUED state - cp.setIssued(); + paper.setIssued(); + + // Newly issued paper is owned by the issuer + paper.setOwner(issuer); // Add the paper to the list of all similar commercial papers in the ledger world state - await ctx.cpList.addState(cp); + await ctx.paperList.addPaper(paper); - return cp.serialize(); + // Must return a serialized paper to caller of smart contract + return paper.toBuffer(); } /** * Buy commercial paper - * @param {TxContext} ctx the transaction context + * + * @param {Context} ctx the transaction context * @param {String} issuer commercial paper issuer * @param {Integer} paperNumber paper number for this issuer * @param {String} currentOwner current owner of paper @@ -80,31 +93,36 @@ class CommercialPaperContract extends Contract { */ async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) { - let cpKey = CommercialPaper.makeKey([issuer, paperNumber]); - - let cp = await ctx.cpList.getState(cpKey); + // Retrieve the current paper using key fields provided + let paperKey = CommercialPaper.makeKey([issuer, paperNumber]); + let paper = await ctx.paperList.getPaper(paperKey); - if (cp.getOwner() !== currentOwner) { + // Validate current owner + if (paper.getOwner() !== currentOwner) { throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner); } + // First buy moves state from ISSUED to TRADING - if (cp.isIssued()) { - cp.setTrading(); + if (paper.isIssued()) { + paper.setTrading(); } + // Check paper is not already REDEEMED - if (cp.IsTrading()) { - cp.setOwner(newOwner); + if (paper.isTrading()) { + paper.setOwner(newOwner); } else { throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' + cp.getCurrentState()); } - await ctx.cpList.updateState(cp); - return cp.deserialize(); + // Update the paper + await ctx.paperList.updatePaper(paper); + return paper.toBuffer(); } /** * Redeem commercial paper - * @param {TxContext} ctx the transaction context + * + * @param {Context} ctx the transaction context * @param {String} issuer commercial paper issuer * @param {Integer} paperNumber paper number for this issuer * @param {String} redeemingOwner redeeming owner of paper @@ -112,27 +130,27 @@ class CommercialPaperContract extends Contract { */ async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) { - let cpKey = CommercialPaper.makeKey([issuer, paperNumber]); + let paperKey = CommercialPaper.makeKey([issuer, paperNumber]); - let cp = await ctx.cpList.getState(cpKey); + let paper = await ctx.paperList.getPaper(paperKey); - // Check paper is TRADING, not REDEEMED - if (cp.IsRedeemed()) { + // Check paper is not REDEEMED + if (paper.isRedeemed()) { throw new Error('Paper ' + issuer + paperNumber + ' already redeemed'); } // Verify that the redeemer owns the commercial paper before redeeming it - if (cp.getOwner() === redeemingOwner) { - cp.setOwner(cp.getIssuer()); - cp.setRedeemed(); + if (paper.getOwner() === redeemingOwner) { + paper.setOwner(paper.getIssuer()); + paper.setRedeemed(); } else { throw new Error('Redeeming owner does not own paper' + issuer + paperNumber); } - await ctx.cpList.updateState(cp); - return cp.serialize(); + await ctx.paperList.updatePaper(paper); + return paper.toBuffer(); } } -module.exports = CommericalPaperContract; +module.exports = CommercialPaperContract; diff --git a/commercial-paper/organization/digibank/contract/lib/paperlist.js b/commercial-paper/organization/digibank/contract/lib/paperlist.js new file mode 100644 index 0000000000..ac5d718370 --- /dev/null +++ b/commercial-paper/organization/digibank/contract/lib/paperlist.js @@ -0,0 +1,33 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +// Utility class for collections of ledger states -- a state list +const StateList = require('./../ledger-api/statelist.js'); + +const CommercialPaper = require('./paper.js'); + +class PaperList extends StateList { + + constructor(ctx) { + super(ctx, 'org.papernet.commercialpaperlist'); + this.use(CommercialPaper); + } + + async addPaper(paper) { + return this.addState(paper); + } + + async getPaper(paperKey) { + return this.getState(paperKey); + } + + async updatePaper(paper) { + return this.updateState(paper); + } +} + + +module.exports = PaperList; \ No newline at end of file diff --git a/commercial-paper/contract/package.json b/commercial-paper/organization/digibank/contract/package.json similarity index 62% rename from commercial-paper/contract/package.json rename to commercial-paper/organization/digibank/contract/package.json index 5741509f1c..6d79d09695 100644 --- a/commercial-paper/contract/package.json +++ b/commercial-paper/organization/digibank/contract/package.json @@ -1,7 +1,8 @@ { - "name": "smart-contract", + "name": "papernet-js", "version": "0.0.1", - "description": "Smart Contract", + "description": "Papernet Contract", + "main": "index.js", "engines": { "node": ">=8", "npm": ">=5" @@ -9,22 +10,25 @@ "scripts": { "lint": "eslint .", "pretest": "npm run lint", - "test": "nyc mocha --recursive", - "start": "startChaincode" + "test": "nyc mocha test --recursive", + "start": "fabric-chaincode-node start", + "mocha": "mocha test --recursive" }, "engineStrict": true, - "author": "Anthony ODowd", + "author": "hyperledger", "license": "Apache-2.0", "dependencies": { - "fabric-shim": "unstable", - "fabric-contract-api": "unstable" + "fabric-contract-api": "^1.4.0-snapshot.17", + "fabric-shim": "^1.4.0-snapshot.27" }, "devDependencies": { "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", "eslint": "^4.19.1", "mocha": "^5.2.0", "nyc": "^12.0.2", - "sinon": "^6.0.0" + "sinon": "^6.0.0", + "sinon-chai": "^3.2.0" }, "nyc": { "exclude": [ diff --git a/commercial-paper/contract/test/contract.js b/commercial-paper/organization/digibank/contract/test/contract.js similarity index 100% rename from commercial-paper/contract/test/contract.js rename to commercial-paper/organization/digibank/contract/test/contract.js diff --git a/commercial-paper/organization/digibank/gateway/networkConnection.yaml b/commercial-paper/organization/digibank/gateway/networkConnection.yaml new file mode 100644 index 0000000000..db50f3188b --- /dev/null +++ b/commercial-paper/organization/digibank/gateway/networkConnection.yaml @@ -0,0 +1,129 @@ +--- +# +# The network connection profile provides client applications the information about the target +# blockchain network that are necessary for the applications to interact with it. These are all +# knowledge that must be acquired from out-of-band sources. This file provides such a source. +# +name: "basic-network" + +# +# Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming +# in HTTP headers or swagger properties work. The SDK will simply ignore these fields and leave +# them for the applications to process. This is a mechanism for different components of an application +# to exchange information that are not part of the standard schema described below. In particular, +# the "x-type" property with the "hlfv1" value example below is used by Hyperledger Composer to +# determine the type of Fabric networks (v0.6 vs. v1.0) it needs to work with. +# +x-type: "hlfv1" + +# +# Describe what the target network is/does. +# +description: "The basic network" + +# +# Schema version of the content. Used by the SDK to apply the corresponding parsing rules. +# +version: "1.0" + +# +# [Optional]. But most apps would have this section so that channel objects can be constructed +# based on the content below. If an app is creating channels, then it likely will not need this +# section. +# +channels: + # name of the channel + mychannel: + # Required. list of orderers designated by the application to use for transactions on this + # channel. This list can be a result of access control ("org1" can only access "ordererA"), or + # operational decisions to share loads from applications among the orderers. The values must + # be "names" of orgs defined under "organizations/peers" + orderers: + - orderer.example.com + + # Required. list of peers from participating orgs + peers: + peer0.org1.example.com: + # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must + # have the chaincode installed. The app can also use this property to decide which peers + # to send the chaincode install request. Default: true + endorsingPeer: true + + # [Optional]. will this peer be sent query proposals? The peer must have the chaincode + # installed. The app can also use this property to decide which peers to send the + # chaincode install request. Default: true + chaincodeQuery: true + + # [Optional]. will this peer be sent query proposals that do not require chaincodes, like + # queryBlock(), queryTransaction(), etc. Default: true + ledgerQuery: true + + # [Optional]. will this peer be the target of the SDK's listener registration? All peers can + # produce events but the app typically only needs to connect to one to listen to events. + # Default: true + eventSource: true + +# +# list of participating organizations in this network +# +organizations: + Org1: + mspid: Org1MSP + + peers: + - peer0.org1.example.com + + # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based + # network. Typically certificates provisioning is done in a separate process outside of the + # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for + # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for + # Fabric-CA servers. + certificateAuthorities: + - ca-org1 + +# +# List of orderers to send transaction and channel create/update requests to. For the time +# being only one orderer is needed. If more than one is defined, which one get used by the +# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. +# +orderers: + orderer.example.com: + url: grpc://localhost:7050 + + # these are standard properties defined by the gRPC library + # they will be passed in as-is to gRPC client constructor + grpcOptions: + ssl-target-name-override: orderer.example.com + +# +# List of peers to send various requests to, including endorsement, query +# and event listener registration. +# +peers: + peer0.org1.example.com: + # this URL is used to send endorsement and query requests + url: grpc://localhost:7051 + + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + request-timeout: 120001 + +# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows +# certificate management to be done via REST APIs. Application may choose to use a standard +# Certificate Authority instead of Fabric-CA, in which case this section would not be specified. +# +certificateAuthorities: + ca-org1: + url: http://localhost:7054 + # the properties specified under this object are passed to the 'http' client verbatim when + # making the request to the Fabric-CA server + httpOptions: + verify: false + + # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is + # needed to enroll and invoke new users. + registrar: + - enrollId: admin + enrollSecret: adminpw + # [Optional] The optional name of the CA. + caName: ca-org1 diff --git a/commercial-paper/application/gateway/connectionProfile.yaml b/commercial-paper/organization/digibank/gateway/papernetConnection.yaml similarity index 86% rename from commercial-paper/application/gateway/connectionProfile.yaml rename to commercial-paper/organization/digibank/gateway/papernetConnection.yaml index 9c6115ccb1..7fc4028316 100644 --- a/commercial-paper/application/gateway/connectionProfile.yaml +++ b/commercial-paper/organization/digibank/gateway/papernetConnection.yaml @@ -4,7 +4,7 @@ # blockchain network that are necessary for the applications to interact with it. These are all # knowledge that must be acquired from out-of-band sources. This file provides such a source. # -name: "global-trade-network" +name: "finance-networks" # # Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming @@ -19,7 +19,7 @@ x-type: "hlfv1" # # Describe what the target network is/does. # -description: "The network to be in if you want to stay in the global trade business" +description: "A gateway connection file for the PaperNet networks" # # Schema version of the content. Used by the SDK to apply the corresponding parsing rules. @@ -59,13 +59,13 @@ version: "1.0" # channels: # name of the channel - PaperNet: + papernet: # Required. list of orderers designated by the application to use for transactions on this # channel. This list can be a result of access control ("org1" can only access "ordererA"), or # operational decisions to share loads from applications among the orderers. The values must # be "names" of orgs defined under "organizations/peers" orderers: - - orderer.example.com + - orderer.magnetocorp.com # Required. list of peers from participating orgs peers: @@ -119,7 +119,7 @@ organizations: # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for # Fabric-CA servers. certificateAuthorities: - - ca-org1 + - ca-magnetocorp # [Optional]. If the application is going to make requests that are reserved to organization # administrators, including creating/updating channels, installing/instantiating chaincodes, it @@ -129,9 +129,9 @@ organizations: # this way. The SDK should allow applications to set the org admin identity via APIs, and only use # this route as an alternative when it exists. adminPrivateKey: - path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/keystore/9022d671ceedbb24af3ea69b5a8136cc64203df6b9920e26f48123fcfcb1d2e9_sk + path: commercial-paper/organization/magnetocorp/users/Admin@magnetocorp/keystore/9022d671ceedbb24af3ea69b5a8136cc64203df6b9920e26f48123fcfcb1d2e9_sk signedCert: - path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/signcerts/Admin@org1.example.com-cert.pem + path: comercial-paper/organization/magnetocorp/users/Admin@magnetocorp.com/signcerts/Admin@magnetocorp.com-cert.pem # the profile will contain public information about organizations other than the one it belongs to. # These are necessary information to make transaction lifecycles work, including MSP IDs and @@ -143,11 +143,11 @@ organizations: peers: - peer1.digibank.com certificateAuthorities: - - ca-org2 + - ca-digibank adminPrivateKey: - path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/keystore/5a983ddcbefe52a7f9b8ee5b85a590c3e3a43c4ccd70c7795bec504e7f74848d_sk + path: commercial-paper/organization/digibank/users/Admin@digibank.com/keystore/5a983ddcbefe52a7f9b8ee5b85a590c3e3a43c4ccd70c7795bec504e7f74848d_sk signedCert: - path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/signcerts/Admin@org2.example.com-cert.pem + path: commercial-paper/organization/digibank/users/Admin@digibank.com/signcerts/Admin@digibank.com-cert.pem # # List of orderers to send transaction and channel create/update requests to. For the time @@ -155,7 +155,7 @@ organizations: # SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. # orderers: - orderer.example.com: + orderer.magnetocorp.com: url: grpcs://localhost:7050 # these are standard properties defined by the gRPC library @@ -164,7 +164,7 @@ orderers: ssl-target-name-override: orderer.example.com tlsCACerts: - path: test/fixtures/channel/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tlscacerts/example.com-cert.pem + path: comercial-paper/organization/magnetocorp/orderer/orderer.magnetocorp.com/tlscacerts/example.com-cert.pem # # List of peers to send various requests to, including endorsement, query @@ -202,7 +202,7 @@ certificateAuthorities: httpOptions: verify: false tlsCACerts: - path: test/fixtures/channel/crypto-config/peerOrganizations/org1.example.com/ca/org1.example.com-cert.pem + path: commercial-paper/organization/magnetocorp/ca/magnetocorp.com-cert.pem # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is # needed to enroll and invoke new users. @@ -210,16 +210,16 @@ certificateAuthorities: - enrollId: admin enrollSecret: adminpw # [Optional] The optional name of the CA. - caName: ca-org1 + caName: ca-magnetocorp ca-org2: url: https://localhost:8054 httpOptions: verify: false tlsCACerts: - path: test/fixtures/channel/crypto-config/peerOrganizations/org2.example.com/ca/org2.example.com-cert.pem + path: commercial-paper/organization/digibank/ca/digibank.com-cert.pem registrar: - enrollId: admin enrollSecret: adminpw # [Optional] The optional name of the CA. - caName: ca-org2 \ No newline at end of file + caName: ca-digibank \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/application/.eslintrc.js b/commercial-paper/organization/magnetocorp/application/.eslintrc.js new file mode 100644 index 0000000000..22fbefc85c --- /dev/null +++ b/commercial-paper/organization/magnetocorp/application/.eslintrc.js @@ -0,0 +1,37 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +module.exports = { + env: { + node: true, + mocha: true + }, + parserOptions: { + ecmaVersion: 8, + sourceType: 'script' + }, + extends: "eslint:recommended", + rules: { + indent: ['error', 4], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'no-unused-vars': ['error', { args: 'none' }], + 'no-console': 'off', + curly: 'error', + eqeqeq: 'error', + 'no-throw-literal': 'error', + strict: 'error', + 'no-var': 'error', + 'dot-notation': 'error', + 'no-tabs': 'error', + 'no-trailing-spaces': 'error', + 'no-use-before-define': 'error', + 'no-useless-call': 'error', + 'no-with': 'error', + 'operator-linebreak': 'error', + yoda: 'error', + 'quote-props': ['error', 'as-needed'] + } +}; \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/application/addToWallet.js b/commercial-paper/organization/magnetocorp/application/addToWallet.js new file mode 100644 index 0000000000..f040353312 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/application/addToWallet.js @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +// Bring key classes into scope, most importantly Fabric SDK network class +const fs = require('fs'); +const { FileSystemWallet, X509WalletMixin } = require('fabric-network'); +const path = require('path'); + +const fixtures = path.resolve(__dirname, '../../../../basic-network'); + +// A wallet stores a collection of identities +const wallet = new FileSystemWallet('../identity/user/isabella/wallet'); + +async function main() { + + // Main try/catch block + try { + + // Identity to credentials to be stored in the wallet + const credPath = path.join(fixtures, '/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com'); + const cert = fs.readFileSync(path.join(credPath, '/msp/signcerts/User1@org1.example.com-cert.pem')).toString(); + const key = fs.readFileSync(path.join(credPath, '/msp/keystore/c75bd6911aca808941c3557ee7c97e90f3952e379497dc55eb903f31b50abc83_sk')).toString(); + + // Load credentials into wallet + const identityLabel = 'User1@org1.example.com'; + const identity = X509WalletMixin.createIdentity('Org1MSP', cert, key); + + await wallet.import(identityLabel, identity); + + } catch (error) { + console.log(`Error adding to wallet. ${error}`); + console.log(error.stack); + } +} + +main().then(() => { + console.log('done'); +}).catch((e) => { + console.log(e); + console.log(e.stack); + process.exit(-1); +}); \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/application/issue.js b/commercial-paper/organization/magnetocorp/application/issue.js new file mode 100644 index 0000000000..3e50c7bf5a --- /dev/null +++ b/commercial-paper/organization/magnetocorp/application/issue.js @@ -0,0 +1,102 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +/* + * This application has 6 basic steps: + * 1. Select an identity from a wallet + * 2. Connect to network gateway + * 3. Access PaperNet network + * 4. Construct request to issue commercial paper + * 5. Submit transaction + * 6. Process response + */ + +'use strict'; + +// Bring key classes into scope, most importantly Fabric SDK network class +const fs = require('fs'); +const yaml = require('js-yaml'); +const { FileSystemWallet, Gateway } = require('fabric-network'); +const CommercialPaper = require('../contract/lib/paper.js'); + +// A wallet stores a collection of identities for use +//const wallet = new FileSystemWallet('../user/isabella/wallet'); +const wallet = new FileSystemWallet('../identity/user/isabella/wallet'); + +// Main program function +async function main() { + + // A gateway defines the peers used to access Fabric networks + const gateway = new Gateway(); + + // Main try/catch block + try { + + // Specify userName for network access + // const userName = 'isabella.issuer@magnetocorp.com'; + const userName = 'User1@org1.example.com'; + + // Load connection profile; will be used to locate a gateway + let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/networkConnection.yaml', 'utf8')); + + // Set connection options; identity and wallet + let connectionOptions = { + identity: userName, + wallet: wallet, + discovery: { enabled:false, asLocalhost: true } + }; + + // Connect to gateway using application specified parameters + console.log('Connect to Fabric gateway.'); + + await gateway.connect(connectionProfile, connectionOptions); + + // Access PaperNet network + console.log('Use network channel: mychannel.'); + + const network = await gateway.getNetwork('mychannel'); + + // Get addressability to commercial paper contract + console.log('Use org.papernet.commercialpaper smart contract.'); + + const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper'); + + // issue commercial paper + console.log('Submit commercial paper issue transaction.'); + + const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000'); + + // process response + console.log('Process issue transaction response.'); + + let paper = CommercialPaper.fromBuffer(issueResponse); + + console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully issued for value ${paper.faceValue}`); + console.log('Transaction complete.'); + + } catch (error) { + + console.log(`Error processing transaction. ${error}`); + console.log(error.stack); + + } finally { + + // Disconnect from the gateway + console.log('Disconnect from Fabric gateway.') + gateway.disconnect(); + + } +} +main().then(() => { + + console.log('Issue program complete.'); + +}).catch((e) => { + + console.log('Issue program exception.'); + console.log(e); + console.log(e.stack); + process.exit(-1); + +}); \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/application/package.json b/commercial-paper/organization/magnetocorp/application/package.json new file mode 100644 index 0000000000..2ed92f2666 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/application/package.json @@ -0,0 +1,20 @@ +{ + "name": "nodejs", + "version": "1.0.0", + "description": "", + "main": "issue.js", + "scripts": { + "test": "rm -rf _idwallet && node addToWallet.js && node issue.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "fabric-network": "^1.4.0-beta", + "fabric-client": "^1.4.0-beta", + "js-yaml": "^3.12.0" + }, + "devDependencies": { + "eslint": "^5.6.0" + } +} diff --git a/commercial-paper/organization/magnetocorp/configuration/cli/docker-compose.yml b/commercial-paper/organization/magnetocorp/configuration/cli/docker-compose.yml new file mode 100644 index 0000000000..8f887d3e06 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/configuration/cli/docker-compose.yml @@ -0,0 +1,38 @@ +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# +version: '2' + +networks: + basic: + external: + name: net_basic + +services: + cliMagnetoCorp: + container_name: cliMagnetoCorp + image: hyperledger/fabric-tools + tty: true + environment: + - GOPATH=/opt/gopath + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_LOGGING_LEVEL=info + - CORE_PEER_ID=cli + - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 + - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + - CORE_CHAINCODE_KEEPALIVE=10 + working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer + command: /bin/bash + volumes: + - /var/run/:/host/var/run/ + - ./../../../../organization/magnetocorp:/opt/gopath/src/github.com/ + - ./../../../../../basic-network/crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ + networks: + - basic + #depends_on: + # - orderer.example.com + # - peer0.org1.example.com + # - couchdb diff --git a/commercial-paper/organization/magnetocorp/configuration/cli/monitordocker.sh b/commercial-paper/organization/magnetocorp/configuration/cli/monitordocker.sh new file mode 100755 index 0000000000..355acf0706 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/configuration/cli/monitordocker.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# This script uses the logspout and http stream tools to let you watch the docker containers +# in action. +# +# More information at https://github.com/gliderlabs/logspout/tree/master/httpstream + +if [ -z "$1" ]; then + DOCKER_NETWORK=basicnetwork_basic +else + DOCKER_NETWORK="$1" +fi + +echo Starting monitoring on all containers on the network ${DOCKER_NETWORK} + +docker kill logspout 2> /dev/null 1>&2 || true +docker rm logspout 2> /dev/null 1>&2 || true + +docker run -d --name="logspout" \ + --volume=/var/run/docker.sock:/var/run/docker.sock \ + --publish=127.0.0.1:8000:80 \ + --network ${DOCKER_NETWORK} \ + gliderlabs/logspout +sleep 3 +curl http://127.0.0.1:8000/logs \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/contract/.editorconfig b/commercial-paper/organization/magnetocorp/contract/.editorconfig new file mode 100755 index 0000000000..75a13be205 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/.editorconfig @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/commercial-paper/organization/magnetocorp/contract/.eslintignore b/commercial-paper/organization/magnetocorp/contract/.eslintignore new file mode 100644 index 0000000000..1595847010 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/.eslintignore @@ -0,0 +1,5 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +coverage diff --git a/commercial-paper/organization/magnetocorp/contract/.eslintrc.js b/commercial-paper/organization/magnetocorp/contract/.eslintrc.js new file mode 100644 index 0000000000..6772c660df --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/.eslintrc.js @@ -0,0 +1,37 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +module.exports = { + env: { + node: true, + mocha: true + }, + parserOptions: { + ecmaVersion: 8, + sourceType: 'script' + }, + extends: "eslint:recommended", + rules: { + indent: ['error', 4], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'no-unused-vars': ['error', { args: 'none' }], + 'no-console': 'off', + curly: 'error', + eqeqeq: 'error', + 'no-throw-literal': 'error', + strict: 'error', + 'no-var': 'error', + 'dot-notation': 'error', + 'no-tabs': 'error', + 'no-trailing-spaces': 'error', + 'no-use-before-define': 'error', + 'no-useless-call': 'error', + 'no-with': 'error', + 'operator-linebreak': 'error', + yoda: 'error', + 'quote-props': ['error', 'as-needed'] + } +}; diff --git a/commercial-paper/organization/magnetocorp/contract/.npmignore b/commercial-paper/organization/magnetocorp/contract/.npmignore new file mode 100644 index 0000000000..a00ca94150 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/.npmignore @@ -0,0 +1,77 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless diff --git a/commercial-paper/organization/magnetocorp/contract/index.js b/commercial-paper/organization/magnetocorp/contract/index.js new file mode 100644 index 0000000000..b957b5e3f9 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/index.js @@ -0,0 +1,8 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +const cpcontract = require('./lib/papercontract.js'); +module.exports.contracts = [cpcontract]; \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/contract/ledger-api/state.js b/commercial-paper/organization/magnetocorp/contract/ledger-api/state.js new file mode 100644 index 0000000000..df2fbe57c6 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/ledger-api/state.js @@ -0,0 +1,98 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +/** + * State class. States have a class, unique key, and a lifecycle current state + * the current state is determined by the specific subclass + */ +class State { + + /** + * @param {String|Object} class An indentifiable class of the instance + * @param {keyParts[]} elements to pull together to make a key for the objects + */ + constructor(stateClass, keyParts) { + this.class = stateClass; + this.key = State.makeKey(keyParts); + this.currentState = null; + } + + getClass() { + return this.class; + } + + getKey() { + return this.key; + } + + getSplitKey(){ + return State.splitKey(this.key); + } + + getCurrentState(){ + return this.currentState; + } + + serialize() { + return State.serialize(this); + } + + /** + * Convert object to buffer containing JSON data serialization + * Typically used before putState()ledger API + * @param {Object} JSON object to serialize + * @return {buffer} buffer with the data to store + */ + static serialize(object) { + return Buffer.from(JSON.stringify(object)); + } + + /** + * Deserialize object into one of a set of supported JSON classes + * i.e. Covert serialized data to JSON object + * Typically used after getState() ledger API + * @param {data} data to deserialize into JSON object + * @param (supportedClasses) the set of classes data can be serialized to + * @return {json} json with the data to store + */ + static deserialize(data, supportedClasses) { + let json = JSON.parse(data.toString()); + let objClass = supportedClasses[json.class]; + if (!objClass) { + throw new Error(`Unknown class of ${json.class}`); + } + let object = new (objClass)(json); + + return object; + } + + /** + * Deserialize object into specific object class + * Typically used after getState() ledger API + * @param {data} data to deserialize into JSON object + * @return {json} json with the data to store + */ + static deserializeClass(data, objClass) { + let json = JSON.parse(data.toString()); + let object = new (objClass)(json); + return object; + } + + /** + * Join the keyParts to make a unififed string + * @param (String[]) keyParts + */ + static makeKey(keyParts) { + return keyParts.map(part => JSON.stringify(part)).join(':'); + } + + static splitKey(key){ + return key.split(':'); + } + +} + +module.exports = State; \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/contract/ledger-api/statelist.js b/commercial-paper/organization/magnetocorp/contract/ledger-api/statelist.js new file mode 100644 index 0000000000..3c39671a83 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/ledger-api/statelist.js @@ -0,0 +1,68 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; +const State = require('./state.js'); + +/** + * StateList provides a named virtual container for a set of ledger states. + * Each state has a unique key which associates it with the container, rather + * than the container containing a link to the state. This minimizes collisions + * for parallel transactions on different states. + */ +class StateList { + + /** + * Store Fabric context for subsequent API access, and name of list + */ + constructor(ctx, listName) { + this.ctx = ctx; + this.name = listName; + this.supportedClasses = {}; + + } + + /** + * Add a state to the list. Creates a new state in worldstate with + * appropriate composite key. Note that state defines its own key. + * State object is serialized before writing. + */ + async addState(state) { + let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey()); + let data = State.serialize(state); + await this.ctx.stub.putState(key, data); + } + + /** + * Get a state from the list using supplied keys. Form composite + * keys to retrieve state from world state. State data is deserialized + * into JSON object before being returned. + */ + async getState(key) { + let ledgerKey = this.ctx.stub.createCompositeKey(this.name, State.splitKey(key)); + let data = await this.ctx.stub.getState(ledgerKey); + let state = State.deserialize(data, this.supportedClasses); + return state; + } + + /** + * Update a state in the list. Puts the new state in world state with + * appropriate composite key. Note that state defines its own key. + * A state is serialized before writing. Logic is very similar to + * addState() but kept separate becuase it is semantically distinct. + */ + async updateState(state) { + let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey()); + let data = State.serialize(state); + await this.ctx.stub.putState(key, data); + } + + /** Stores the class for future deserialization */ + use(stateClass) { + this.supportedClasses[stateClass.getClass()] = stateClass; + } + +} + +module.exports = StateList; \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/contract/lib/paper.js b/commercial-paper/organization/magnetocorp/contract/lib/paper.js new file mode 100644 index 0000000000..9d76adacd3 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/lib/paper.js @@ -0,0 +1,102 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +// Utility class for ledger state +const State = require('./../ledger-api/state.js'); + +// Enumerate commercial paper state values +const cpState = { + ISSUED: 1, + TRADING: 2, + REDEEMED: 3 +}; + +/** + * CommercialPaper class extends State class + * Class will be used by application and smart contract to define a paper + */ +class CommercialPaper extends State { + + constructor(obj) { + super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]); + Object.assign(this, obj); + } + + /** + * Basic getters and setters + */ + getIssuer() { + return this.issuer; + } + + setIssuer(newIssuer) { + this.issuer = newIssuer; + } + + getOwner() { + return this.owner; + } + + setOwner(newOwner) { + this.owner = newOwner; + } + + /** + * Useful methods to encapsulate commercial paper states + */ + setIssued() { + this.currentState = cpState.ISSUED; + } + + setTrading() { + this.currentState = cpState.TRADING; + } + + setRedeemed() { + this.currentState = cpState.REDEEMED; + } + + isIssued() { + return this.currentState === cpState.ISSUED; + } + + isTrading() { + return this.currentState === cpState.TRADING; + } + + isRedeemed() { + return this.currentState === cpState.REDEEMED; + } + + static fromBuffer(buffer) { + return CommercialPaper.deserialize(Buffer.from(JSON.parse(buffer))); + } + + toBuffer() { + return Buffer.from(JSON.stringify(this)); + } + + /** + * Deserialize a state data to commercial paper + * @param {Buffer} data to form back into the object + */ + static deserialize(data) { + return State.deserializeClass(data, CommercialPaper); + } + + /** + * Factory method to create a commercial paper object + */ + static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { + return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue }); + } + + static getClass() { + return 'org.papernet.commercialpaper'; + } +} + +module.exports = CommercialPaper; diff --git a/commercial-paper/organization/magnetocorp/contract/lib/papercontract.js b/commercial-paper/organization/magnetocorp/contract/lib/papercontract.js new file mode 100644 index 0000000000..c9e54aa33c --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/lib/papercontract.js @@ -0,0 +1,156 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +// Fabric smart contract classes +const { Contract, Context } = require('fabric-contract-api'); + +// PaperNet specifc classes +const CommercialPaper = require('./paper.js'); +const PaperList = require('./paperlist.js'); + +/** + * A custom context provides easy access to list of all commercial papers + */ +class CommercialPaperContext extends Context { + + constructor() { + super(); + // All papers are held in a list of papers + this.paperList = new PaperList(this); + } + +} + +/** + * Define commercial paper smart contract by extending Fabric Contract class + * + */ +class CommercialPaperContract extends Contract { + + constructor() { + // Unique namespace when multiple contracts per chaincode file + super('org.papernet.commercialpaper'); + } + + /** + * Define a custom context for commercial paper + */ + createContext() { + return new CommercialPaperContext(); + } + + /** + * Instantiate to perform any setup of the ledger that might be required. + * @param {Context} ctx the transaction context + */ + async instantiate(ctx) { + // No implementation required with this example + // It could be where data migration is performed, if necessary + console.log('Instantiate the contract'); + } + + /** + * Issue commercial paper + * + * @param {Context} ctx the transaction context + * @param {String} issuer commercial paper issuer + * @param {Integer} paperNumber paper number for this issuer + * @param {String} issueDateTime paper issue date + * @param {String} maturityDateTime paper maturity date + * @param {Integer} faceValue face value of paper + */ + async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) { + + // create an instance of the paper + let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue); + + // Smart contract, rather than paper, moves paper into ISSUED state + paper.setIssued(); + + // Newly issued paper is owned by the issuer + paper.setOwner(issuer); + + // Add the paper to the list of all similar commercial papers in the ledger world state + await ctx.paperList.addPaper(paper); + + // Must return a serialized paper to caller of smart contract + return paper.toBuffer(); + } + + /** + * Buy commercial paper + * + * @param {Context} ctx the transaction context + * @param {String} issuer commercial paper issuer + * @param {Integer} paperNumber paper number for this issuer + * @param {String} currentOwner current owner of paper + * @param {String} newOwner new owner of paper + * @param {Integer} price price paid for this paper + * @param {String} purchaseDateTime time paper was purchased (i.e. traded) + */ + async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) { + + // Retrieve the current paper using key fields provided + let paperKey = CommercialPaper.makeKey([issuer, paperNumber]); + let paper = await ctx.paperList.getPaper(paperKey); + + // Validate current owner + if (paper.getOwner() !== currentOwner) { + throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner); + } + + // First buy moves state from ISSUED to TRADING + if (paper.isIssued()) { + paper.setTrading(); + } + + // Check paper is not already REDEEMED + if (paper.isTrading()) { + paper.setOwner(newOwner); + } else { + throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' +paper.getCurrentState()); + } + + // Update the paper + await ctx.paperList.updatePaper(paper); + return paper.toBuffer(); + } + + /** + * Redeem commercial paper + * + * @param {Context} ctx the transaction context + * @param {String} issuer commercial paper issuer + * @param {Integer} paperNumber paper number for this issuer + * @param {String} redeemingOwner redeeming owner of paper + * @param {String} redeemDateTime time paper was redeemed + */ + async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) { + + let paperKey = CommercialPaper.makeKey([issuer, paperNumber]); + + let paper = await ctx.paperList.getPaper(paperKey); + + // Check paper is not REDEEMED + if (paper.isRedeemed()) { + throw new Error('Paper ' + issuer + paperNumber + ' already redeemed'); + } + + // Verify that the redeemer owns the commercial paper before redeeming it + if (paper.getOwner() === redeemingOwner) { + paper.setOwner(paper.getIssuer()); + paper.setRedeemed(); + } else { + throw new Error('Redeeming owner does not own paper' + issuer + paperNumber); + } + + await ctx.paperList.updatePaper(paper); + return paper.toBuffer(); + } + +} + +module.exports = CommercialPaperContract; diff --git a/commercial-paper/organization/magnetocorp/contract/lib/paperlist.js b/commercial-paper/organization/magnetocorp/contract/lib/paperlist.js new file mode 100644 index 0000000000..ac5d718370 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/lib/paperlist.js @@ -0,0 +1,33 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +// Utility class for collections of ledger states -- a state list +const StateList = require('./../ledger-api/statelist.js'); + +const CommercialPaper = require('./paper.js'); + +class PaperList extends StateList { + + constructor(ctx) { + super(ctx, 'org.papernet.commercialpaperlist'); + this.use(CommercialPaper); + } + + async addPaper(paper) { + return this.addState(paper); + } + + async getPaper(paperKey) { + return this.getState(paperKey); + } + + async updatePaper(paper) { + return this.updateState(paper); + } +} + + +module.exports = PaperList; \ No newline at end of file diff --git a/commercial-paper/organization/magnetocorp/contract/package.json b/commercial-paper/organization/magnetocorp/contract/package.json new file mode 100644 index 0000000000..6d79d09695 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/package.json @@ -0,0 +1,49 @@ +{ + "name": "papernet-js", + "version": "0.0.1", + "description": "Papernet Contract", + "main": "index.js", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "scripts": { + "lint": "eslint .", + "pretest": "npm run lint", + "test": "nyc mocha test --recursive", + "start": "fabric-chaincode-node start", + "mocha": "mocha test --recursive" + }, + "engineStrict": true, + "author": "hyperledger", + "license": "Apache-2.0", + "dependencies": { + "fabric-contract-api": "^1.4.0-snapshot.17", + "fabric-shim": "^1.4.0-snapshot.27" + }, + "devDependencies": { + "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", + "eslint": "^4.19.1", + "mocha": "^5.2.0", + "nyc": "^12.0.2", + "sinon": "^6.0.0", + "sinon-chai": "^3.2.0" + }, + "nyc": { + "exclude": [ + "coverage/**", + "test/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": true, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/commercial-paper/organization/magnetocorp/contract/test/contract.js b/commercial-paper/organization/magnetocorp/contract/test/contract.js new file mode 100644 index 0000000000..e0aafd5e97 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/contract/test/contract.js @@ -0,0 +1,41 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ +'use strict'; + +const Chaincode = require('../lib/chaincode'); +const { Stub } = require('fabric-shim'); + +require('chai').should(); +const sinon = require('sinon'); + +describe('Chaincode', () => { + + describe('#Init', () => { + + it('should work', async () => { + const cc = new Chaincode(); + const stub = sinon.createStubInstance(Stub); + stub.getFunctionAndParameters.returns({ fcn: 'initFunc', params: [] }); + const res = await cc.Init(stub); + res.status.should.equal(Stub.RESPONSE_CODE.OK); + }); + + }); + + describe('#Invoke', async () => { + + it('should work', async () => { + const cc = new Chaincode(); + const stub = sinon.createStubInstance(Stub); + stub.getFunctionAndParameters.returns({ fcn: 'initFunc', params: [] }); + let res = await cc.Init(stub); + res.status.should.equal(Stub.RESPONSE_CODE.OK); + stub.getFunctionAndParameters.returns({ fcn: 'invokeFunc', params: [] }); + res = await cc.Invoke(stub); + res.status.should.equal(Stub.RESPONSE_CODE.OK); + }); + + }); + +}); diff --git a/commercial-paper/organization/magnetocorp/gateway/networkConnection.yaml b/commercial-paper/organization/magnetocorp/gateway/networkConnection.yaml new file mode 100644 index 0000000000..db50f3188b --- /dev/null +++ b/commercial-paper/organization/magnetocorp/gateway/networkConnection.yaml @@ -0,0 +1,129 @@ +--- +# +# The network connection profile provides client applications the information about the target +# blockchain network that are necessary for the applications to interact with it. These are all +# knowledge that must be acquired from out-of-band sources. This file provides such a source. +# +name: "basic-network" + +# +# Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming +# in HTTP headers or swagger properties work. The SDK will simply ignore these fields and leave +# them for the applications to process. This is a mechanism for different components of an application +# to exchange information that are not part of the standard schema described below. In particular, +# the "x-type" property with the "hlfv1" value example below is used by Hyperledger Composer to +# determine the type of Fabric networks (v0.6 vs. v1.0) it needs to work with. +# +x-type: "hlfv1" + +# +# Describe what the target network is/does. +# +description: "The basic network" + +# +# Schema version of the content. Used by the SDK to apply the corresponding parsing rules. +# +version: "1.0" + +# +# [Optional]. But most apps would have this section so that channel objects can be constructed +# based on the content below. If an app is creating channels, then it likely will not need this +# section. +# +channels: + # name of the channel + mychannel: + # Required. list of orderers designated by the application to use for transactions on this + # channel. This list can be a result of access control ("org1" can only access "ordererA"), or + # operational decisions to share loads from applications among the orderers. The values must + # be "names" of orgs defined under "organizations/peers" + orderers: + - orderer.example.com + + # Required. list of peers from participating orgs + peers: + peer0.org1.example.com: + # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must + # have the chaincode installed. The app can also use this property to decide which peers + # to send the chaincode install request. Default: true + endorsingPeer: true + + # [Optional]. will this peer be sent query proposals? The peer must have the chaincode + # installed. The app can also use this property to decide which peers to send the + # chaincode install request. Default: true + chaincodeQuery: true + + # [Optional]. will this peer be sent query proposals that do not require chaincodes, like + # queryBlock(), queryTransaction(), etc. Default: true + ledgerQuery: true + + # [Optional]. will this peer be the target of the SDK's listener registration? All peers can + # produce events but the app typically only needs to connect to one to listen to events. + # Default: true + eventSource: true + +# +# list of participating organizations in this network +# +organizations: + Org1: + mspid: Org1MSP + + peers: + - peer0.org1.example.com + + # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based + # network. Typically certificates provisioning is done in a separate process outside of the + # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for + # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for + # Fabric-CA servers. + certificateAuthorities: + - ca-org1 + +# +# List of orderers to send transaction and channel create/update requests to. For the time +# being only one orderer is needed. If more than one is defined, which one get used by the +# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. +# +orderers: + orderer.example.com: + url: grpc://localhost:7050 + + # these are standard properties defined by the gRPC library + # they will be passed in as-is to gRPC client constructor + grpcOptions: + ssl-target-name-override: orderer.example.com + +# +# List of peers to send various requests to, including endorsement, query +# and event listener registration. +# +peers: + peer0.org1.example.com: + # this URL is used to send endorsement and query requests + url: grpc://localhost:7051 + + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + request-timeout: 120001 + +# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows +# certificate management to be done via REST APIs. Application may choose to use a standard +# Certificate Authority instead of Fabric-CA, in which case this section would not be specified. +# +certificateAuthorities: + ca-org1: + url: http://localhost:7054 + # the properties specified under this object are passed to the 'http' client verbatim when + # making the request to the Fabric-CA server + httpOptions: + verify: false + + # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is + # needed to enroll and invoke new users. + registrar: + - enrollId: admin + enrollSecret: adminpw + # [Optional] The optional name of the CA. + caName: ca-org1 diff --git a/commercial-paper/organization/magnetocorp/gateway/papernetConnection.yaml b/commercial-paper/organization/magnetocorp/gateway/papernetConnection.yaml new file mode 100644 index 0000000000..7fc4028316 --- /dev/null +++ b/commercial-paper/organization/magnetocorp/gateway/papernetConnection.yaml @@ -0,0 +1,225 @@ +--- +# +# The network connection profile provides client applications the information about the target +# blockchain network that are necessary for the applications to interact with it. These are all +# knowledge that must be acquired from out-of-band sources. This file provides such a source. +# +name: "finance-networks" + +# +# Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming +# in HTTP headers or swagger properties work. The SDK will simply ignore these fields and leave +# them for the applications to process. This is a mechanism for different components of an application +# to exchange information that are not part of the standard schema described below. In particular, +# the "x-type" property with the "hlfv1" value example below is used by Hyperledger Composer to +# determine the type of Fabric networks (v0.6 vs. v1.0) it needs to work with. +# +x-type: "hlfv1" + +# +# Describe what the target network is/does. +# +description: "A gateway connection file for the PaperNet networks" + +# +# Schema version of the content. Used by the SDK to apply the corresponding parsing rules. +# +version: "1.0" + +# +# The client section is SDK-specific. The sample below is for the node.js SDK +# +#client: + # Which organization does this application instance belong to? The value must be the name of an org + # defined under "organizations" + #organization: Org1 + + # Some SDKs support pluggable KV stores, the properties under "credentialStore" + # are implementation specific + #credentialStore: + # [Optional]. Specific to FileKeyValueStore.js or similar implementations in other SDKs. Can be others + # if using an alternative impl. For instance, CouchDBKeyValueStore.js would require an object + # here for properties like url, db name, etc. + #path: "/tmp/hfc-kvs" + + # [Optional]. Specific to the CryptoSuite implementation. Software-based implementations like + # CryptoSuite_ECDSA_AES.js in node SDK requires a key store. PKCS#11 based implementations does + # not. + #cryptoStore: + # Specific to the underlying KeyValueStore that backs the crypto key store. + #path: "/tmp/hfc-cvs" + + # [Optional]. Specific to Composer environment + #wallet: wallet-name + +# +# [Optional]. But most apps would have this section so that channel objects can be constructed +# based on the content below. If an app is creating channels, then it likely will not need this +# section. +# +channels: + # name of the channel + papernet: + # Required. list of orderers designated by the application to use for transactions on this + # channel. This list can be a result of access control ("org1" can only access "ordererA"), or + # operational decisions to share loads from applications among the orderers. The values must + # be "names" of orgs defined under "organizations/peers" + orderers: + - orderer.magnetocorp.com + + # Required. list of peers from participating orgs + peers: + peer1.magnetocorp.com: + # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must + # have the chaincode installed. The app can also use this property to decide which peers + # to send the chaincode install request. Default: true + endorsingPeer: true + + # [Optional]. will this peer be sent query proposals? The peer must have the chaincode + # installed. The app can also use this property to decide which peers to send the + # chaincode install request. Default: true + chaincodeQuery: true + + # [Optional]. will this peer be sent query proposals that do not require chaincodes, like + # queryBlock(), queryTransaction(), etc. Default: true + ledgerQuery: true + + # [Optional]. will this peer be the target of the SDK's listener registration? All peers can + # produce events but the app typically only needs to connect to one to listen to events. + # Default: true + eventSource: true + + peer2.digibank.com: + endorsingPeer: true + chaincodeQuery: false + ledgerQuery: true + eventSource: true + + # [Optional]. what chaincodes are expected to exist on this channel? The application can use + # this information to validate that the target peers are in the expected state by comparing + # this list with the query results of getInstalledChaincodes() and getInstantiatedChaincodes() + chaincodes: + # the format follows the "cannonical name" of chaincodes by fabric code + - example02:v1 + - marbles:1.0 + +# +# list of participating organizations in this network +# +organizations: + Org1: + mspid: magnetocorpMSP + + peers: + - peer1.magnetocorp.com + + # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based + # network. Typically certificates provisioning is done in a separate process outside of the + # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for + # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for + # Fabric-CA servers. + certificateAuthorities: + - ca-magnetocorp + + # [Optional]. If the application is going to make requests that are reserved to organization + # administrators, including creating/updating channels, installing/instantiating chaincodes, it + # must have access to the admin identity represented by the private key and signing certificate. + # Both properties can be the PEM string or local path to the PEM file. Note that this is mainly for + # convenience in development mode, production systems should not expose sensitive information + # this way. The SDK should allow applications to set the org admin identity via APIs, and only use + # this route as an alternative when it exists. + adminPrivateKey: + path: commercial-paper/organization/magnetocorp/users/Admin@magnetocorp/keystore/9022d671ceedbb24af3ea69b5a8136cc64203df6b9920e26f48123fcfcb1d2e9_sk + signedCert: + path: comercial-paper/organization/magnetocorp/users/Admin@magnetocorp.com/signcerts/Admin@magnetocorp.com-cert.pem + + # the profile will contain public information about organizations other than the one it belongs to. + # These are necessary information to make transaction lifecycles work, including MSP IDs and + # peers with a public URL to send transaction proposals. The file will not contain private + # information reserved for members of the organization, such as admin key and certificate, + # fabric-ca registrar enroll ID and secret, etc. + Org2: + mspid: digibankMSP + peers: + - peer1.digibank.com + certificateAuthorities: + - ca-digibank + adminPrivateKey: + path: commercial-paper/organization/digibank/users/Admin@digibank.com/keystore/5a983ddcbefe52a7f9b8ee5b85a590c3e3a43c4ccd70c7795bec504e7f74848d_sk + signedCert: + path: commercial-paper/organization/digibank/users/Admin@digibank.com/signcerts/Admin@digibank.com-cert.pem + +# +# List of orderers to send transaction and channel create/update requests to. For the time +# being only one orderer is needed. If more than one is defined, which one get used by the +# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers. +# +orderers: + orderer.magnetocorp.com: + url: grpcs://localhost:7050 + + # these are standard properties defined by the gRPC library + # they will be passed in as-is to gRPC client constructor + grpcOptions: + ssl-target-name-override: orderer.example.com + + tlsCACerts: + path: comercial-paper/organization/magnetocorp/orderer/orderer.magnetocorp.com/tlscacerts/example.com-cert.pem + +# +# List of peers to send various requests to, including endorsement, query +# and event listener registration. +# +peers: + peer1.magnetocorp.com: + # this URL is used to send endorsement and query requests + url: grpcs://localhost:7051 + + grpcOptions: + ssl-target-name-override: peer1.magnetocorp.com + request-timeout: 120 + + tlsCACerts: + path: certificates/magnetocorp/magnetocorp.com-cert.pem + + peer1.digibank.com: + url: grpcs://localhost:8051 + grpcOptions: + ssl-target-name-override: peer1.digibank.com + tlsCACerts: + path: certificates/digibank/digibank.com-cert.pem + +# +# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows +# certificate management to be done via REST APIs. Application may choose to use a standard +# Certificate Authority instead of Fabric-CA, in which case this section would not be specified. +# +certificateAuthorities: + ca-org1: + url: https://localhost:7054 + # the properties specified under this object are passed to the 'http' client verbatim when + # making the request to the Fabric-CA server + httpOptions: + verify: false + tlsCACerts: + path: commercial-paper/organization/magnetocorp/ca/magnetocorp.com-cert.pem + + # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is + # needed to enroll and invoke new users. + registrar: + - enrollId: admin + enrollSecret: adminpw + # [Optional] The optional name of the CA. + caName: ca-magnetocorp + + ca-org2: + url: https://localhost:8054 + httpOptions: + verify: false + tlsCACerts: + path: commercial-paper/organization/digibank/ca/digibank.com-cert.pem + registrar: + - enrollId: admin + enrollSecret: adminpw + # [Optional] The optional name of the CA. + caName: ca-digibank \ No newline at end of file