diff --git a/asset-transfer-basic/application-gateway-typescript/.eslintrc.json b/asset-transfer-basic/application-gateway-typescript/.eslintrc.json new file mode 100644 index 00000000..cc7230a8 --- /dev/null +++ b/asset-transfer-basic/application-gateway-typescript/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "env": { + "node": true, + "es6": true + }, + "root": true, + "ignorePatterns": [ + "dist/" + ], + "extends": [ + "eslint:recommended" + ], + "rules": { + "indent": [ + "error", + 4 + ], + "quotes": [ + "error", + "single" + ] + }, + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "impliedStrict": true + } + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] + } + ] + } \ No newline at end of file diff --git a/asset-transfer-basic/application-gateway-typescript/.gitignore b/asset-transfer-basic/application-gateway-typescript/.gitignore new file mode 100644 index 00000000..99e5af9f --- /dev/null +++ b/asset-transfer-basic/application-gateway-typescript/.gitignore @@ -0,0 +1,14 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules/ +jspm_packages/ + +# Compiled TypeScript files +dist diff --git a/asset-transfer-basic/application-gateway-typescript/package.json b/asset-transfer-basic/application-gateway-typescript/package.json new file mode 100644 index 00000000..d633b8af --- /dev/null +++ b/asset-transfer-basic/application-gateway-typescript/package.json @@ -0,0 +1,52 @@ +{ + "name": "asset-transfer-basic", + "version": "1.0.0", + "description": "Asset Transfer Basic Application implemented in typeScript using fabric-gateway", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "scripts": { + "lint": "eslint . --ext .ts", + "pretest": "npm run lint", + "start": "npm run build && node dist/app.js", + "build": "tsc", + "build:watch": "tsc -w", + "prepublishOnly": "npm run build" + }, + "engineStrict": true, + "author": "Hyperledger", + "license": "Apache-2.0", + "dependencies": { + "fabric-gateway": "0.1.0-dev.20211006.12" + }, + "devDependencies": { + "@tsconfig/node12": "^1.0.9", + "@typescript-eslint/eslint-plugin": "^4.31.2", + "@typescript-eslint/parser": "^4.31.2", + "eslint": "^7.32.0", + "typescript": "^3.1.6" + }, + "nyc": { + "extension": [ + ".ts", + ".tsx" + ], + "exclude": [ + "coverage/**", + "dist/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": true, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/asset-transfer-basic/application-gateway-typescript/src/app.ts b/asset-transfer-basic/application-gateway-typescript/src/app.ts new file mode 100644 index 00000000..0664ee42 --- /dev/null +++ b/asset-transfer-basic/application-gateway-typescript/src/app.ts @@ -0,0 +1,246 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import * as crypto from 'crypto'; +import { connect, Identity, Signer, signers ,Contract} from 'fabric-gateway'; +import { promises as fs } from 'fs'; +import * as path from 'path'; + +const channelName = 'mychannel'; +const chaincodeName = 'basic'; +const mspId = 'Org1MSP'; + +// path to crypto materials +const cryptoPath = path.resolve( + __dirname, + '..', + '..', + '..', + 'test-network', + 'organizations', + 'peerOrganizations', + 'org1.example.com', +); + +//path to user private key directory +const keyDirectoryPath = path.resolve( + cryptoPath, + 'users', + 'User1@org1.example.com', + 'msp', + 'keystore' +); +//path to user certificate +const certPath = path.resolve( + cryptoPath, + 'users', + 'User1@org1.example.com', + 'msp', + 'signcerts', + 'cert.pem', +); +//path to peer tls certificate +const tlsCertPath = path.resolve( + cryptoPath, + 'peers', + 'peer0.org1.example.com', + 'tls', + 'ca.crt', +); + +//Gateway peer endpoint +const peerEndpoint = 'localhost:7051'; + +// pre-requisites: +// - fabric-sample two organization test-network setup with two peers and ordering service and and 2 certificate authorities +// ===> from directory /fabric-samples/test-network +// ./network.sh up createChannel +// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel" +// with the chaincode name of "basic". The following deploy command will package, +// install, approve, and commit the javascript chaincode, all the actions it takes +// to deploy a chaincode to a channel. +// ===> from directory /fabric-samples/test-network +// ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript +// - Be sure that node.js is installed +// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript +// node -v +// - npm installed code dependencies +// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript +// npm install +// - to run this test application +// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript +// npm start + +/** + * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes + * -- How to submit a transaction + * -- How to query and check the results + * + * To see the SDK workings, try setting the logging to show on the console before running + * export HFC_LOGGING='{"debug":"console"}' + */ + +async function main():Promise { + + // The gRPC client connection should be shared by all Gateway connections to this endpoint + const client = await newGrpcConnection(); + + const gateway = connect({ + client, + identity: await newIdentity(), + signer: await newSigner(), + }); + + try { + + // Build a network instance based on the channel where the smart contract is deployed + const network = gateway.getNetwork(channelName); + + // Get the contract from the network. + const contract = network.getContract(chaincodeName); + + // Initialize a set of asset data on the channel using the chaincode 'InitLedger' function. + await initLedger(contract); + + //Return all the current assets on the ledger. + await getAllAssets(contract); + + //Create new asset on the ledger. + await createAsset(contract); + + //Update an existing asset asynchronously. + await updateAssetAsync(contract); + + //Get the asset details by assetID + await readAssetByID(contract); + + //Update an asset which does not exist. + await updateNonExistentAsset(contract) + + } finally { + gateway.close(); + client.close(); + } + +} + +main().catch(error => console.error('******** FAILED to run the application:', error)); + +async function newGrpcConnection(): Promise { + const tlsRootCert = await fs.readFile(tlsCertPath); + const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); + const GrpcClient = grpc.makeGenericClientConstructor({}, ''); + return new GrpcClient(peerEndpoint, tlsCredentials, { + 'grpc.ssl_target_name_override': 'peer0.org1.example.com', + }); +} + +async function newIdentity(): Promise { + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +async function newSigner(): Promise { + const files = await fs.readdir(keyDirectoryPath); + const privateKeyPem = await fs.readFile(path.resolve(keyDirectoryPath,files[0])); + const privateKey = crypto.createPrivateKey(privateKeyPem); + return signers.newPrivateKeySigner(privateKey); +} + + +async function initLedger(contract:Contract):Promise { + // This type of transaction would only be run once by an application the first time it was started after it + // deployed the first time. Any updates to the chaincode deployed later would likely not need to run + // an "init" type function. + + console.log( + '\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger', + ); + + // Submit a transaction, blocking until the transaction has been committed on the ledger + await contract.submitTransaction('InitLedger'); + + console.log('*** Result: committed'); + +} + +async function getAllAssets(contract:Contract):Promise { + console.log( + '\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger', + ); + const result = await contract.evaluateTransaction('GetAllAssets'); + console.log(`*** Result: ${Buffer.from(result).toString()}`); +} + +async function createAsset(contract:Contract):Promise { + console.log( + '\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments', + ); + await contract.submitTransaction( + 'CreateAsset', + 'asset13', + 'yellow', + '5', + 'Tom', + '1300', + ); + console.log('*** Result: committed'); +} + +async function updateAssetAsync(contract:Contract):Promise { + + // Submit transaction asynchronously, blocking until the transaction has been sent to the orderer, and allowing + // this thread to process the chaincode response (e.g. update a UI) without waiting for the commit notification + + const commit = await contract.submitAsync('UpdateAsset', { + arguments: ['asset1', 'blue', '5', 'Tomoko', '400'], + }); + + console.log('*** Waiting for transaction commit'); + + + const status = await commit.getStatus(); + if (!status.successful) { + throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${status.code}`); + } + + console.log('*** Transaction committed successfully'); + +} + +async function readAssetByID(contract:Contract):Promise { + console.log( + '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes', + ); + const result = await contract.evaluateTransaction('ReadAsset', 'asset1'); + console.log(`*** Result: ${Buffer.from(result).toString()}`); + +} + +async function updateNonExistentAsset(contract:Contract):Promise{ + try { + // How about we try a transaction where the executing chaincode throws an error + // Notice how the submitTransaction will throw an error containing the error thrown by the chaincode + console.log( + '\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error', + ); + await contract.submitTransaction( + 'UpdateAsset', + 'asset70', + 'blue', + '5', + 'Tomoko', + '300', + ); + console.log('******** FAILED to return an error'); + } catch (error) { + console.log(`*** Successfully caught the error: \n ${error}`); + } +} + + + diff --git a/asset-transfer-basic/application-gateway-typescript/tsconfig.json b/asset-transfer-basic/application-gateway-typescript/tsconfig.json new file mode 100644 index 00000000..efc75170 --- /dev/null +++ b/asset-transfer-basic/application-gateway-typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends":"@tsconfig/node12/tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "noImplicitAny": true + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "./src/**/*.spec.ts" + ] +} diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index 22f5b8c6..8f78c9e7 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -63,6 +63,18 @@ node dist/app.js popd stopNetwork +# Run gateway typescript application +createNetwork +print "Initializing Typescript gateway application" +pushd ../asset-transfer-basic/application-gateway-typescript +npm install +print "Building app.ts" +npm run build +print "Running the output app" +node dist/app.js +popd +stopNetwork + # Run typescript HSM application createNetwork print "Initializing Typescript HSM application"