Merge "[FAB-6550] Sample app written in typescript"

This commit is contained in:
David Enyeart 2017-12-20 14:01:02 +00:00 committed by Gerrit Code Review
commit 55d0140381
23 changed files with 2748 additions and 0 deletions

View file

@ -0,0 +1,4 @@
node_modules
package-lock.json
dist
types/fabric-client

View file

@ -0,0 +1,303 @@
## Balance transfer
This is a sample Node.js application written using typescript which demonstrates
the **__fabric-client__** and **__fabric-ca-client__** Node.js SDK APIs for typescript.
### Prerequisites and setup:
* [Docker](https://www.docker.com/products/overview) - v1.12 or higher
* [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher
* [Git client](https://git-scm.com/downloads) - needed for clone commands
* **Node.js** v6.9.0 - 6.10.0 ( __Node v7+ is not supported__ )
* [Download Docker images](http://hyperledger-fabric.readthedocs.io/en/latest/samples.html#binaries)
```
cd fabric-samples/balance-transfer/
```
Once you have completed the above setup, you will have provisioned a local network with the following docker container configuration:
* 2 CAs
* A SOLO orderer
* 4 peers (2 peers per Org)
#### Artifacts
* Crypto material has been generated using the **cryptogen** tool from Hyperledger Fabric and mounted to all peers, the orderering node and CA containers. More details regarding the cryptogen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#crypto-generator).
* An Orderer genesis block (genesis.block) and channel configuration transaction (mychannel.tx) has been pre generated using the **configtxgen** tool from Hyperledger Fabric and placed within the artifacts folder. More details regarding the configtxgen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#configuration-transaction-generator).
## Running the sample program
There are two options available for running the balance-transfer sample as shown below.
### Option 1
##### Terminal Window 1
```
cd fabric-samples/balance-transfer/typescript
./runApp.sh
```
This performs the following steps:
* lauches the required network on your local machine
* installs the fabric-client and fabric-ca-client node modules
* starts the node app on PORT 4000
##### Terminal Window 2
NOTE: In order for the following shell script to properly parse the JSON, you must install ``jq``.
See instructions at [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/).
Test the APIs as follows:
```
cd fabric-samples/balance-transfer/typescript
./testAPIs.sh
```
### Option 2 is a more manual approach
##### Terminal Window 1
* Launch the network using docker-compose
```
docker-compose -f artifacts/docker-compose.yaml up
```
##### Terminal Window 2
* Install the fabric-client and fabric-ca-client node modules
```
npm install
```
*** NOTE - If running this before the new version of the node SDK is published which includes the typescript definition files, you will need to do the following:
```
cp types/fabric-client/index.d.tx node_modules/fabric-client/index.d.ts
cp types/fabric-ca-client/index.d.tx node_modules/fabric-ca-client/index.d.ts
```
* Start the node app on PORT 4000
```
PORT=4000 ts-node app.ts
```
##### Terminal Window 3
* Execute the REST APIs from the section [Sample REST APIs Requests](https://github.com/hyperledger/fabric-samples/tree/master/balance-transfer#sample-rest-apis-requests)
## Sample REST APIs Requests
### Login Request
* Register and enroll new users in Organization - **Org1**:
`curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=org1'`
**OUTPUT:**
```
{
"success": true,
"secret": "RaxhMgevgJcm",
"message": "Jim enrolled Successfully",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI"
}
```
The response contains the success/failure status, an **enrollment Secret** and a **JSON Web Token (JWT)** that is a required string in the Request Headers for subsequent requests.
### Create Channel request
```
curl -s -X POST \
http://localhost:4000/channels \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json" \
-d '{
"channelName":"mychannel",
"channelConfigPath":"../artifacts/channel/mychannel.tx"
}'
```
Please note that the Header **authorization** must contain the JWT returned from the `POST /users` call
### Join Channel request
```
curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"]
}'
```
### Install chaincode
```
curl -s -X POST \
http://localhost:4000/chaincodes \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"],
"chaincodeName":"mycc",
"chaincodePath":"github.com/example_cc",
"chaincodeVersion":"v0"
}'
```
### Instantiate chaincode
```
curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json" \
-d '{
"chaincodeName":"mycc",
"chaincodeVersion":"v0",
"args":["a","100","b","200"]
}'
```
### Invoke request
```
curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes/mycc \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json" \
-d '{
"fcn":"move",
"args":["a","b","10"]
}'
```
**NOTE:** Ensure that you save the Transaction ID from the response in order to pass this string in the subsequent query transactions.
### Chaincode Query
```
curl -s -X GET \
"http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Query Block by BlockNumber
```
curl -s -X GET \
"http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Query Transaction by TransactionID
```
curl -s -X GET http://localhost:4000/channels/mychannel/transactions/TRX_ID?peer=peer1 \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
**NOTE**: Here the TRX_ID can be from any previous invoke transaction
### Query ChainInfo
```
curl -s -X GET \
"http://localhost:4000/channels/mychannel?peer=peer1" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Query Installed chaincodes
```
curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer1&type=installed" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Query Instantiated chaincodes
```
curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Query Channels
```
curl -s -X GET \
"http://localhost:4000/channels?peer=peer1" \
-H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
-H "content-type: application/json"
```
### Network configuration considerations
You have the ability to change configuration parameters by either directly editing the network-config.json file or provide an additional file for an alternative target network. The app uses an optional environment variable "TARGET_NETWORK" to control the configuration files to use. For example, if you deployed the target network on Amazon Web Services EC2, you can add a file "network-config-aws.json", and set the "TARGET_NETWORK" environment to 'aws'. The app will pick up the settings inside the "network-config-aws.json" file.
#### IP Address** and PORT information
If you choose to customize your docker-compose yaml file by hardcoding IP Addresses and PORT information for your peers and orderer, then you MUST also add the identical values into the network-config.json file. The paths shown below will need to be adjusted to match your docker-compose yaml file.
```
"orderer": {
"url": "grpcs://x.x.x.x:7050",
"server-hostname": "orderer0",
"tls_cacerts": "../artifacts/tls/orderer/ca-cert.pem"
},
"org1": {
"ca": "http://x.x.x.x:7054",
"peer1": {
"requests": "grpcs://x.x.x.x:7051",
"events": "grpcs://x.x.x.x:7053",
...
},
"peer2": {
"requests": "grpcs://x.x.x.x:7056",
"events": "grpcs://x.x.x.x:7058",
...
}
},
"org2": {
"ca": "http://x.x.x.x:8054",
"peer1": {
"requests": "grpcs://x.x.x.x:8051",
"events": "grpcs://x.x.x.x:8053",
... },
"peer2": {
"requests": "grpcs://x.x.x.x:8056",
"events": "grpcs://x.x.x.x:8058",
...
}
}
```
#### Discover IP Address
To retrieve the IP Address for one of your network entities, issue the following command:
```
# The following will return the IP Address for peer0
docker inspect peer0 | grep IPAddress
```
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.

View file

@ -0,0 +1,89 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as express from 'express';
import log4js = require('log4js');
const logger = log4js.getLogger('SampleWebApp');
import hfc = require('fabric-client');
import * as jwt from 'jsonwebtoken';
import * as helper from '../lib/helper';
import * as channelApi from '../lib/channel';
import * as chainCodeApi from '../lib/chaincode';
import { RequestEx } from '../interfaces';
import { getErrorMessage } from './utils';
export default function chainCodeHandlers(app: express.Application) {
async function installChainCode(req: RequestEx, res: express.Response) {
logger.debug('==================== INSTALL CHAINCODE ==================');
const peers = req.body.peers;
const chaincodeName = req.body.chaincodeName;
const chaincodePath = req.body.chaincodePath;
const chaincodeVersion = req.body.chaincodeVersion;
logger.debug('peers : ' + peers); // target peers list
logger.debug('chaincodeName : ' + chaincodeName);
logger.debug('chaincodePath : ' + chaincodePath);
logger.debug('chaincodeVersion : ' + chaincodeVersion);
if (!peers || peers.length === 0) {
res.json(getErrorMessage('\'peers\''));
return;
}
if (!chaincodeName) {
res.json(getErrorMessage('\'chaincodeName\''));
return;
}
if (!chaincodePath) {
res.json(getErrorMessage('\'chaincodePath\''));
return;
}
if (!chaincodeVersion) {
res.json(getErrorMessage('\'chaincodeVersion\''));
return;
}
const message = await chainCodeApi.installChaincode(
peers, chaincodeName, chaincodePath, chaincodeVersion, req.username, req.orgname);
res.send(message);
}
async function queryChainCode(req: RequestEx, res: express.Response) {
const peer = req.query.peer;
const installType = req.query.type;
// TODO: add Constnats
if (installType === 'installed') {
logger.debug(
'================ GET INSTALLED CHAINCODES ======================');
} else {
logger.debug(
'================ GET INSTANTIATED CHAINCODES ======================');
}
const message = await chainCodeApi.getInstalledChaincodes(
peer, installType, req.username, req.orgname);
res.send(message);
}
const API_ENDPOINT_CHAINCODE_INSTALL = '/chaincodes';
const API_ENDPOINT_CHAINCODE_QUERY = '/chaincodes';
app.post(API_ENDPOINT_CHAINCODE_INSTALL, installChainCode);
app.get(API_ENDPOINT_CHAINCODE_QUERY, queryChainCode);
}

View file

@ -0,0 +1,261 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as express from 'express';
import log4js = require('log4js');
const logger = log4js.getLogger('SampleWebApp');
import hfc = require('fabric-client');
import * as jwt from 'jsonwebtoken';
import * as helper from '../lib/helper';
import * as channelApi from '../lib/channel';
import { RequestEx } from '../interfaces';
import { getErrorMessage } from './utils';
export default function channelHandlers(app: express.Application) {
async function createNewChannel(req: RequestEx, res: express.Response) {
logger.info('<<<<<<<<<<<<<<<<< C R E A T E C H A N N E L >>>>>>>>>>>>>>>>>');
logger.debug('End point : /channels');
const channelName = req.body.channelName;
const channelConfigPath = req.body.channelConfigPath;
logger.debug('Channel name : ' + channelName);
// ../artifacts/channel/mychannel.tx
logger.debug('channelConfigPath : ' + channelConfigPath);
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!channelConfigPath) {
res.json(getErrorMessage('\'channelConfigPath\''));
return;
}
const response = await channelApi.createChannel(
channelName, channelConfigPath, req.username, req.orgname);
res.send(response);
}
async function joinChannel(req: RequestEx, res: express.Response) {
logger.info('<<<<<<<<<<<<<<<<< J O I N C H A N N E L >>>>>>>>>>>>>>>>>');
const channelName = req.params.channelName;
const peers = req.body.peers;
logger.debug('channelName : ' + channelName);
logger.debug('peers : ' + peers);
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!peers || peers.length === 0) {
res.json(getErrorMessage('\'peers\''));
return;
}
const message = await channelApi.joinChannel(channelName, peers, req.username, req.orgname);
res.send(message);
}
async function instantiateChainCode(req: RequestEx, res: express.Response) {
logger.debug('==================== INSTANTIATE CHAINCODE ==================');
const chaincodeName = req.body.chaincodeName;
const chaincodeVersion = req.body.chaincodeVersion;
const channelName = req.params.channelName;
const fcn = req.body.fcn;
const args = req.body.args;
logger.debug('channelName : ' + channelName);
logger.debug('chaincodeName : ' + chaincodeName);
logger.debug('chaincodeVersion : ' + chaincodeVersion);
logger.debug('fcn : ' + fcn);
logger.debug('args : ' + args);
if (!chaincodeName) {
res.json(getErrorMessage('\'chaincodeName\''));
return;
}
if (!chaincodeVersion) {
res.json(getErrorMessage('\'chaincodeVersion\''));
return;
}
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!args) {
res.json(getErrorMessage('\'args\''));
return;
}
const message = await channelApi.instantiateChainCode(
channelName, chaincodeName, chaincodeVersion, fcn, args, req.username, req.orgname);
res.send(message);
}
async function invokeChainCode(req: RequestEx, res: express.Response) {
logger.debug('==================== INVOKE ON CHAINCODE ==================');
const peers = req.body.peers;
const chaincodeName = req.params.chaincodeName;
const channelName = req.params.channelName;
const fcn = req.body.fcn;
const args = req.body.args;
logger.debug('channelName : ' + channelName);
logger.debug('chaincodeName : ' + chaincodeName);
logger.debug('fcn : ' + fcn);
logger.debug('args : ' + args);
if (!chaincodeName) {
res.json(getErrorMessage('\'chaincodeName\''));
return;
}
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!fcn) {
res.json(getErrorMessage('\'fcn\''));
return;
}
if (!args) {
res.json(getErrorMessage('\'args\''));
return;
}
const message = await channelApi.invokeChaincode(
peers, channelName, chaincodeName, fcn, args, req.username, req.orgname);
res.send(message);
}
async function queryChainCode(req: RequestEx, res: express.Response) {
const channelName = req.params.channelName;
const chaincodeName = req.params.chaincodeName;
let args = req.query.args;
const fcn = req.query.fcn;
const peer = req.query.peer;
logger.debug('channelName : ' + channelName);
logger.debug('chaincodeName : ' + chaincodeName);
logger.debug('fcn : ' + fcn);
logger.debug('args : ' + args);
if (!chaincodeName) {
res.json(getErrorMessage('\'chaincodeName\''));
return;
}
if (!channelName) {
res.json(getErrorMessage('\'channelName\''));
return;
}
if (!fcn) {
res.json(getErrorMessage('\'fcn\''));
return;
}
if (!args) {
res.json(getErrorMessage('\'args\''));
return;
}
args = args.replace(/'/g, '"');
args = JSON.parse(args);
logger.debug(args);
const message = await channelApi.queryChaincode(
peer, channelName, chaincodeName, args, fcn, req.username, req.orgname);
res.send(message);
}
async function queryByBlockNumber(req: RequestEx, res: express.Response) {
logger.debug('==================== GET BLOCK BY NUMBER ==================');
const blockId = req.params.blockId;
const peer = req.query.peer;
logger.debug('channelName : ' + req.params.channelName);
logger.debug('BlockID : ' + blockId);
logger.debug('Peer : ' + peer);
if (!blockId) {
res.json(getErrorMessage('\'blockId\''));
return;
}
const message = await channelApi.getBlockByNumber(peer, blockId, req.username, req.orgname);
res.send(message);
}
async function queryByTransactionId(req: RequestEx, res: express.Response) {
logger.debug(
'================ GET TRANSACTION BY TRANSACTION_ID ======================'
);
logger.debug('channelName : ' + req.params.channelName);
const trxnId = req.params.trxnId;
const peer = req.query.peer;
if (!trxnId) {
res.json(getErrorMessage('\'trxnId\''));
return;
}
const message = await channelApi.getTransactionByID(
peer, trxnId, req.username, req.orgname);
res.send(message);
}
async function queryChannelInfo(req: RequestEx, res: express.Response) {
logger.debug(
'================ GET CHANNEL INFORMATION ======================');
logger.debug('channelName : ' + req.params.channelName);
const peer = req.query.peer;
const message = await channelApi.getChainInfo(peer, req.username, req.orgname);
res.send(message);
}
async function queryChannels(req: RequestEx, res: express.Response) {
logger.debug('================ GET CHANNELS ======================');
logger.debug('peer: ' + req.query.peer);
const peer = req.query.peer;
if (!peer) {
res.json(getErrorMessage('\'peer\''));
return;
}
const message = await channelApi.getChannels(peer, req.username, req.orgname);
res.send(message);
}
const API_ENDPOINT_CHANNEL_CREATE = '/channels';
const API_ENDPOINT_CHANNEL_JOIN = '/channels/:channelName/peers';
const API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE = '/channels/:channelName/chaincodes';
const API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE =
'/channels/:channelName/chaincodes/:chaincodeName';
const API_ENDPOINT_CHANNEL_QUERY_CHAINCODE = '/channels/:channelName/chaincodes/:chaincodeName';
const API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER = '/channels/:channelName/blocks/:blockId';
const API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID
= '/channels/:channelName/transactions/:trxnId';
const API_ENDPOINT_CHANNEL_INFO = '/channels/:channelName';
const API_ENDPOINT_CHANNEL_QUERY = '/channels';
app.post(API_ENDPOINT_CHANNEL_CREATE, createNewChannel);
app.post(API_ENDPOINT_CHANNEL_JOIN, joinChannel);
app.post(API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE, instantiateChainCode);
app.post(API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE, invokeChainCode);
app.get(API_ENDPOINT_CHANNEL_QUERY_CHAINCODE, queryChainCode);
app.get(API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER, queryByBlockNumber);
app.get(API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID, queryByTransactionId);
app.get(API_ENDPOINT_CHANNEL_INFO, queryChannelInfo);
app.get(API_ENDPOINT_CHANNEL_QUERY, queryChannels);
}

View file

@ -0,0 +1,27 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as express from 'express';
import userHandlers from './users';
import channelHandlers from './channel';
import chainCodeHandlers from './chaincode';
export default function entryPoint(app: express.Application) {
// various handlers
userHandlers(app);
channelHandlers(app);
chainCodeHandlers(app);
}

View file

@ -0,0 +1,69 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RequestEx } from '../interfaces';
import * as express from 'express';
import log4js = require('log4js');
const logger = log4js.getLogger('SampleWebApp');
import hfc = require('fabric-client');
import * as jwt from 'jsonwebtoken';
import * as helper from '../lib/helper';
import { getErrorMessage } from './utils';
export default function userHandlers(app: express.Application) {
async function registerUser(req: RequestEx, res: express.Response) {
const username = req.body.username;
const orgName = req.body.orgName;
logger.debug('End point : /users');
logger.debug('User name : ' + username);
logger.debug('Org name : ' + orgName);
if (!username) {
res.json(getErrorMessage('\'username\''));
return;
}
if (!orgName) {
res.json(getErrorMessage('\'orgName\''));
return;
}
const token = jwt.sign({
exp: Math.floor(Date.now() / 1000) + parseInt(
hfc.getConfigSetting('jwt_expiretime'), 10),
username,
orgName
}, app.get('secret'));
const response = await helper.getRegisteredUsers(username, orgName);
if (response && typeof response !== 'string') {
res.json({
success: true,
token
});
} else {
res.json({
success: false,
message: response
});
}
}
const API_ENDPOINT_REGISTER_USER = '/users';
app.post(API_ENDPOINT_REGISTER_USER, registerUser);
}

View file

@ -0,0 +1,23 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function getErrorMessage(field: string) {
const response = {
success: false,
message: field + ' field is missing or Invalid in the request'
};
return response;
}

View file

@ -0,0 +1,92 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import log4js = require('log4js');
import * as util from 'util';
import * as http from 'http';
import * as express from 'express';
import * as jwt from 'jsonwebtoken';
import * as bodyParser from 'body-parser';
import expressJWT = require('express-jwt');
// tslint:disable-next-line:no-var-requires
const bearerToken = require('express-bearer-token');
import cors = require('cors');
import hfc = require('fabric-client');
import * as helper from './lib/helper';
import { RequestEx } from './interfaces';
import api from './api';
helper.init();
const SERVER_HOST = process.env.HOST || hfc.getConfigSetting('host');
const SERVER_PORT = process.env.PORT || hfc.getConfigSetting('port');
const logger = log4js.getLogger('SampleWebApp');
// create express App
const app = express();
app.options('*', cors());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.set('secret', 'thisismysecret');
app.use(expressJWT({
secret: 'thisismysecret'
}).unless({
path: ['/users']
}));
app.use(bearerToken());
app.use((req: RequestEx, res, next) => {
if (req.originalUrl.indexOf('/users') >= 0) {
return next();
}
const token = req.token;
jwt.verify(token, app.get('secret'), (err: Error, decoded: any) => {
if (err) {
res.send({
success: false,
message: 'Failed to authenticate token. Make sure to include the ' +
'token returned from /users call in the authorization header ' +
' as a Bearer token'
});
return;
} else {
// add the decoded user name and org name to the request object
// for the downstream code to use
req.username = decoded.username;
req.orgname = decoded.orgName;
logger.debug(
util.format('Decoded from JWT token: username - %s, orgname - %s',
decoded.username, decoded.orgName));
return next();
}
});
});
// configure various routes
api(app);
const server = http.createServer(app);
server.listen(SERVER_PORT);
logger.info('****************** SERVER STARTED ************************');
logger.info('************** http://' + SERVER_HOST + ':' + SERVER_PORT + ' ******************');
server.timeout = 240000;

View file

@ -0,0 +1,13 @@
{
"host": "localhost",
"port": "4000",
"jwt_expiretime": "36000",
"channelName": "mychannel",
"CC_SRC_PATH": "../artifacts",
"keyValueStore": "/tmp/fabric-client-kvs",
"eventWaitTime": "30000",
"admins": [{
"username": "admin",
"secret": "adminpw"
}]
}

View file

@ -0,0 +1 @@
../artifacts

View file

@ -0,0 +1,30 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as util from 'util';
let file = 'network-config%s.json';
const env = process.env.TARGET_NETWORK;
if (env) {
file = util.format(file, '-' + env);
} else {
file = util.format(file, '');
}
export default {
networkConfigFile: file
};

View file

@ -0,0 +1,23 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as express from 'express';
export interface RequestEx extends express.Request {
username?: any;
orgname?: any;
token?: any;
}

View file

@ -0,0 +1,148 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as util from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as helper from './helper';
// tslint:disable-next-line:no-var-requires
const config = require('../app_config.json');
const logger = helper.getLogger('ChaincodeApi');
function buildTarget(peer: string, org: string): Peer {
let target: Peer = null;
if (typeof peer !== 'undefined') {
const targets: Peer[] = helper.newPeers([peer], org);
if (targets && targets.length > 0) {
target = targets[0];
}
}
return target;
}
export async function installChaincode(
peers: string[], chaincodeName: string, chaincodePath: string,
chaincodeVersion: string, username: string, org: string) {
logger.debug(
'\n============ Install chaincode on organizations ============\n');
helper.setupChaincodeDeploy();
const channel = helper.getChannelForOrg(org);
const client = helper.getClientForOrg(org);
const admin = await helper.getOrgAdmin(org);
const request = {
targets: helper.newPeers(peers, org),
chaincodePath,
chaincodeId: chaincodeName,
chaincodeVersion
};
try {
const results = await client.installChaincode(request);
const proposalResponses = results[0];
const proposal = results[1];
let allGood = true;
proposalResponses.forEach((pr) => {
let oneGood = false;
if (pr.response && pr.response.status === 200) {
oneGood = true;
logger.info('install proposal was good');
} else {
logger.error('install proposal was bad');
}
allGood = allGood && oneGood;
});
if (allGood) {
logger.info(util.format(
'Successfully sent install Proposal and received ProposalResponse: Status - %s',
proposalResponses[0].response.status));
logger.debug('\nSuccessfully Installed chaincode on organization ' + org +
'\n');
return 'Successfully Installed chaincode on organization ' + org;
} else {
logger.error(
// tslint:disable-next-line:max-line-length
'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...'
);
// tslint:disable-next-line:max-line-length
return 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...';
}
} catch (err) {
logger.error('Failed to send install proposal due to error: ' + err.stack ?
err.stack : err);
throw new Error('Failed to send install proposal due to error: ' + err.stack ?
err.stack : err);
}
}
export async function getInstalledChaincodes(
peer: string, type: string, username: string, org: string) {
const target = buildTarget(peer, org);
const channel = helper.getChannelForOrg(org);
const client = helper.getClientForOrg(org);
const user = await helper.getOrgAdmin(org);
try {
let response: ChaincodeQueryResponse = null;
if (type === 'installed') {
response = await client.queryInstalledChaincodes(target);
} else {
response = await channel.queryInstantiatedChaincodes(target);
}
if (response) {
if (type === 'installed') {
logger.debug('<<< Installed Chaincodes >>>');
} else {
logger.debug('<<< Instantiated Chaincodes >>>');
}
const details: string[] = [];
response.chaincodes.forEach((c) => {
logger.debug('name: ' + c.name + ', version: ' +
c.version + ', path: ' + c.path
);
details.push('name: ' + c.name + ', version: ' +
c.version + ', path: ' + c.path
);
});
return details;
} else {
logger.error('response is null');
return 'response is null';
}
} catch (err) {
logger.error('Failed to query with error:' + err.stack ? err.stack : err);
return 'Failed to query with error:' + err.stack ? err.stack : err;
}
}

View file

@ -0,0 +1,599 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as util from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as helper from './helper';
const logger = helper.getLogger('ChannelApi');
// tslint:disable-next-line:no-var-requires
const config = require('../app_config.json');
const allEventhubs: EventHub[] = [];
function buildTarget(peer: string, org: string): Peer {
let target: Peer = null;
if (typeof peer !== 'undefined') {
const targets: Peer[] = helper.newPeers([peer], org);
if (targets && targets.length > 0) {
target = targets[0];
}
}
return target;
}
// Attempt to send a request to the orderer with the sendCreateChain method
export async function createChannel(
channelName: string, channelConfigPath: string, username: string, orgName: string) {
logger.debug('\n====== Creating Channel \'' + channelName + '\' ======\n');
const client = helper.getClientForOrg(orgName);
const channel = helper.getChannelForOrg(orgName);
// read in the envelope for the channel config raw bytes
const envelope = fs.readFileSync(path.join(__dirname, channelConfigPath));
// extract the channel config bytes from the envelope to be signed
const channelConfig = client.extractChannelConfig(envelope);
// Acting as a client in the given organization provided with "orgName" param
const admin = await helper.getOrgAdmin(orgName);
logger.debug(util.format('Successfully acquired admin user for the organization "%s"',
orgName));
// sign the channel config bytes as "endorsement", this is required by
// the orderer's channel creation policy
const signature = client.signChannelConfig(channelConfig);
const request = {
config: channelConfig,
signatures: [signature],
name: channelName,
orderer: channel.getOrderers()[0],
txId: client.newTransactionID()
};
try {
const response = await client.createChannel(request);
if (response && response.status === 'SUCCESS') {
logger.debug('Successfully created the channel.');
return {
success: true,
message: 'Channel \'' + channelName + '\' created Successfully'
};
} else {
logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName +
'\' !!!!!!!!!\n\n');
throw new Error('Failed to create the channel \'' + channelName + '\'');
}
} catch (err) {
logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName +
'\' !!!!!!!!!\n\n');
throw new Error('Failed to create the channel \'' + channelName + '\'');
}
}
export async function joinChannel(
channelName: string, peers: string[], username: string, org: string) {
// on process exit, always disconnect the event hub
const closeConnections = (isSuccess: boolean) => {
if (isSuccess) {
logger.debug('\n============ Join Channel is SUCCESS ============\n');
} else {
logger.debug('\n!!!!!!!! ERROR: Join Channel FAILED !!!!!!!!\n');
}
logger.debug('');
allEventhubs.forEach((hub) => {
console.log(hub);
if (hub && hub.isconnected()) {
hub.disconnect();
}
});
};
// logger.debug('\n============ Join Channel ============\n')
logger.info(util.format(
'Calling peers in organization "%s" to join the channel', org));
const client = helper.getClientForOrg(org);
const channel = helper.getChannelForOrg(org);
const admin = await helper.getOrgAdmin(org);
logger.info(util.format('received member object for admin of the organization "%s": ', org));
const request = {
txId: client.newTransactionID()
};
const genesisBlock = await channel.getGenesisBlock(request);
const request2 = {
targets: helper.newPeers(peers, org),
txId: client.newTransactionID(),
block: genesisBlock
};
const eventhubs = helper.newEventHubs(peers, org);
eventhubs.forEach((eh) => {
eh.connect();
allEventhubs.push(eh);
});
const eventPromises: Array<Promise<any>> = [];
eventhubs.forEach((eh) => {
const txPromise = new Promise((resolve, reject) => {
const handle = setTimeout(reject, parseInt(config.eventWaitTime, 10));
eh.registerBlockEvent((block: any) => {
clearTimeout(handle);
// in real-world situations, a peer may have more than one channels so
// we must check that this block came from the channel we asked the peer to join
if (block.data.data.length === 1) {
// Config block must only contain one transaction
const channel_header = block.data.data[0].payload.header.channel_header;
if (channel_header.channel_id === channelName) {
resolve();
} else {
reject();
}
}
});
});
eventPromises.push(txPromise);
});
const sendPromise = channel.joinChannel(request2);
const results = await Promise.all([sendPromise].concat(eventPromises));
logger.debug(util.format('Join Channel R E S P O N S E : %j', results));
if (results[0] && results[0][0] && results[0][0].response && results[0][0]
.response.status === 200) {
logger.info(util.format(
'Successfully joined peers in organization %s to the channel \'%s\'',
org, channelName));
closeConnections(true);
const response = {
success: true,
message: util.format(
'Successfully joined peers in organization %s to the channel \'%s\'',
org, channelName)
};
return response;
} else {
logger.error(' Failed to join channel');
closeConnections(false);
throw new Error('Failed to join channel');
}
}
export async function instantiateChainCode(
channelName: string, chaincodeName: string, chaincodeVersion: string,
functionName: string, args: string[], username: string, org: string) {
logger.debug('\n============ Instantiate chaincode on organization ' + org +
' ============\n');
const channel = helper.getChannelForOrg(org);
const client = helper.getClientForOrg(org);
const admin = await helper.getOrgAdmin(org);
await channel.initialize();
const txId = client.newTransactionID();
// send proposal to endorser
const request = {
chaincodeId: chaincodeName,
chaincodeVersion,
args,
txId,
fcn: functionName
};
try {
const results = await channel.sendInstantiateProposal(request);
const proposalResponses = results[0];
const proposal = results[1];
let allGood = true;
proposalResponses.forEach((pr) => {
let oneGood = false;
if (pr.response && pr.response.status === 200) {
oneGood = true;
logger.info('install proposal was good');
} else {
logger.error('install proposal was bad');
}
allGood = allGood && oneGood;
});
if (allGood) {
logger.info(util.format(
// tslint:disable-next-line:max-line-length
'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
proposalResponses[0].response.status, proposalResponses[0].response.message,
proposalResponses[0].response.payload, proposalResponses[0].endorsement
.signature));
const request2 = {
proposalResponses,
proposal
};
// set the transaction listener and set a timeout of 30sec
// if the transaction did not get committed within the timeout period,
// fail the test
const deployId = txId.getTransactionID();
const ORGS = helper.getOrgs();
const eh = client.newEventHub();
const data = fs.readFileSync(path.join(__dirname, ORGS[org].peers['peer1'][
'tls_cacerts'
]));
eh.setPeerAddr(ORGS[org].peers['peer1']['events'], {
'pem': Buffer.from(data).toString(),
'ssl-target-name-override': ORGS[org].peers['peer1']['server-hostname']
});
eh.connect();
const txPromise: Promise<any> = new Promise((resolve, reject) => {
const handle = setTimeout(() => {
eh.disconnect();
reject();
}, 30000);
eh.registerTxEvent(deployId, (tx, code) => {
// logger.info(
// 'The chaincode instantiate transaction has been committed on peer ' +
// eh._ep._endpoint.addr);
clearTimeout(handle);
eh.unregisterTxEvent(deployId);
eh.disconnect();
if (code !== 'VALID') {
logger.error(
'The chaincode instantiate transaction was invalid, code = ' + code);
reject();
} else {
logger.info('The chaincode instantiate transaction was valid.');
resolve();
}
});
});
const sendPromise = channel.sendTransaction(request2);
const transactionResults = await Promise.all([sendPromise].concat([txPromise]));
const response = transactionResults[0];
if (response.status === 'SUCCESS') {
logger.info('Successfully sent transaction to the orderer.');
return 'Chaincode Instantiation is SUCCESS';
} else {
logger.error('Failed to order the transaction. Error code: ' + response.status);
return 'Failed to order the transaction. Error code: ' + response.status;
}
} else {
logger.error(
// tslint:disable-next-line:max-line-length
'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...'
);
// tslint:disable-next-line:max-line-length
return 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...';
}
} catch (err) {
logger.error('Failed to send instantiate due to error: ' + err.stack ? err
.stack : err);
return 'Failed to send instantiate due to error: ' + err.stack ? err.stack :
err;
}
}
export async function invokeChaincode(
peerNames: string[], channelName: string,
chaincodeName: string, fcn: string, args: string[], username: string, org: string) {
logger.debug(
util.format('\n============ invoke transaction on organization %s ============\n', org));
const client = helper.getClientForOrg(org);
const channel = helper.getChannelForOrg(org);
const targets = (peerNames) ? helper.newPeers(peerNames, org) : undefined;
const user = await helper.getRegisteredUsers(username, org);
const txId = client.newTransactionID();
logger.debug(util.format('Sending transaction "%j"', txId));
// send proposal to endorser
const request: ChaincodeInvokeRequest = {
chaincodeId: chaincodeName,
fcn,
args,
txId
};
if (targets) {
request.targets = targets;
}
try {
const results = await channel.sendTransactionProposal(request);
const proposalResponses = results[0];
const proposal = results[1];
let allGood = true;
proposalResponses.forEach((pr) => {
let oneGood = false;
if (pr.response && pr.response.status === 200) {
oneGood = true;
logger.info('transaction proposal was good');
} else {
logger.error('transaction proposal was bad');
}
allGood = allGood && oneGood;
});
if (allGood) {
logger.debug(util.format(
// tslint:disable-next-line:max-line-length
'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
proposalResponses[0].response.status, proposalResponses[0].response.message,
proposalResponses[0].response.payload, proposalResponses[0].endorsement
.signature));
const request2 = {
proposalResponses,
proposal
};
// set the transaction listener and set a timeout of 30sec
// if the transaction did not get committed within the timeout period,
// fail the test
const transactionID = txId.getTransactionID();
const eventPromises: Array<Promise<any>> = [];
if (!peerNames) {
peerNames = channel.getPeers().map((peer) => {
return peer.getName();
});
}
const eventhubs = helper.newEventHubs(peerNames, org);
eventhubs.forEach((eh: EventHub) => {
eh.connect();
const txPromise = new Promise((resolve, reject) => {
const handle = setTimeout(() => {
eh.disconnect();
reject();
}, 30000);
eh.registerTxEvent(transactionID, (tx: string, code: string) => {
clearTimeout(handle);
eh.unregisterTxEvent(transactionID);
eh.disconnect();
if (code !== 'VALID') {
logger.error(
'The balance transfer transaction was invalid, code = ' + code);
reject();
} else {
// logger.info(
// 'The balance transfer transaction has been committed on peer ' +
// eh._ep._endpoint.addr);
resolve();
}
});
});
eventPromises.push(txPromise);
});
const sendPromise = channel.sendTransaction(request2);
const results2 = await Promise.all([sendPromise].concat(eventPromises));
logger.debug(' event promise all complete and testing complete');
if (results2[0].status === 'SUCCESS') {
logger.info('Successfully sent transaction to the orderer.');
return txId.getTransactionID();
} else {
logger.error('Failed to order the transaction. Error code: ' + results2[0].status);
return 'Failed to order the transaction. Error code: ' + results2[0].status;
}
} else {
logger.error(
// tslint:disable-next-line:max-line-length
'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'
);
// tslint:disable-next-line:max-line-length
return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...';
}
} catch (err) {
logger.error('Failed to send transaction due to error: ' + err.stack ? err
.stack : err);
return 'Failed to send transaction due to error: ' + err.stack ? err.stack :
err;
}
}
export async function queryChaincode(
peer: string, channelName: string, chaincodeName: string,
args: string[], fcn: string, username: string, org: string) {
const channel = helper.getChannelForOrg(org);
const client = helper.getClientForOrg(org);
const target = buildTarget(peer, org);
const user = await helper.getRegisteredUsers(username, org);
const txId = client.newTransactionID();
// send query
const request: ChaincodeQueryRequest = {
chaincodeId: chaincodeName,
txId,
fcn,
args
};
if (target) {
request.targets = [target];
}
try {
const responsePayloads = await channel.queryByChaincode(request);
if (responsePayloads) {
responsePayloads.forEach((rp) => {
logger.info(args[0] + ' now has ' + rp.toString('utf8') +
' after the move');
return args[0] + ' now has ' + rp.toString('utf8') +
' after the move';
});
} else {
logger.error('response_payloads is null');
return 'response_payloads is null';
}
} catch (err) {
logger.error('Failed to send query due to error: ' + err.stack ? err.stack :
err);
return 'Failed to send query due to error: ' + err.stack ? err.stack : err;
}
}
export async function getBlockByNumber(
peer: string, blockNumber: string, username: string, org: string) {
const target = buildTarget(peer, org);
const channel = helper.getChannelForOrg(org);
const user = await helper.getRegisteredUsers(username, org);
try {
const responsePayloads = await channel.queryBlock(parseInt(blockNumber, 10), target);
if (responsePayloads) {
logger.debug(responsePayloads);
return responsePayloads; // response_payloads.data.data[0].buffer;
} else {
logger.error('response_payloads is null');
return 'response_payloads is null';
}
} catch (err) {
logger.error('Failed to query with error:' + err.stack ? err.stack : err);
return 'Failed to query with error:' + err.stack ? err.stack : err;
}
}
export async function getTransactionByID(
peer: string, trxnID: string, username: string, org: string) {
const target = buildTarget(peer, org);
const channel = helper.getChannelForOrg(org);
const user = await helper.getRegisteredUsers(username, org);
try {
const responsePayloads = await channel.queryTransaction(trxnID, target);
if (responsePayloads) {
logger.debug(responsePayloads);
return responsePayloads;
} else {
logger.error('response_payloads is null');
return 'response_payloads is null';
}
} catch (err) {
logger.error('Failed to query with error:' + err.stack ? err.stack : err);
return 'Failed to query with error:' + err.stack ? err.stack : err;
}
}
export async function getChainInfo(peer: string, username: string, org: string) {
const target = buildTarget(peer, org);
const channel = helper.getChannelForOrg(org);
const user = await helper.getRegisteredUsers(username, org);
try {
const blockChainInfo = await channel.queryInfo(target);
if (blockChainInfo) {
// FIXME: Save this for testing 'getBlockByHash' ?
logger.debug('===========================================');
logger.debug(blockChainInfo.currentBlockHash);
logger.debug('===========================================');
// logger.debug(blockchainInfo);
return blockChainInfo;
} else {
logger.error('blockChainInfo is null');
return 'blockChainInfo is null';
}
} catch (err) {
logger.error('Failed to query with error:' + err.stack ? err.stack : err);
return 'Failed to query with error:' + err.stack ? err.stack : err;
}
}
export async function getChannels(peer: string, username: string, org: string) {
const target = buildTarget(peer, org);
const channel = helper.getChannelForOrg(org);
const client = helper.getClientForOrg(org);
const user = await helper.getRegisteredUsers(username, org);
try {
const response = await client.queryChannels(target);
if (response) {
logger.debug('<<< channels >>>');
const channelNames: string[] = [];
response.channels.forEach((ci) => {
channelNames.push('channel id: ' + ci.channel_id);
});
return response;
} else {
logger.error('response_payloads is null');
return 'response_payloads is null';
}
} catch (err) {
logger.error('Failed to query with error:' + err.stack ? err.stack : err);
return 'Failed to query with error:' + err.stack ? err.stack : err;
}
}

View file

@ -0,0 +1,311 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import log4js = require('log4js');
import * as path from 'path';
import * as fs from 'fs';
import * as util from 'util';
import config from '../config';
import hfc = require('fabric-client');
// tslint:disable-next-line:no-var-requires
const copService = require('fabric-ca-client');
const logger = log4js.getLogger('Helper');
logger.setLevel('DEBUG');
hfc.setLogger(logger);
let ORGS: any;
const clients = {};
const channels = {};
const caClients = {};
function readAllFiles(dir: string) {
const files = fs.readdirSync(dir);
const certs: any = [];
files.forEach((fileName) => {
const filePath = path.join(dir, fileName);
const data = fs.readFileSync(filePath);
certs.push(data);
});
return certs;
}
function getKeyStoreForOrg(org: string) {
return hfc.getConfigSetting('keyValueStore') + '_' + org;
}
function setupPeers(channel: any, org: string, client: Client) {
for (const key in ORGS[org].peers) {
if (key) {
const data = fs.readFileSync(
path.join(__dirname, ORGS[org].peers[key]['tls_cacerts']));
const peer = client.newPeer(
ORGS[org].peers[key].requests,
{
'pem': Buffer.from(data).toString(),
'ssl-target-name-override': ORGS[org].peers[key]['server-hostname']
}
);
peer.setName(key);
channel.addPeer(peer);
}
}
}
function newOrderer(client: Client) {
const caRootsPath = ORGS.orderer.tls_cacerts;
const data = fs.readFileSync(path.join(__dirname, caRootsPath));
const caroots = Buffer.from(data).toString();
return client.newOrderer(ORGS.orderer.url, {
'pem': caroots,
'ssl-target-name-override': ORGS.orderer['server-hostname']
});
}
function getOrgName(org: string) {
return ORGS[org].name;
}
function getMspID(org: string) {
logger.debug('Msp ID : ' + ORGS[org].mspid);
return ORGS[org].mspid;
}
function newRemotes(names: string[], forPeers: boolean, userOrg: string) {
const client = getClientForOrg(userOrg);
const targets: any[] = [];
// find the peer that match the names
names.forEach((n) => {
if (ORGS[userOrg].peers[n]) {
// found a peer matching the name
const data = fs.readFileSync(
path.join(__dirname, ORGS[userOrg].peers[n]['tls_cacerts']));
const grpcOpts = {
'pem': Buffer.from(data).toString(),
'ssl-target-name-override': ORGS[userOrg].peers[n]['server-hostname']
};
if (forPeers) {
targets.push(client.newPeer(ORGS[userOrg].peers[n].requests, grpcOpts));
} else {
const eh = client.newEventHub();
eh.setPeerAddr(ORGS[userOrg].peers[n].events, grpcOpts);
targets.push(eh);
}
}
});
if (targets.length === 0) {
logger.error(util.format('Failed to find peers matching the names %s', names));
}
return targets;
}
async function getAdminUser(userOrg: string): Promise<User> {
const users = hfc.getConfigSetting('admins');
const username = users[0].username;
const password = users[0].secret;
const client = getClientForOrg(userOrg);
const store = await hfc.newDefaultKeyValueStore({
path: getKeyStoreForOrg(getOrgName(userOrg))
});
client.setStateStore(store);
const user = await client.getUserContext(username, true);
if (user && user.isEnrolled()) {
logger.info('Successfully loaded member from persistence');
return user;
}
const caClient = caClients[userOrg];
const enrollment = await caClient.enroll({
enrollmentID: username,
enrollmentSecret: password
});
logger.info('Successfully enrolled user \'' + username + '\'');
const userOptions: UserOptions = {
username,
mspid: getMspID(userOrg),
cryptoContent: {
privateKeyPEM: enrollment.key.toBytes(),
signedCertPEM: enrollment.certificate
}
};
const member = await client.createUser(userOptions);
return member;
}
export function newPeers(names: string[], org: string) {
return newRemotes(names, true, org);
}
export function newEventHubs(names: string[], org: string) {
return newRemotes(names, false, org);
}
export function setupChaincodeDeploy() {
process.env.GOPATH = path.join(__dirname, hfc.getConfigSetting('CC_SRC_PATH'));
}
export function getOrgs() {
return ORGS;
}
export function getClientForOrg(org: string): Client {
return clients[org];
}
export function getChannelForOrg(org: string): Channel {
return channels[org];
}
export function init() {
hfc.addConfigFile(path.join(__dirname, config.networkConfigFile));
hfc.addConfigFile(path.join(__dirname, '../app_config.json'));
ORGS = hfc.getConfigSetting('network-config');
// set up the client and channel objects for each org
for (const key in ORGS) {
if (key.indexOf('org') === 0) {
const client = new hfc();
const cryptoSuite = hfc.newCryptoSuite();
// TODO: Fix it up as setCryptoKeyStore is only available for s/w impl
(cryptoSuite as any).setCryptoKeyStore(
hfc.newCryptoKeyStore({
path: getKeyStoreForOrg(ORGS[key].name)
}));
client.setCryptoSuite(cryptoSuite);
const channel = client.newChannel(hfc.getConfigSetting('channelName'));
channel.addOrderer(newOrderer(client));
clients[key] = client;
channels[key] = channel;
setupPeers(channel, key, client);
const caUrl = ORGS[key].ca;
caClients[key] = new copService(
caUrl, null /*defautl TLS opts*/, '' /* default CA */, cryptoSuite);
}
}
}
export async function getRegisteredUsers(
username: string, userOrg: string): Promise<User> {
const client = getClientForOrg(userOrg);
const store = await hfc.newDefaultKeyValueStore({
path: getKeyStoreForOrg(getOrgName(userOrg))
});
client.setStateStore(store);
const user = await client.getUserContext(username, true);
if (user && user.isEnrolled()) {
logger.info('Successfully loaded member from persistence');
return user;
}
logger.info('Using admin to enroll this user ..');
// get the Admin and use it to enroll the user
const adminUser = await getAdminUser(userOrg);
const caClient = caClients[userOrg];
const secret = await caClient.register({
enrollmentID: username,
affiliation: userOrg + '.department1'
}, adminUser);
logger.debug(username + ' registered successfully');
const message = await caClient.enroll({
enrollmentID: username,
enrollmentSecret: secret
});
if (message && typeof message === 'string' && message.includes(
'Error:')) {
logger.error(username + ' enrollment failed');
}
logger.debug(username + ' enrolled successfully');
const userOptions: UserOptions = {
username,
mspid: getMspID(userOrg),
cryptoContent: {
privateKeyPEM: message.key.toBytes(),
signedCertPEM: message.certificate
}
};
const member = await client.createUser(userOptions);
return member;
}
export function getLogger(moduleName: string) {
const moduleLogger = log4js.getLogger(moduleName);
moduleLogger.setLevel('DEBUG');
return moduleLogger;
}
export async function getOrgAdmin(userOrg: string): Promise<User> {
const admin = ORGS[userOrg].admin;
const keyPath = path.join(__dirname, admin.key);
const keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString();
const certPath = path.join(__dirname, admin.cert);
const certPEM = readAllFiles(certPath)[0].toString();
const client = getClientForOrg(userOrg);
const cryptoSuite = hfc.newCryptoSuite();
if (userOrg) {
(cryptoSuite as any).setCryptoKeyStore(
hfc.newCryptoKeyStore({ path: getKeyStoreForOrg(getOrgName(userOrg)) }));
client.setCryptoSuite(cryptoSuite);
}
const store = await hfc.newDefaultKeyValueStore({
path: getKeyStoreForOrg(getOrgName(userOrg))
});
client.setStateStore(store);
return client.createUser({
username: 'peer' + userOrg + 'Admin',
mspid: getMspID(userOrg),
cryptoContent: {
privateKeyPEM: keyPEM,
signedCertPEM: certPEM
}
});
}

