From ef5e91f82b0de33c1edb880462b135b73ab7455a Mon Sep 17 00:00:00 2001 From: Annmol Date: Sun, 6 Mar 2022 16:48:26 -0600 Subject: [PATCH] Added files --- supply-chain-client/AppUtil.js | 66 ++++++++ supply-chain-client/CAUtil.js | 98 ++++++++++++ supply-chain-client/README.md | 105 ++++++++++++ supply-chain-client/client/app.js | 102 ++++++++++++ supply-chain-client/client/favicon.png | Bin 0 -> 896 bytes supply-chain-client/client/index.html | 149 ++++++++++++++++++ .../client/transaction_flow.PNG | Bin 0 -> 18970 bytes supply-chain-client/enrollAdmin.js | 73 +++++++++ supply-chain-client/enrollUser.js | 84 ++++++++++ supply-chain-client/invoker.js | 104 ++++++++++++ supply-chain-client/package.json | 26 +++ supply-chain-client/routes.js | 27 ++++ supply-chain-client/server.js | 34 ++++ supply-chain-client/transaction_flow.PNG | Bin 0 -> 18970 bytes 14 files changed, 868 insertions(+) create mode 100644 supply-chain-client/AppUtil.js create mode 100644 supply-chain-client/CAUtil.js create mode 100644 supply-chain-client/README.md create mode 100644 supply-chain-client/client/app.js create mode 100644 supply-chain-client/client/favicon.png create mode 100644 supply-chain-client/client/index.html create mode 100644 supply-chain-client/client/transaction_flow.PNG create mode 100644 supply-chain-client/enrollAdmin.js create mode 100644 supply-chain-client/enrollUser.js create mode 100644 supply-chain-client/invoker.js create mode 100644 supply-chain-client/package.json create mode 100644 supply-chain-client/routes.js create mode 100644 supply-chain-client/server.js create mode 100644 supply-chain-client/transaction_flow.PNG diff --git a/supply-chain-client/AppUtil.js b/supply-chain-client/AppUtil.js new file mode 100644 index 00000000..bd104183 --- /dev/null +++ b/supply-chain-client/AppUtil.js @@ -0,0 +1,66 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +exports.buildCCPOrg1 = () => { + // load the common connection configuration file + const ccpPath = path.resolve(__dirname, '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); + const fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + const contents = fs.readFileSync(ccpPath, 'utf8'); + + // build a JSON object from the file contents + const ccp = JSON.parse(contents); + + console.log(`Loaded the network configuration located at ${ccpPath}`); + return ccp; +}; + +exports.buildCCPOrg2 = () => { + // load the common connection configuration file + const ccpPath = path.resolve(__dirname, '..', 'test-network', + 'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json'); + const fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + const contents = fs.readFileSync(ccpPath, 'utf8'); + + // build a JSON object from the file contents + const ccp = JSON.parse(contents); + + console.log(`Loaded the network configuration located at ${ccpPath}`); + return ccp; +}; + +exports.buildWallet = async (Wallets, walletPath) => { + // Create a new wallet : Note that wallet is for managing identities. + let wallet; + if (walletPath) { + wallet = await Wallets.newFileSystemWallet(walletPath); + console.log(`Built a file system wallet at ${walletPath}`); + } else { + wallet = await Wallets.newInMemoryWallet(); + console.log('Built an in memory wallet'); + } + + return wallet; +}; + +exports.prettyJSONString = (inputString) => { + if (inputString) { + return JSON.stringify(JSON.parse(inputString), null, 2); + } + else { + return inputString; + } +} diff --git a/supply-chain-client/CAUtil.js b/supply-chain-client/CAUtil.js new file mode 100644 index 00000000..10ec7344 --- /dev/null +++ b/supply-chain-client/CAUtil.js @@ -0,0 +1,98 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const adminUserId = 'admin'; +const adminUserPasswd = 'adminpw'; + +/** + * + * @param {*} FabricCAServices + * @param {*} ccp + */ +exports.buildCAClient = (FabricCAServices, ccp, caHostName) => { + // Create a new CA client for interacting with the CA. + const caInfo = ccp.certificateAuthorities[caHostName]; //lookup CA details from config + const caTLSCACerts = caInfo.tlsCACerts.pem; + const caClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName); + + console.log(`Built a CA Client named ${caInfo.caName}`); + return caClient; +}; + +exports.enrollAdmin = async (caClient, wallet, orgMspId) => { + try { + // Check to see if we've already enrolled the admin user. + const identity = await wallet.get(adminUserId); + if (identity) { + console.log('An identity for the admin user already exists in the wallet'); + return; + } + + // Enroll the admin user, and import the new identity into the wallet. + const enrollment = await caClient.enroll({ enrollmentID: adminUserId, enrollmentSecret: adminUserPasswd }); + const x509Identity = { + credentials: { + certificate: enrollment.certificate, + privateKey: enrollment.key.toBytes(), + }, + mspId: orgMspId, + type: 'X.509', + }; + await wallet.put(adminUserId, x509Identity); + console.log('Successfully enrolled admin user and imported it into the wallet'); + } catch (error) { + console.error(`Failed to enroll admin user : ${error}`); + } +}; + +exports.registerAndEnrollUser = async (caClient, wallet, orgMspId, userId, affiliation) => { + try { + // Check to see if we've already enrolled the user + const userIdentity = await wallet.get(userId); + if (userIdentity) { + console.log(`An identity for the user ${userId} already exists in the wallet`); + return; + } + + // Must use an admin to register a new user + const adminIdentity = await wallet.get(adminUserId); + if (!adminIdentity) { + console.log('An identity for the admin user does not exist in the wallet'); + console.log('Enroll the admin user before retrying'); + return; + } + + // build a user object for authenticating with the CA + const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type); + const adminUser = await provider.getUserContext(adminIdentity, adminUserId); + + // Register the user, enroll the user, and import the new identity into the wallet. + // if affiliation is specified by client, the affiliation value must be configured in CA + const secret = await caClient.register({ + affiliation: affiliation, + enrollmentID: userId, + role: 'client' + }, adminUser); + const enrollment = await caClient.enroll({ + enrollmentID: userId, + enrollmentSecret: secret + }); + const x509Identity = { + credentials: { + certificate: enrollment.certificate, + privateKey: enrollment.key.toBytes(), + }, + mspId: orgMspId, + type: 'X.509', + }; + await wallet.put(userId, x509Identity); + console.log(`Successfully registered and enrolled user ${userId} and imported it into the wallet`); + } catch (error) { + console.error(`Failed to register user : ${error}`); + } +}; diff --git a/supply-chain-client/README.md b/supply-chain-client/README.md new file mode 100644 index 00000000..e57b9932 --- /dev/null +++ b/supply-chain-client/README.md @@ -0,0 +1,105 @@ +# farm-chain POC with Fabric v1.4 +--- + +## Setting up Hyperledger Fabric and Dependencies + +### Remove any pre-existing containers and images: + +``$ docker rm -f $(docker -q)`` \ +``$ docker rmi -f $(docker ps -aq)`` + +If you have not used docker previously on the same machine follow the step 1. + +1. Docker - Guide (https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) +2. NodeJS - Guide (https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-18-04) +3. Hyperledger Fabric v1.4 and above + + + +### Setup Golang language: +```bash +curl -O https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz +sha256sum go1.11.1.linux-amd64.tar.gz +tar -xvf go1.11.1.linux-amd64.tar.gz +sudo mv go /usr/local +``` +Add the following paths to the bashrc file +```bash +export GOPATH=$HOME/go +export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin +``` +Re-source the bash script +```bash +source ~/.bashrc +source ~/.bashrc +``` + +### Get fabric-samples and install +``` +git clone https://github.com/hyperledger/fabric-samples.git +``` +From the root level of the fabric-samples directory: +``` +$ ./scripts/bootstrap.h +``` + +## Start the Hyperledger Fabric network for the POC +From the root level of this project + +``` +$ ./startFabric.sh >&logs/startup_logs.txt +``` + +On first run of the project: install the required node js libraries, register the Admin and User components of our network, and start the server: + +``` +$ npm install +``` +If this throws some errors, try running ``$ npm update`` and then ``$npm install`` + +Enroll the admin user: +``` +$ node enrollAdmin.js +``` +Expected output: +```markdown +Store path:/home/sakya/.hfc-key-store +Successfully loaded admin from persistence +Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"091d15d647a3053a769faf8f4122e7ac577323b919d00413d6f4e2208337eee9","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICATCCAaigAwIBAgIUTyI7MAMMLUNlJt7m+dPVhKZKvXgwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwNTAzMDUyMTAwWhcNMjAwNTAyMDUy\nNjAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE0i55Xns6VEn2Y+DUNgQR3bfbLz40B99srq5rKF+C\n8QH6A3lDMtN7dFJQvddZprSxNaScaA81sJzXmygJ/9qBzaNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNyNLIJjiV6+GK97W/DO7a30\nLbUbMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0cAMEQCIGe2ilQJ9PNaPueLFL9Joc9zaV7Eq0krEX1wBR8c\nWCE/AiBdDgWhzztwAtdsV7/y6NXkmvCcJQvgtmz/ga+7gcolIQ==\n-----END CERTIFICATE-----\n"}}} + +``` +Enroll the normal user: +``` +$ node enrollUser.js +``` +If you received the following error: ``Failed to register: Error: fabric-ca request register failed with errors [[{"code":20,"message":"Authentication failure"}]]`` + +remove the keys from hfc-key-store: ``rm -rf ~/.hfc-key-store/* +`` +After this, re-enroll the admin and normal user. Successful completion of ``$node enrollUser.js`` should give the following output: + +```markdown +Successfully loaded admin from persistence +Successfully registered user1 - secret:MPGQrXnJyxjc +Successfully enrolled member user "user1" +User1 was successfully registered and enrolled and is ready to intreact with the fabric network + +``` + + +*Start the server and browse to ``localhost:8000`` in order to interact with the POC network and ledger* +``` +$ node server.js >&logs/server_logs.txt +``` + +```markdown +NOTE: All console logs will be stored in the ``logs`` directory. +``` + +### Transaction flow in the network +![transaction flow diagram](transaction_flow.PNG) + +### Stakeholders in the network +1. Farmer -> invokes - recordProduce() +2. Buyer -> invokes - queryProduce() / changeProduceOwner() +3. Regulator -> invokes - queryProduce() / queryAllProduce() diff --git a/supply-chain-client/client/app.js b/supply-chain-client/client/app.js new file mode 100644 index 00000000..42e7d371 --- /dev/null +++ b/supply-chain-client/client/app.js @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +'use strict'; + +var app = angular.module('application', []); + +// Angular Controller +app.controller('appController', function($scope, appFactory){ + + $("#success_holder").hide(); + $("#success_create").hide(); + $("#error_holder").hide(); + $("#error_query").hide(); + + $scope.queryAllProduce = function(){ + + appFactory.queryAllProduce(function(data){ + $scope.all_produce = data; + }); + } + + $scope.queryProduce = function(){ + + var id = $scope.produce_id; + + appFactory.queryProduce(id, function(data){ + $scope.query_produce = data; + + if ($scope.query_produce == "Could not locate produce"){ + console.log() + $("#error_query").show(); + } else{ + $("#error_query").hide(); + } + }); + } + + $scope.recordProduce = function(){ + + appFactory.recordProduce($scope.produce, function(data){ + + $scope.create_produce = data; + $("#success_create").show(); + }); + } + + $scope.changeHolder = function(){ + + appFactory.changeHolder($scope.holder, function(data){ + $scope.change_holder = data; + if ($scope.change_holder == "Error: no produce found"){ + $("#error_holder").show(); + $("#success_holder").hide(); + } else{ + $("#success_holder").show(); + $("#error_holder").hide(); + } + }); + } + +}); + +// Angular Factory +app.factory('appFactory', function($http){ + + var factory = {}; + + factory.queryAllProduce = function(callback){ + + $http.get('/get_all_produce/').success(function(output){ + callback(output) + }); + } + + factory.queryProduce = function(id, callback){ + $http.get('/get_produce/'+id).success(function(output){ + callback(output) + }); + } + + factory.recordProduce = function(data, callback){ + console.log(data) + var produce = data.assetID + "-" + data.color + "-" + data.size + "-" + data.appraisedValue + "-" + data.owner; + + $http.get('/add_produce/'+produce).success(function(output){ + callback(output) + }); + } + + factory.changeHolder = function(data, callback){ + + var holder = data.assetID + "-" + data.name; + + $http.get('/change_holder/'+holder).success(function(output){ + callback(output) + }); + } + + return factory; +}); + + diff --git a/supply-chain-client/client/favicon.png b/supply-chain-client/client/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..823bb5e933505c321926bee773fc3bcc2c3d350a GIT binary patch literal 896 zcmV-`1AqL9P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv00000008+zyMF)x010qNS#tmY3labT3lag+-G2N400QJmL_t(I%cYawZ&P&` z#y{tKYPYVZr?oS5Ym=>I9c*XaCI}k?BXXmJASQsx0=GtQH6igw`~!^s4;udf^+GX5 z8Hw5f>cWWaFp*)~aoTR@Y&&gF(w=oYJzgvu61?-ddT!oN-n`HAN}lI={15r|OugRV zgAYIA?YG_pKoWwE>mvJP9UBKN)g zD3)d6I1Y(K9Dq)p9V`*ZFt9{(7*?mb1m=bGydgS@t6z(qG&_67ha?y-dB)VCwj>$2|b3)oSQ^ z8AVZOIW1xbV&w99Y}+Q0h@*{;($&?)mtTF&*;ij77z{r73z0|!T`#k-vB9QgAtb@f ziD~?PKelc2)6c(9tJT@wZto92vB93sp1Z*N?_DDj4kP;h literal 0 HcmV?d00001 diff --git a/supply-chain-client/client/index.html b/supply-chain-client/client/index.html new file mode 100644 index 00000000..9e2e21e2 --- /dev/null +++ b/supply-chain-client/client/index.html @@ -0,0 +1,149 @@ + + + + + + Hyperledger Fabric v1.4 Transactions POC + + + + + + + + + + + + +
+
Hyperledger Fabric v1.4 Transactions POC
+
+ +
+
+ + + + +

+
+ + + + + + + + + + + + + + + + + + +
IDColorSizePriceOwner
{{produce.ID}}{{produce.Color}}{{produce.Size}}{{produce.AppraisedValue}}{{produce.Owner}}
+ +
+
+
Error: Please enter a valid Id
+ + Enter a ID number: + +
+ + + + + + + + + + + + + + + + + + +
IDColorSizePriceOwner
{{query_produce.ID}}{{query_produce.Color}}{{query_produce.Size}}{{query_produce.AppraisedValue}}{{query_produce.Owner}}
+ +
+ +
Successfully added to Ledger!
+
+ Fabric id: + Color: + Size: + Price: + Owner name: + +
+ +
+
+
Successfully updated Ledger! Tx ID: {{change_holder}}
+
Error: Please enter a valid Product Id
+ Enter a ID of the product to transfer ownership: + Enter name of new owner: + +
+ +
+ + + + \ No newline at end of file diff --git a/supply-chain-client/client/transaction_flow.PNG b/supply-chain-client/client/transaction_flow.PNG new file mode 100644 index 0000000000000000000000000000000000000000..82622d89bffa195158f333bc3bc9965d4be87e8c GIT binary patch literal 18970 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 WvO4+8`07@WyF6X}T-G@yGywpG#3F?N literal 0 HcmV?d00001 diff --git a/supply-chain-client/enrollAdmin.js b/supply-chain-client/enrollAdmin.js new file mode 100644 index 00000000..5f24d9e0 --- /dev/null +++ b/supply-chain-client/enrollAdmin.js @@ -0,0 +1,73 @@ +'use strict'; +/* +* SPDX-License-Identifier: Apache-2.0 +*/ +/* + * Chaincode Invoke + */ + +var Fabric_Client = require('fabric-client'); +var Fabric_CA_Client = require('fabric-ca-client'); + +var path = require('path'); +var util = require('util'); +var os = require('os'); + +// +var fabric_client = new Fabric_Client(); +var fabric_ca_client = null; +var admin_user = null; +var member_user = null; +var store_path = path.join(os.homedir(), '.hfc-key-store'); +console.log(' Store path:'+store_path); + +// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting +Fabric_Client.newDefaultKeyValueStore({ path: store_path +}).then((state_store) => { + // assign the store to the fabric client + fabric_client.setStateStore(state_store); + var crypto_suite = Fabric_Client.newCryptoSuite(); + // use the same location for the state store (where the users' certificate are kept) + // and the crypto store (where the users' keys are kept) + var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path}); + crypto_suite.setCryptoKeyStore(crypto_store); + fabric_client.setCryptoSuite(crypto_suite); + var tlsOptions = { + trustedRoots: [], + verify: false + }; + // be sure to change the http to https when the CA is running TLS enabled + fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', tlsOptions , 'ca.example.com', crypto_suite); + + // first check to see if the admin is already enrolled + return fabric_client.getUserContext('admin', true); +}).then((user_from_store) => { + if (user_from_store && user_from_store.isEnrolled()) { + console.log('Successfully loaded admin from persistence'); + admin_user = user_from_store; + return null; + } else { + // need to enroll it with CA server + return fabric_ca_client.enroll({ + enrollmentID: 'admin', + enrollmentSecret: 'adminpw' + }).then((enrollment) => { + console.log('Successfully enrolled admin user "admin"'); + return fabric_client.createUser( + {username: 'admin', + mspid: 'Org1MSP', + cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate } + }); + }).then((user) => { + admin_user = user; + return fabric_client.setUserContext(admin_user); + }).catch((err) => { + console.error('Failed to enroll and persist admin. Error: ' + err.stack ? err.stack : err); + throw new Error('Failed to enroll admin'); + }); + } +}).then(() => { + console.log('Assigned the admin user to the fabric client ::' + admin_user.toString()); +}).catch((err) => { + console.error('Failed to enroll admin: ' + err); +}); \ No newline at end of file diff --git a/supply-chain-client/enrollUser.js b/supply-chain-client/enrollUser.js new file mode 100644 index 00000000..7d318610 --- /dev/null +++ b/supply-chain-client/enrollUser.js @@ -0,0 +1,84 @@ +'use strict'; +/* +* SPDX-License-Identifier: Apache-2.0 +*/ +/* + * Chaincode Invoke + +This code is based on code written by the Hyperledger Fabric community. + Original code can be found here: https://gerrit.hyperledger.org/r/#/c/14395/4/fabcar/registerUser.js + + */ + +var Fabric_Client = require('fabric-client'); +var Fabric_CA_Client = require('fabric-ca-client'); + +var path = require('path'); +var util = require('util'); +var os = require('os'); + +// +var fabric_client = new Fabric_Client(); +var fabric_ca_client = null; +var admin_user = null; +var member_user = null; +var store_path = path.join(os.homedir(), '.hfc-key-store'); +console.log(' Store path:'+store_path); + +// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting +Fabric_Client.newDefaultKeyValueStore({ path: store_path +}).then((state_store) => { + // assign the store to the fabric client + fabric_client.setStateStore(state_store); + var crypto_suite = Fabric_Client.newCryptoSuite(); + // use the same location for the state store (where the users' certificate are kept) + // and the crypto store (where the users' keys are kept) + var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path}); + crypto_suite.setCryptoKeyStore(crypto_store); + fabric_client.setCryptoSuite(crypto_suite); + var tlsOptions = { + trustedRoots: [], + verify: false + }; + // be sure to change the http to https when the CA is running TLS enabled + fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', null , '', crypto_suite); + + // first check to see if the admin is already enrolled + return fabric_client.getUserContext('admin', true); +}).then((user_from_store) => { + if (user_from_store && user_from_store.isEnrolled()) { + console.log('Successfully loaded admin from persistence'); + admin_user = user_from_store; + } else { + throw new Error('Failed to get admin.... run registerAdmin.js'); + } + + // at this point we should have the admin user + // first need to register the user with the CA server + return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1'}, admin_user); +}).then((secret) => { + // next we need to enroll the user with CA server + console.log('Successfully registered user1 - secret:'+ secret); + + return fabric_ca_client.enroll({enrollmentID: 'user1', enrollmentSecret: secret}); +}).then((enrollment) => { + console.log('Successfully enrolled member user "user1" '); + return fabric_client.createUser( + {username: 'user1', + mspid: 'Org1MSP', + cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate } + }); +}).then((user) => { + member_user = user; + + return fabric_client.setUserContext(member_user); +}).then(()=>{ + console.log('User1 was successfully registered and enrolled and is ready to intreact with the fabric network'); + +}).catch((err) => { + console.error('Failed to register: ' + err); + if(err.toString().indexOf('Authorization') > -1) { + console.error('Authorization failures may be caused by having admin credentials from a previous CA instance.\n' + + 'Try again after deleting the contents of the store directory '+store_path); + } +}); \ No newline at end of file diff --git a/supply-chain-client/invoker.js b/supply-chain-client/invoker.js new file mode 100644 index 00000000..ef8204de --- /dev/null +++ b/supply-chain-client/invoker.js @@ -0,0 +1,104 @@ +const { Gateway, Wallets } = require('fabric-network'); +const FabricCAServices = require('fabric-ca-client'); +const path = require('path'); +const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('./CAUtil.js'); +const { buildCCPOrg1, buildWallet } = require('./AppUtil.js'); + +const channelName = 'mychannel'; +const chaincodeName = 'basic'; +const mspOrg1 = 'Org1MSP'; +const walletPath = path.join(__dirname, 'wallet'); +const org1UserId = 'appUser'; + +function prettyJSONString(inputString) { + return JSON.stringify(JSON.parse(inputString), null, 2); +} + +class FabricSampleService { + constructor(){ + + } + + async init() { + try { + const ccp = buildCCPOrg1(); + + // build an instance of the fabric ca services client based on + // the information in the network configuration + const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com'); + + // setup the wallet to hold the credentials of the application user + const wallet = await buildWallet(Wallets, walletPath); + + // in a real application this would be done on an administrative flow, and only once + await enrollAdmin(caClient, wallet, mspOrg1); + + // in a real application this would be done only when a new user was required to be added + // and would be part of an administrative flow + await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1'); + + // Create a new gateway instance for interacting with the fabric network. + // In a real application this would be done as the backend server session is setup for + // a user that has been verified. + const gateway = new Gateway(); + + // setup the gateway instance + // The user will now be able to create connections to the fabric network and be able to + // submit transactions and query. All transactions submitted by this gateway will be + // signed by this user using the credentials stored in the wallet. + await gateway.connect(ccp, { + wallet, + identity: org1UserId, + discovery: { enabled: true, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally + }); + + // Build a network instance based on the channel where the smart contract is deployed + const network = await gateway.getNetwork(channelName); + + // Get the contract from the network. + this.contract = network.getContract(chaincodeName); + + console.log('Adding initial inventory to Ledger'); + await this.contract.submitTransaction('InitLedger'); + console.log('Done, applicaiton Ready!'); + + } catch (error) { + console.error(`******** FAILED to startup the FabicSampleService: ${error}`); + } + } + + async get_fabric(id) { + console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID'); + let result = await this.contract.evaluateTransaction('ReadAsset', id); + console.log(`*** Result: ${prettyJSONString(result.toString())}`); + return JSON.parse(result.toString()) + } + + async get_all_fabric() { + console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger'); + let result = await this.contract.evaluateTransaction('GetAllAssets'); + console.log(`*** Result: ${prettyJSONString(result.toString())}`); + return JSON.parse(result.toString()) + } + + async add_fabric(fabric) { + console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments'); + let result = await this.contract.submitTransaction('CreateAsset', fabric.ID, fabric.Color, fabric.Size, fabric.Owner, fabric.AppraisedValue); + console.log('*** Result: committed'); + if (`${result}` !== '') { + console.log(`*** Result: ${prettyJSONString(result.toString())}`); + return JSON.parse(result.toString()) + } + return {} + + } + + async change_owner(id, newowner) { + console.log('\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom'); + let result = await this.contract.submitTransaction('TransferAsset', id, newowner); + console.log(result.toString()) + return {} + } +} + +module.exports = FabricSampleService; \ No newline at end of file diff --git a/supply-chain-client/package.json b/supply-chain-client/package.json new file mode 100644 index 00000000..a4108c4c --- /dev/null +++ b/supply-chain-client/package.json @@ -0,0 +1,26 @@ +{ + "name": "Supply-Chain-app", + "version": "1.0.0", + "description": "Hyperledger Fabric POC", + "scripts": { + "start": "nodemon -L server.js" + }, + "dependencies": { + "angular": "^1.7.8", + "body-parser": "latest", + "ejs": "latest", + "express": "latest", + "fabric-ca-client": "^2.2.4", + "fabric-network": "^2.2.4" + }, + "devDependencies": { + "nodemon": "^2.0.15" + }, + "license": "Apache-2.0", + "keywords": [ + "Hyperledger", + "Fabric", + "Sample", + "Application" + ] +} diff --git a/supply-chain-client/routes.js b/supply-chain-client/routes.js new file mode 100644 index 00000000..e4938777 --- /dev/null +++ b/supply-chain-client/routes.js @@ -0,0 +1,27 @@ +const FabricSampleService = require("./invoker.js"); + +module.exports = async function(app){ + let fss = new FabricSampleService() + await fss.init() + + app.get('/get_produce/:id', async function(req, res){ + console.log(req.params) + asset = await fss.get_fabric(req.params.id); + + res.json(asset) + }); + app.get('/add_produce/:produce', async function(req, res){ + p = req.params.produce + pparts = p.split('-') + result = await fss.add_fabric({ID: pparts[0], Color: pparts[1], Size: pparts[2], AppraisedValue:pparts[3], Owner: pparts[4]}); + res.json(result) + }); + app.get('/get_all_produce', async function(req, res){ + res.json(await fss.get_all_fabric()) + }); + app.get('/change_holder/:holder', async function(req, res){ + p = req.params.holder + pparts = p.split('-') + res.json(await fss.change_owner(pparts[0], pparts[1])); + }); +} diff --git a/supply-chain-client/server.js b/supply-chain-client/server.js new file mode 100644 index 00000000..308b8a56 --- /dev/null +++ b/supply-chain-client/server.js @@ -0,0 +1,34 @@ +//SPDX-License-Identifier: Apache-2.0 + +// nodejs server setup + +// call the packages we need +var express = require('express'); // call express +var app = express(); // define our app using express +var bodyParser = require('body-parser'); +var path = require('path'); + +// Load all of our middleware +// configure app to use bodyParser() +// this will let us get the data from a POST +// app.use(express.static(__dirname + '/client')); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); + +// instantiate the app +var app = express(); + +// this line requires and runs the code from our routes.js file and passes it app +require('./routes.js')(app); + +// set up a static file server that points to the "client" directory +app.use(express.static(path.join(__dirname, './client'))); + +// Save our port +var port = process.env.PORT || 8000; + +// Start the server and listen on port +app.listen(port,function(){ + console.log("Server is now Live on port: " + port); +}); + diff --git a/supply-chain-client/transaction_flow.PNG b/supply-chain-client/transaction_flow.PNG new file mode 100644 index 0000000000000000000000000000000000000000..82622d89bffa195158f333bc3bc9965d4be87e8c GIT binary patch literal 18970 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 WvO4+8`07@WyF6X}T-G@yGywpG#3F?N literal 0 HcmV?d00001