Skip to content

Commit

Permalink
FAB-12322 Update commercial-paper sample
Browse files Browse the repository at this point in the history
Change-Id: I235cb62df492b7713bb1c355b7457f679903bd34
Signed-off-by: Anthony O'Dowd <a_o-dowd@uk.ibm.com>
ODOWDAIBM committed Nov 14, 2018
1 parent df311ce commit e67fcf1
Showing 47 changed files with 2,086 additions and 522 deletions.
8 changes: 8 additions & 0 deletions commercial-paper/.gitignore
Original file line number Diff line number Diff line change
@@ -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
76 changes: 0 additions & 76 deletions commercial-paper/application/application.js

This file was deleted.

7 changes: 0 additions & 7 deletions commercial-paper/contract/index.js

This file was deleted.

113 changes: 0 additions & 113 deletions commercial-paper/contract/lib/cpcontract.js

This file was deleted.

148 changes: 0 additions & 148 deletions commercial-paper/contract/lib/cpstate.js

This file was deleted.

34 changes: 0 additions & 34 deletions commercial-paper/contract/lib/utils.js

This file was deleted.

37 changes: 37 additions & 0 deletions commercial-paper/organization/digibank/application/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -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']
}
};
45 changes: 45 additions & 0 deletions commercial-paper/organization/digibank/application/addToWallet.js
Original file line number Diff line number Diff line change
@@ -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);
});
102 changes: 102 additions & 0 deletions commercial-paper/organization/digibank/application/buy.js
Original file line number Diff line number Diff line change
@@ -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);

});
20 changes: 20 additions & 0 deletions commercial-paper/organization/digibank/application/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
101 changes: 101 additions & 0 deletions commercial-paper/organization/digibank/application/redeem.js
Original file line number Diff line number Diff line change
@@ -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);

});
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions commercial-paper/organization/digibank/contract/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
SPDX-License-Identifier: Apache-2.0
*/

'use strict';

const cpcontract = require('./lib/papercontract.js');
module.exports.contracts = [cpcontract];
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -4,52 +4,25 @@ 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
};

/**
* 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;
Original file line number Diff line number Diff line change
@@ -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,59 +93,64 @@ 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
* @param {String} redeemDateTime time paper was redeemed
*/
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;
33 changes: 33 additions & 0 deletions commercial-paper/organization/digibank/contract/lib/paperlist.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
{
"name": "smart-contract",
"name": "papernet-js",
"version": "0.0.1",
"description": "Smart Contract",
"description": "Papernet Contract",
"main": "index.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"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": [
File renamed without changes.
129 changes: 129 additions & 0 deletions commercial-paper/organization/digibank/gateway/networkConnection.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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,19 +143,19 @@ 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
# 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:
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,24 +202,24 @@ 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.
registrar:
- 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
caName: ca-digibank
37 changes: 37 additions & 0 deletions commercial-paper/organization/magnetocorp/application/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -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']
}
};
Original file line number Diff line number Diff line change
@@ -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);
});
102 changes: 102 additions & 0 deletions commercial-paper/organization/magnetocorp/application/issue.js
Original file line number Diff line number Diff line change
@@ -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);

});
20 changes: 20 additions & 0 deletions commercial-paper/organization/magnetocorp/application/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/.editorconfig
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# SPDX-License-Identifier: Apache-2.0
#

coverage
37 changes: 37 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -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']
}
};
77 changes: 77 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/.npmignore
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
SPDX-License-Identifier: Apache-2.0
*/

'use strict';

const cpcontract = require('./lib/papercontract.js');
module.exports.contracts = [cpcontract];
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
102 changes: 102 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/lib/paper.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
49 changes: 49 additions & 0 deletions commercial-paper/organization/magnetocorp/contract/package.json
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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);
});

});

});
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e67fcf1

Please sign in to comment.