View file

@ -0,0 +1,55 @@
{
"network-config": {
"orderer": {
"url": "grpcs://localhost:7050",
"server-hostname": "orderer.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
},
"org1": {
"name": "peerOrg1",
"mspid": "Org1MSP",
"ca": "https://localhost:7054",
"peers": {
"peer1": {
"requests": "grpcs://localhost:7051",
"events": "grpcs://localhost:7053",
"server-hostname": "peer0.org1.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
},
"peer2": {
"requests": "grpcs://localhost:7056",
"events": "grpcs://localhost:7058",
"server-hostname": "peer1.org1.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt"
}
},
"admin": {
"key": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore",
"cert": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts"
}
},
"org2": {
"name": "peerOrg2",
"mspid": "Org2MSP",
"ca": "https://localhost:8054",
"peers": {
"peer1": {
"requests": "grpcs://localhost:8051",
"events": "grpcs://localhost:8053",
"server-hostname": "peer0.org2.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
},
"peer2": {
"requests": "grpcs://localhost:8056",
"events": "grpcs://localhost:8058",
"server-hostname": "peer1.org2.example.com",
"tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt"
}
},
"admin": {
"key": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore",
"cert": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts"
}
}
}
}

View file

@ -0,0 +1,37 @@
{
"name": "balance-transfer-typescript",
"version": "0.1.0",
"description": "The balance transfer sample written using typescript",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Kapil Sachdeva",
"license": "Apache-2.0",
"devDependencies": {
"@types/body-parser": "^1.16.5",
"@types/cors": "^2.8.1",
"@types/express-jwt": "0.0.37",
"@types/express-session": "^1.15.3",
"@types/jsonwebtoken": "^7.2.3",
"@types/log4js": "0.0.33",
"@types/node": "^8.0.33",
"express-bearer-token": "^2.1.0",
"jsonwebtoken": "^8.1.0",
"ts-node": "^3.3.0",
"tslint": "^5.6.0",
"tslint-microsoft-contrib": "^5.0.1",
"typescript": "^2.5.3"
},
"dependencies": {
"body-parser": "^1.18.2",
"cookie-parser": "^1.4.3",
"cors": "^2.8.4",
"express": "^4.16.1",
"express-jwt": "^5.3.0",
"express-session": "^1.15.6",
"fabric-ca-client": "^1.0.2",
"fabric-client": "^1.0.2",
"log4js": "^0.6.38"
}
}

View file

@ -0,0 +1,71 @@
#!/bin/bash
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
function dkcl(){
CONTAINER_IDS=$(docker ps -aq)
echo
if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" = " " ]; then
echo "========== No containers available for deletion =========="
else
docker rm -f $CONTAINER_IDS
fi
echo
}
function dkrm(){
DOCKER_IMAGE_IDS=$(docker images | grep "dev\|none\|test-vp\|peer[0-9]-" | awk '{print $3}')
echo
if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" = " " ]; then
echo "========== No images available for deletion ==========="
else
docker rmi -f $DOCKER_IMAGE_IDS
fi
echo
}
function restartNetwork() {
echo
#teardown the network and clean the containers and intermediate images
docker-compose -f ../artifacts/docker-compose.yaml down
dkcl
dkrm
#Cleanup the material
rm -rf /tmp/hfc-test-kvs_peerOrg* $HOME/.hfc-key-store/ /tmp/fabric-client-kvs_peerOrg*
#Start the network
docker-compose -f ../artifacts/docker-compose.yaml up -d
echo
}
function installNodeModules() {
echo
if [ -d node_modules ]; then
echo "============== node modules installed already ============="
else
echo "============== Installing node modules ============="
npm install
fi
copyIndex fabric-client/index.d.ts
copyIndex fabric-ca-client/index.d.ts
echo
}
function copyIndex() {
if [ ! -f node_modules/$1 ]; then
cp types/$1 node_modules/$1
fi
}
restartNetwork
installNodeModules
PORT=4000 ts-node app.ts

View file

@ -0,0 +1,197 @@
#!/bin/bash
#
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
jq --version > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Please Install 'jq' https://stedolan.github.io/jq/ to execute this script"
echo
exit 1
fi
starttime=$(date +%s)
echo "POST request Enroll on Org1 ..."
echo
ORG1_TOKEN=$(curl -s -X POST \
http://localhost:4000/users \
-H "content-type: application/x-www-form-urlencoded" \
-d 'username=Jim&orgName=org1')
echo $ORG1_TOKEN
ORG1_TOKEN=$(echo $ORG1_TOKEN | jq ".token" | sed "s/\"//g")
echo
echo "ORG1 token is $ORG1_TOKEN"
echo
echo "POST request Enroll on Org2 ..."
echo
ORG2_TOKEN=$(curl -s -X POST \
http://localhost:4000/users \
-H "content-type: application/x-www-form-urlencoded" \
-d 'username=Barry&orgName=org2')
echo $ORG2_TOKEN
ORG2_TOKEN=$(echo $ORG2_TOKEN | jq ".token" | sed "s/\"//g")
echo
echo "ORG2 token is $ORG2_TOKEN"
echo
echo
echo "POST request Create channel ..."
echo
curl -s -X POST \
http://localhost:4000/channels \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"channelName":"mychannel",
"channelConfigPath":"../artifacts/channel/mychannel.tx"
}'
echo
echo
sleep 5
echo "POST request Join channel on Org1"
echo
curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"]
}'
echo
echo
echo "POST request Join channel on Org2"
echo
curl -s -X POST \
http://localhost:4000/channels/mychannel/peers \
-H "authorization: Bearer $ORG2_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"]
}'
echo
echo
echo "POST Install chaincode on Org1"
echo
curl -s -X POST \
http://localhost:4000/chaincodes \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1", "peer2"],
"chaincodeName":"mycc",
"chaincodePath":"github.com/example_cc",
"chaincodeVersion":"v0"
}'
echo
echo
echo "POST Install chaincode on Org2"
echo
curl -s -X POST \
http://localhost:4000/chaincodes \
-H "authorization: Bearer $ORG2_TOKEN" \
-H "content-type: application/json" \
-d '{
"peers": ["peer1","peer2"],
"chaincodeName":"mycc",
"chaincodePath":"github.com/example_cc",
"chaincodeVersion":"v0"
}'
echo
echo
echo "POST instantiate chaincode on peer1 of Org1"
echo
curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"chaincodeName":"mycc",
"chaincodeVersion":"v0",
"args":["a","100","b","200"]
}'
echo
echo
echo "POST invoke chaincode on peers of Org1 and Org2"
echo
TRX_ID=$(curl -s -X POST \
http://localhost:4000/channels/mychannel/chaincodes/mycc \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json" \
-d '{
"fcn":"move",
"args":["a","b","10"]
}')
echo "Transacton ID is $TRX_ID"
echo
echo
echo "GET query chaincode on peer1 of Org1"
echo
curl -s -X GET \
"http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query Block by blockNumber"
echo
curl -s -X GET \
"http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query Transaction by TransactionID"
echo
curl -s -X GET http://localhost:4000/channels/mychannel/transactions/$TRX_ID?peer=peer1 \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query ChainInfo"
echo
curl -s -X GET \
"http://localhost:4000/channels/mychannel?peer=peer1" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query Installed chaincodes"
echo
curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer1&type=installed" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query Instantiated chaincodes"
echo
curl -s -X GET \
"http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "GET query Channels"
echo
curl -s -X GET \
"http://localhost:4000/channels?peer=peer1" \
-H "authorization: Bearer $ORG1_TOKEN" \
-H "content-type: application/json"
echo
echo
echo "Total execution time : $(($(date +%s)-starttime)) secs ..."

