mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
[FAB-6550] Sample app written in typescript
This is a sample application that demonstrates usage of Fabric SDK typings. Change-Id: I5b9b42c666de51a490043cafe0faac29e4f4a0a4 Signed-off-by: Kapil Sachdeva <ksachdeva17@gmail.com>
This commit is contained in:
parent
38ad27885f
commit
c4465100c4
23 changed files with 2748 additions and 0 deletions
4
balance-transfer/typescript/.gitignore
vendored
Normal file
4
balance-transfer/typescript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
package-lock.json
|
||||
dist
|
||||
types/fabric-client
|
||||
303
balance-transfer/typescript/README.md
Normal file
303
balance-transfer/typescript/README.md
Normal 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>.
|
||||
89
balance-transfer/typescript/api/chaincode.ts
Normal file
89
balance-transfer/typescript/api/chaincode.ts
Normal 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);
|
||||
}
|
||||
261
balance-transfer/typescript/api/channel.ts
Normal file
261
balance-transfer/typescript/api/channel.ts
Normal 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);
|
||||
}
|
||||
27
balance-transfer/typescript/api/index.ts
Normal file
27
balance-transfer/typescript/api/index.ts
Normal 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);
|
||||
}
|
||||
69
balance-transfer/typescript/api/users.ts
Normal file
69
balance-transfer/typescript/api/users.ts
Normal 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);
|
||||
}
|
||||
23
balance-transfer/typescript/api/utils.ts
Normal file
23
balance-transfer/typescript/api/utils.ts
Normal 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;
|
||||
}
|
||||
92
balance-transfer/typescript/app.ts
Normal file
92
balance-transfer/typescript/app.ts
Normal 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;
|
||||
13
balance-transfer/typescript/app_config.json
Normal file
13
balance-transfer/typescript/app_config.json
Normal 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"
|
||||
}]
|
||||
}
|
||||
1
balance-transfer/typescript/artifacts
Symbolic link
1
balance-transfer/typescript/artifacts
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../artifacts
|
||||
30
balance-transfer/typescript/config.ts
Normal file
30
balance-transfer/typescript/config.ts
Normal 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
|
||||
};
|
||||
23
balance-transfer/typescript/interfaces.ts
Normal file
23
balance-transfer/typescript/interfaces.ts
Normal 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;
|
||||
}
|
||||
148
balance-transfer/typescript/lib/chaincode.ts
Normal file
148
balance-transfer/typescript/lib/chaincode.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
599
balance-transfer/typescript/lib/channel.ts
Normal file
599
balance-transfer/typescript/lib/channel.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
311
balance-transfer/typescript/lib/helper.ts
Normal file
311
balance-transfer/typescript/lib/helper.ts
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
||||
55
balance-transfer/typescript/lib/network-config.json
Normal file
55
balance-transfer/typescript/lib/network-config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
balance-transfer/typescript/package.json
Normal file
37
balance-transfer/typescript/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
71
balance-transfer/typescript/runApp.sh
Executable file
71
balance-transfer/typescript/runApp.sh
Executable 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
|
||||
197
balance-transfer/typescript/testAPIs.sh
Executable file
197
balance-transfer/typescript/testAPIs.sh
Executable 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 ..."
|
||||
27
balance-transfer/typescript/tsconfig.json
Normal file
27
balance-transfer/typescript/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
38
balance-transfer/typescript/tslint.json
Normal file
38
balance-transfer/typescript/tslint.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
18
balance-transfer/typescript/types/fabric-ca-client/index.d.ts
vendored
Normal file
18
balance-transfer/typescript/types/fabric-ca-client/index.d.ts
vendored
Normal 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' {
|
||||
}
|
||||
312
balance-transfer/typescript/types/fabric-client/index.d.ts
vendored
Normal file
312
balance-transfer/typescript/types/fabric-client/index.d.ts
vendored
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue