-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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>
- Loading branch information
Showing
23 changed files
with
2,748 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
package-lock.json | ||
dist | ||
types/fabric-client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Oops, something went wrong.