View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
"removeComments": false,
"preserveConstEnums": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"suppressImplicitAnyIndexErrors": true,
"moduleResolution": "node",
"module": "commonjs",
"target": "es6",
"outDir": "dist",
"baseUrl": ".",
"typeRoots": [
"types",
"node_modules/@types"
]
},
"formatCodeOptions": {
"indentSize": 2,
"tabSize": 2
}
}

View file

@ -0,0 +1,38 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"tslint-microsoft-contrib"
],
"rules": {
"trailing-comma": [false, {
"multiline": "always",
"singleline": "never"
}],
"interface-name": [false, "always-prefix"],
"no-console": [true,
"time",
"timeEnd",
"trace"
],
"max-line-length": [
true,
100
],
"no-string-literal": false,
"no-use-before-declare": true,
"object-literal-sort-keys": false,
"ordered-imports": [false],
"quotemark": [
true,
"single",
"avoid-escape"
],
"variable-name": [
true,
"allow-leading-underscore",
"allow-pascal-case",
"ban-keywords",
"check-format"
]
}
}

View file

@ -0,0 +1,18 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module 'fabric-ca-client' {
}

View file

@ -0,0 +1,312 @@
/**
* Copyright 2017 Kapil Sachdeva All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare enum Status {
UNKNOWN = 0,
SUCCESS = 200,
BAD_REQUEST = 400,
FORBIDDEN = 403,
NOT_FOUND = 404,
REQUEST_ENTITY_TOO_LARGE = 413,
INTERNAL_SERVER_ERROR = 500,
SERVICE_UNAVAILABLE = 503
}
type ChaicodeType = "golang" | "car" | "java";
interface ProtoBufObject {
toBuffer(): Buffer;
}
interface KeyOpts {
ephemeral: boolean;
}
interface ConnectionOptions {
}
interface ConfigSignature extends ProtoBufObject {
signature_header: Buffer;
signature: Buffer;
}
interface ICryptoKey {
getSKI(): string;
isSymmetric(): boolean;
isPrivate(): boolean;
getPublicKey(): ICryptoKey;
toBytes(): string;
}
interface ICryptoKeyStore {
getKey(ski: string): Promise<string>;
putKey(key: ICryptoKey): Promise<ICryptoKey>;
}
interface IKeyValueStore {
getValue(name: string): Promise<string>;
setValue(name: string, value: string): Promise<string>;
}
interface IdentityFiles {
privateKey: string;
signedCert: string;
}
interface IdentityPEMs {
privateKeyPEM: string;
signedCertPEM: string;
}
interface UserOptions {
username: string;
mspid: string;
cryptoContent: IdentityFiles | IdentityPEMs;
}
interface ICryptoSuite {
decrypt(key: ICryptoKey, cipherText: Buffer, opts: any): Buffer;
deriveKey(key: ICryptoKey): ICryptoKey;
encrypt(key: ICryptoKey, plainText: Buffer, opts: any): Buffer;
getKey(ski: string): Promise<ICryptoKey>;
generateKey(opts: KeyOpts): Promise<ICryptoKey>;
hash(msg: string, opts: any): string;
importKey(pem: string, opts: KeyOpts): ICryptoKey | Promise<ICryptoKey>;
sign(key: ICryptoKey, digest: Buffer): Buffer;
verify(key: ICryptoKey, signature: Buffer, digest: Buffer): boolean;
}
interface ChannelRequest {
name: string;
orderer: Orderer;
envelope?: Buffer;
config?: Buffer;
txId?: TransactionId;
signatures: ConfigSignature[];
}
interface TransactionRequest {
proposalResponses: ProposalResponse[];
proposal: Proposal;
}
interface BroadcastResponse {
status: string;
}
interface IIdentity {
serialize(): Buffer;
getMSPId(): string;
isValid(): boolean;
getOrganizationUnits(): string;
verify(msg: Buffer, signature: Buffer, opts: any): boolean;
}
interface ISigningIdentity {
sign(msg: Buffer, opts: any): Buffer;
}
interface ChaincodeInstallRequest {
targets: Peer[];
chaincodePath: string;
chaincodeId: string;
chaincodeVersion: string;
chaincodePackage?: Buffer;
chaincodeType?: ChaicodeType;
}
interface ChaincodeInstantiateUpgradeRequest {
targets?: Peer[];
chaincodeType?: string;
chaincodeId: string;
chaincodeVersion: string;
txId: TransactionId;
fcn?: string;
args?: string[];
'endorsement-policy'?: any;
}
interface ChaincodeInvokeRequest {
targets?: Peer[];
chaincodeId: string;
txId: TransactionId;
fcn?: string;
args: string[];
}
interface ChaincodeQueryRequest {
targets?: Peer[];
chaincodeId: string;
txId: TransactionId;
fcn?: string;
args: string[];
}
interface ChaincodeInfo {
name: string;
version: string;
path: string;
input: string;
escc: string;
vscc: string;
}
interface ChannelInfo {
channel_id: string;
}
interface ChaincodeQueryResponse {
chaincodes: ChaincodeInfo[];
}
interface ChannelQueryResponse {
channels: ChannelInfo[];
}
interface OrdererRequest {
txId: TransactionId;
}
interface JoinChannelRequest {
txId: TransactionId;
targets: Peer[];
block: Buffer;
}
interface ResponseObject {
status: Status;
message: string;
payload: Buffer;
}
interface Proposal {
header: ByteBuffer;
payload: ByteBuffer;
extension: ByteBuffer;
}
interface Header {
channel_header: ByteBuffer;
signature_header: ByteBuffer;
}
interface ProposalResponse {
version: number;
timestamp: Date;
response: ResponseObject;
payload: Buffer;
endorsement: any;
}
type ProposalResponseObject = [Array<ProposalResponse>, Proposal, Header];
declare class Orderer {
}
declare class Peer {
setName(name: string): void;
getName(): string;
}
declare class EventHub {
connect(): void;
disconnect(): void;
getPeerAddr(): string;
setPeerAddr(url: string, opts: ConnectionOptions): void;
isconnected(): boolean;
registerBlockEvent(onEvent: (b: any) => void, onError?: (err: Error) => void): number;
registerTxEvent(txId: string, onEvent: (txId: any, code: string) => void, onError?: (err: Error) => void): void;
unregisterTxEvent(txId: string): void;
}
declare class Channel {
initialize(): Promise<void>;
addOrderer(orderer: Orderer): void;
addPeer(peer: Peer): void;
getGenesisBlock(request: OrdererRequest): Promise<any>;
getChannelConfig(): Promise<any>;
joinChannel(request: JoinChannelRequest): Promise<ProposalResponse>;
sendInstantiateProposal(request: ChaincodeInstantiateUpgradeRequest): Promise<ProposalResponseObject>;
sendTransactionProposal(request: ChaincodeInvokeRequest): Promise<ProposalResponseObject>;
sendTransaction(request: TransactionRequest): Promise<BroadcastResponse>;
queryByChaincode(request: ChaincodeQueryRequest): Promise<Buffer[]>;
queryBlock(blockNumber: number, target: Peer): Promise<any>;
queryTransaction(txId: string, target: Peer): Promise<any>;
queryInstantiatedChaincodes(target: Peer): Promise<ChaincodeQueryResponse>;
queryInfo(target: Peer): Promise<any>;
getOrderers(): Orderer[];
getPeers(): Peer[];
}
declare abstract class BaseClient {
static setLogger(logger: any): void;
static addConfigFile(path: string): void;
static getConfigSetting(name: string, default_value?: any): any;
static newCryptoSuite(): ICryptoSuite;
static newCryptoKeyStore(obj?: { path: string }): ICryptoKeyStore;
static newDefaultKeyValueStore(obj?: { path: string }): Promise<IKeyValueStore>;
setCryptoSuite(suite: ICryptoSuite): void;
getCryptoSuite(): ICryptoSuite;
}
declare class TransactionId {
getTransactionID(): string;
}
interface UserConfig {
enrollmentID: string;
name: string
roles?: string[];
affiliation?: string;
}
declare class User {
isEnrolled(): boolean;
getName(): string;
getRoles(): string[];
setRoles(roles: string[]): void;
getAffiliation(): string;
setAffiliation(affiliation: string): void;
getIdentity(): IIdentity;
getSigningIdentity(): ISigningIdentity;
setCryptoSuite(suite: ICryptoSuite): void;
setEnrollment(privateKey: ICryptoKey, certificate: string, mspId: string): Promise<void>;
}
declare class Client extends BaseClient {
isDevMode(): boolean;
getUserContext(name: string, checkPersistence: boolean): Promise<User> | User;
setUserContext(user: User, skipPersistence?: boolean): Promise<User>;
setDevMode(mode: boolean): void;
newOrderer(url: string, opts: ConnectionOptions): Orderer;
newChannel(name: string): Channel;
newPeer(url: string, opts: ConnectionOptions): Peer;
newEventHub(): EventHub;
newTransactionID(): TransactionId;
extractChannelConfig(envelope: Buffer): Buffer;
createChannel(request: ChannelRequest): Promise<BroadcastResponse>;
createUser(opts: UserOptions): Promise<User>;
signChannelConfig(config: Buffer): ConfigSignature;
setStateStore(store: IKeyValueStore): void;
installChaincode(request: ChaincodeInstallRequest): Promise<ProposalResponseObject>;
queryInstalledChaincodes(target: Peer): Promise<ChaincodeQueryResponse>;
queryChannels(target: Peer): Promise<ChannelQueryResponse>;
}
declare module 'fabric-client' {
export = Client;
}