mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Add JavaScript asset-transfer-basic application
While the TypeScript application sample is essentially identical (with the addition of some type declarations), there seems to be sufficient uncertainty amongst JavaScript developers not familiar with TypeScript on how best to implement a JavaScript application that it is worthwhile having one plain JavaScript sample using the current client API. Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
This commit is contained in:
parent
76088d0273
commit
7258b4f4ab
8 changed files with 420 additions and 28 deletions
|
|
@ -6,7 +6,7 @@ indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.ts]
|
[*.{mj,cj,j,t}s]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
quote_type = single
|
quote_type = single
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,34 +37,63 @@ Note that the asset transfer implemented by the smart contract is a simplified s
|
||||||
The Fabric test network is used to deploy and run this sample. Follow these steps in order:
|
The Fabric test network is used to deploy and run this sample. Follow these steps in order:
|
||||||
|
|
||||||
1. Create the test network and a channel (from the `test-network` folder).
|
1. Create the test network and a channel (from the `test-network` folder).
|
||||||
|
|
||||||
```
|
```
|
||||||
./network.sh up createChannel -c mychannel -ca
|
./network.sh up createChannel -c mychannel -ca
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Deploy one of the smart contract implementations (from the `test-network` folder).
|
1. Deploy one of the smart contract implementations (from the `test-network` folder).
|
||||||
```
|
|
||||||
# To deploy the TypeScript chaincode implementation
|
- To deploy the **TypeScript** chaincode implementation:
|
||||||
|
|
||||||
|
```shell
|
||||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
|
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript
|
||||||
|
```
|
||||||
|
|
||||||
# To deploy the Go chaincode implementation
|
- To deploy the **JavaScript** chaincode implementation:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript
|
||||||
|
```
|
||||||
|
|
||||||
|
- To deploy the **Go** chaincode implementation:
|
||||||
|
|
||||||
|
```shell
|
||||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
|
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go/ -ccl go
|
||||||
|
```
|
||||||
|
|
||||||
# To deploy the Java chaincode implementation
|
- To deploy the **Java** chaincode implementation:
|
||||||
|
```shell
|
||||||
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
|
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java/ -ccl java
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Run the application (from the `asset-transfer-basic` folder).
|
1. Run the application (from the `asset-transfer-basic` folder).
|
||||||
```
|
|
||||||
# To run the Typescript sample application
|
- To run the **TypeScript** sample application:
|
||||||
|
|
||||||
|
```shell
|
||||||
cd application-gateway-typescript
|
cd application-gateway-typescript
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
# To run the Go sample application
|
- To run the **JavaScript** sample application:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd application-gateway-javascript
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
- To run the **Go** sample application:
|
||||||
|
|
||||||
|
```shell
|
||||||
cd application-gateway-go
|
cd application-gateway-go
|
||||||
go run .
|
go run .
|
||||||
|
```
|
||||||
|
|
||||||
# To run the Java sample application
|
- To run the **Java** sample application:
|
||||||
|
```shell
|
||||||
cd application-gateway-java
|
cd application-gateway-java
|
||||||
./gradlew run
|
./gradlew run
|
||||||
```
|
```
|
||||||
|
|
@ -73,6 +102,6 @@ The Fabric test network is used to deploy and run this sample. Follow these step
|
||||||
|
|
||||||
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
|
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
./network.sh down
|
./network.sh down
|
||||||
```
|
```
|
||||||
11
asset-transfer-basic/application-gateway-javascript/.gitignore
vendored
Normal file
11
asset-transfer-basic/application-gateway-javascript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2023,
|
||||||
|
sourceType: 'commonjs',
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "asset-transfer-basic",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Asset Transfer Basic Application implemented in JavaScript using fabric-gateway",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"start": "node src/app.js"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"author": "Hyperledger",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@grpc/grpc-js": "^1.10",
|
||||||
|
"@hyperledger/fabric-gateway": "^1.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.5.0",
|
||||||
|
"eslint": "^9.5.0",
|
||||||
|
"globals": "^15.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
300
asset-transfer-basic/application-gateway-javascript/src/app.js
Normal file
300
asset-transfer-basic/application-gateway-javascript/src/app.js
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
/*
|
||||||
|
* Copyright IBM Corp. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
const grpc = require('@grpc/grpc-js');
|
||||||
|
const { connect, signers } = require('@hyperledger/fabric-gateway');
|
||||||
|
const crypto = require('node:crypto');
|
||||||
|
const fs = require('node:fs/promises');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { TextDecoder } = require('node:util');
|
||||||
|
|
||||||
|
const channelName = envOrDefault('CHANNEL_NAME', 'mychannel');
|
||||||
|
const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic');
|
||||||
|
const mspId = envOrDefault('MSP_ID', 'Org1MSP');
|
||||||
|
|
||||||
|
// Path to crypto materials.
|
||||||
|
const cryptoPath = envOrDefault(
|
||||||
|
'CRYPTO_PATH',
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'test-network',
|
||||||
|
'organizations',
|
||||||
|
'peerOrganizations',
|
||||||
|
'org1.example.com'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Path to user private key directory.
|
||||||
|
const keyDirectoryPath = envOrDefault(
|
||||||
|
'KEY_DIRECTORY_PATH',
|
||||||
|
path.resolve(
|
||||||
|
cryptoPath,
|
||||||
|
'users',
|
||||||
|
'User1@org1.example.com',
|
||||||
|
'msp',
|
||||||
|
'keystore'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Path to user certificate directory.
|
||||||
|
const certDirectoryPath = envOrDefault(
|
||||||
|
'CERT_DIRECTORY_PATH',
|
||||||
|
path.resolve(
|
||||||
|
cryptoPath,
|
||||||
|
'users',
|
||||||
|
'User1@org1.example.com',
|
||||||
|
'msp',
|
||||||
|
'signcerts'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Path to peer tls certificate.
|
||||||
|
const tlsCertPath = envOrDefault(
|
||||||
|
'TLS_CERT_PATH',
|
||||||
|
path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Gateway peer endpoint.
|
||||||
|
const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051');
|
||||||
|
|
||||||
|
// Gateway peer SSL host name override.
|
||||||
|
const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.example.com');
|
||||||
|
|
||||||
|
const utf8Decoder = new TextDecoder();
|
||||||
|
const assetId = `asset${String(Date.now())}`;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
displayInputParameters();
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
// Default timeouts for different gRPC calls
|
||||||
|
evaluateOptions: () => {
|
||||||
|
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||||
|
},
|
||||||
|
endorseOptions: () => {
|
||||||
|
return { deadline: Date.now() + 15000 }; // 15 seconds
|
||||||
|
},
|
||||||
|
submitOptions: () => {
|
||||||
|
return { deadline: Date.now() + 5000 }; // 5 seconds
|
||||||
|
},
|
||||||
|
commitStatusOptions: () => {
|
||||||
|
return { deadline: Date.now() + 60000 }; // 1 minute
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get a network instance representing the channel where the smart contract is deployed.
|
||||||
|
const network = gateway.getNetwork(channelName);
|
||||||
|
|
||||||
|
// Get the smart contract from the network.
|
||||||
|
const contract = network.getContract(chaincodeName);
|
||||||
|
|
||||||
|
// Initialize a set of asset data on the ledger using the chaincode 'InitLedger' function.
|
||||||
|
await initLedger(contract);
|
||||||
|
|
||||||
|
// Return all the current assets on the ledger.
|
||||||
|
await getAllAssets(contract);
|
||||||
|
|
||||||
|
// Create a new asset on the ledger.
|
||||||
|
await createAsset(contract);
|
||||||
|
|
||||||
|
// Update an existing asset asynchronously.
|
||||||
|
await transferAssetAsync(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);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function newGrpcConnection() {
|
||||||
|
const tlsRootCert = await fs.readFile(tlsCertPath);
|
||||||
|
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
|
||||||
|
return new grpc.Client(peerEndpoint, tlsCredentials, {
|
||||||
|
'grpc.ssl_target_name_override': peerHostAlias,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newIdentity() {
|
||||||
|
const certPath = await getFirstDirFileName(certDirectoryPath);
|
||||||
|
const credentials = await fs.readFile(certPath);
|
||||||
|
return { mspId, credentials };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFirstDirFileName(dirPath) {
|
||||||
|
const files = await fs.readdir(dirPath);
|
||||||
|
const file = files[0];
|
||||||
|
if (!file) {
|
||||||
|
throw new Error(`No files in directory: ${dirPath}`);
|
||||||
|
}
|
||||||
|
return path.join(dirPath, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newSigner() {
|
||||||
|
const keyPath = await getFirstDirFileName(keyDirectoryPath);
|
||||||
|
const privateKeyPem = await fs.readFile(keyPath);
|
||||||
|
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
||||||
|
return signers.newPrivateKeySigner(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type of transaction would typically only be run once by an application the first time it was started after its
|
||||||
|
* initial deployment. A new version of the chaincode deployed later would likely not need to run an "init" function.
|
||||||
|
*/
|
||||||
|
async function initLedger(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger'
|
||||||
|
);
|
||||||
|
|
||||||
|
await contract.submitTransaction('InitLedger');
|
||||||
|
|
||||||
|
console.log('*** Transaction committed successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a transaction to query ledger state.
|
||||||
|
*/
|
||||||
|
async function getAllAssets(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger'
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultBytes = await contract.evaluateTransaction('GetAllAssets');
|
||||||
|
|
||||||
|
const resultJson = utf8Decoder.decode(resultBytes);
|
||||||
|
const result = JSON.parse(resultJson);
|
||||||
|
console.log('*** Result:', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a transaction synchronously, blocking until it has been committed to the ledger.
|
||||||
|
*/
|
||||||
|
async function createAsset(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments'
|
||||||
|
);
|
||||||
|
|
||||||
|
await contract.submitTransaction(
|
||||||
|
'CreateAsset',
|
||||||
|
assetId,
|
||||||
|
'yellow',
|
||||||
|
'5',
|
||||||
|
'Tom',
|
||||||
|
'1300'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('*** Transaction committed successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit transaction asynchronously, allowing the application to process the smart contract response (e.g. update a UI)
|
||||||
|
* while waiting for the commit notification.
|
||||||
|
*/
|
||||||
|
async function transferAssetAsync(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Async Submit Transaction: TransferAsset, updates existing asset owner'
|
||||||
|
);
|
||||||
|
|
||||||
|
const commit = await contract.submitAsync('TransferAsset', {
|
||||||
|
arguments: [assetId, 'Saptha'],
|
||||||
|
});
|
||||||
|
const oldOwner = utf8Decoder.decode(commit.getResult());
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`*** Successfully submitted transaction to transfer ownership from ${oldOwner} to Saptha`
|
||||||
|
);
|
||||||
|
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 ${String(status.code)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('*** Transaction committed successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readAssetByID(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Evaluate Transaction: ReadAsset, function returns asset attributes'
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultBytes = await contract.evaluateTransaction(
|
||||||
|
'ReadAsset',
|
||||||
|
assetId
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultJson = utf8Decoder.decode(resultBytes);
|
||||||
|
const result = JSON.parse(resultJson);
|
||||||
|
console.log('*** Result:', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* submitTransaction() will throw an error containing details of any error responses from the smart contract.
|
||||||
|
*/
|
||||||
|
async function updateNonExistentAsset(contract) {
|
||||||
|
console.log(
|
||||||
|
'\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error'
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined.
|
||||||
|
*/
|
||||||
|
function envOrDefault(key, defaultValue) {
|
||||||
|
return process.env[key] || defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* displayInputParameters() will print the global scope parameters used by the main driver routine.
|
||||||
|
*/
|
||||||
|
function displayInputParameters() {
|
||||||
|
console.log(`channelName: ${channelName}`);
|
||||||
|
console.log(`chaincodeName: ${chaincodeName}`);
|
||||||
|
console.log(`mspId: ${mspId}`);
|
||||||
|
console.log(`cryptoPath: ${cryptoPath}`);
|
||||||
|
console.log(`keyDirectoryPath: ${keyDirectoryPath}`);
|
||||||
|
console.log(`certDirectoryPath: ${certDirectoryPath}`);
|
||||||
|
console.log(`tlsCertPath: ${tlsCertPath}`);
|
||||||
|
console.log(`peerEndpoint: ${peerEndpoint}`);
|
||||||
|
console.log(`peerHostAlias: ${peerHostAlias}`);
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,8 @@ set -x
|
||||||
createNetwork
|
createNetwork
|
||||||
|
|
||||||
|
|
||||||
# Run Go gateway application
|
# Run Go application
|
||||||
print "Initializing Go gateway application"
|
print "Initializing Go application"
|
||||||
export CHAINCODE_NAME=go_gateway
|
export CHAINCODE_NAME=go_gateway
|
||||||
deployChaincode
|
deployChaincode
|
||||||
pushd ../asset-transfer-basic/application-gateway-go
|
pushd ../asset-transfer-basic/application-gateway-go
|
||||||
|
|
@ -48,8 +48,8 @@ go run .
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
||||||
# Run gateway typescript application
|
# Run TypeScript application
|
||||||
print "Initializing Typescript gateway application"
|
print "Initializing TypeScript application"
|
||||||
export CHAINCODE_NAME=typescript_gateway
|
export CHAINCODE_NAME=typescript_gateway
|
||||||
deployChaincode
|
deployChaincode
|
||||||
pushd ../asset-transfer-basic/application-gateway-typescript
|
pushd ../asset-transfer-basic/application-gateway-typescript
|
||||||
|
|
@ -59,7 +59,18 @@ npm start
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
||||||
# Run Java application using gateway
|
# Run JavaScript application
|
||||||
|
print "Initializing JavaScript application"
|
||||||
|
export CHAINCODE_NAME=javascript_gateway
|
||||||
|
deployChaincode
|
||||||
|
pushd ../asset-transfer-basic/application-gateway-javascript
|
||||||
|
npm install
|
||||||
|
print "Start application"
|
||||||
|
npm start
|
||||||
|
popd
|
||||||
|
|
||||||
|
|
||||||
|
# Run Java application
|
||||||
print "Initializing Java application"
|
print "Initializing Java application"
|
||||||
export CHAINCODE_NAME=java_gateway
|
export CHAINCODE_NAME=java_gateway
|
||||||
deployChaincode
|
deployChaincode
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue