Skip to content

Commit

Permalink
[FAB-6550] Sample app written in typescript
Browse files Browse the repository at this point in the history
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
ksachdeva authored and Keith Smith committed Dec 7, 2017
1 parent 38ad278 commit c446510
Show file tree
Hide file tree
Showing 23 changed files with 2,748 additions and 0 deletions.
4 changes: 4 additions & 0 deletions balance-transfer/typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
package-lock.json
dist
types/fabric-client
303 changes: 303 additions & 0 deletions balance-transfer/typescript/README.md
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>.
89 changes: 89 additions & 0 deletions balance-transfer/typescript/api/chaincode.ts
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);
}
Loading

0 comments on commit c446510

Please sign in to comment.