diff --git a/fabcar/javascript-low-level/invoke.js b/fabcar/javascript-low-level/invoke.js index 2232303ade..65e067a778 100644 --- a/fabcar/javascript-low-level/invoke.js +++ b/fabcar/javascript-low-level/invoke.js @@ -8,118 +8,158 @@ * Chaincode Invoke */ -var Fabric_Client = require('fabric-client'); -var path = require('path'); -var util = require('util'); -var os = require('os'); - -// -var fabric_client = new Fabric_Client(); - -// setup the fabric network -var channel = fabric_client.newChannel('mychannel'); -var peer = fabric_client.newPeer('grpc://localhost:7051'); -channel.addPeer(peer); -var order = fabric_client.newOrderer('grpc://localhost:7050') -channel.addOrderer(order); - -// -var member_user = null; -var store_path = path.join(__dirname, 'hfc-key-store'); -console.log('Store path:'+store_path); -var tx_id = null; - -// 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); - - // get the enrolled user from persistence, this user will sign all requests - return fabric_client.getUserContext('user1', true); -}).then((user_from_store) => { - if (user_from_store && user_from_store.isEnrolled()) { - console.log('Successfully loaded user1 from persistence'); - member_user = user_from_store; - } else { - throw new Error('Failed to get user1.... run registerUser.js'); - } +const Fabric_Client = require('fabric-client'); +const path = require('path'); +const util = require('util'); +const os = require('os'); + +invoke(); + +async function invoke() { + console.log('\n\n --- invoke.js - start'); + try { + console.log('Setting up client side network objects'); + // fabric client instance + // starting point for all interactions with the fabric network + const fabric_client = new Fabric_Client(); + + // setup the fabric network + // -- channel instance to represent the ledger named "mychannel" + const channel = fabric_client.newChannel('mychannel'); + console.log('Created client side object to represent the channel'); + // -- peer instance to represent a peer on the channel + const peer = fabric_client.newPeer('grpc://localhost:7051'); + console.log('Created client side object to represent the peer'); + // -- orderer instance to reprsent the channel's orderer + const orderer = fabric_client.newOrderer('grpc://localhost:7050') + console.log('Created client side object to represent the orderer'); + + // This sample application uses a file based key value stores to hold + // the user information and credentials. These are the same stores as used + // by the 'registerUser.js' sample code + const member_user = null; + const store_path = path.join(__dirname, 'hfc-key-store'); + console.log('Setting up the user store at path:'+store_path); + // create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting + const state_store = await Fabric_Client.newDefaultKeyValueStore({ path: store_path}); + // assign the store to the fabric client + fabric_client.setStateStore(state_store); + const 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) + const crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path}); + crypto_suite.setCryptoKeyStore(crypto_store); + fabric_client.setCryptoSuite(crypto_suite); + + // get the enrolled user from persistence and assign to the client instance + // this user will sign all requests for the fabric network + const user = await fabric_client.getUserContext('user1', true); + if (user && user.isEnrolled()) { + console.log('Successfully loaded "user1" from user store'); + } else { + throw new Error('\n\nFailed to get user1.... run registerUser.js'); + } + + console.log('Successfully setup client side'); + console.log('\n\nStart invoke processing'); + + // get a transaction id object based on the current user assigned to fabric client + // Transaction ID objects contain more then just a transaction ID, also includes + // a nonce value and if built from the client's admin user. + const tx_id = fabric_client.newTransactionID(); + console.log(util.format("\nCreated a transaction ID: %s", tx_id.getTransactionID())); + + // The fabcar chaincode is able to perform a few functions + // 'createCar' - requires 5 args, ex: args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'] + // 'changeCarOwner' - requires 2 args , ex: args: ['CAR10', 'Dave'] + const proposal_request = { + targets: [peer], + chaincodeId: 'fabcar', + fcn: 'createCar', + args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'], + chainId: 'mychannel', + txId: tx_id + }; - // get a transaction id object based on the current user assigned to fabric client - tx_id = fabric_client.newTransactionID(); - console.log("Assigning transaction_id: ", tx_id._transaction_id); - - // createCar chaincode function - requires 5 args, ex: args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'], - // changeCarOwner chaincode function - requires 2 args , ex: args: ['CAR10', 'Dave'], - // must send the proposal to endorsing peers - var request = { - //targets: let default to the peer assigned to the client - chaincodeId: 'fabcar', - fcn: '', - args: [''], - chainId: 'mychannel', - txId: tx_id - }; - - // send the transaction proposal to the peers - return channel.sendTransactionProposal(request); -}).then((results) => { - var proposalResponses = results[0]; - var proposal = results[1]; - let isProposalGood = false; - if (proposalResponses && proposalResponses[0].response && - proposalResponses[0].response.status === 200) { - isProposalGood = true; - console.log('Transaction proposal was good'); + // notice the proposal_request has the peer defined in the 'targets' attribute + + // Send the transaction proposal to the endorsing peers. + // The peers will run the function requested with the arguments supplied + // based on the current state of the ledger. If the chaincode successfully + // runs this simulation it will return a postive result in the endorsement. + const endorsement_results = await channel.sendTransactionProposal(proposal_request); + + // The results will contain a few different items + // first is the actual endorsements by the peers, these will be the responses + // from the peers. In our sammple there will only be one results since + // only sent the proposal to one peer. + // second is the proposal that was sent to the peers to be endorsed. This will + // be needed later when the endorsements are sent to the orderer. + const proposalResponses = endorsement_results[0]; + const proposal = endorsement_results[1]; + + // check the results to decide if we should send the endorsment to be orderered + if (proposalResponses[0] instanceof Error) { + console.error('Failed to send Proposal. Received an error :: ' + proposalResponses[0].toString()); + throw proposalResponses[0]; + } else if (proposalResponses[0].response && proposalResponses[0].response.status === 200) { + console.log(util.format( + 'Successfully sent Proposal and received response: Status - %s', + proposalResponses[0].response.status)); } else { - console.error('Transaction proposal was bad'); + const error_message = util.format('Invoke chaincode proposal:: %j', proposalResponses[i]); + console.error(error_message); + throw new Error(error_message); } - if (isProposalGood) { - console.log(util.format( - 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"', - proposalResponses[0].response.status, proposalResponses[0].response.message)); - // build up the request for the orderer to have the transaction committed - var request = { + // The proposal was good, now send to the orderer to have the transaction + // committed. + + const commit_request = { + orderer: orderer, proposalResponses: proposalResponses, proposal: proposal }; - // set the transaction listener and set a timeout of 30 sec - // if the transaction did not get committed within the timeout period, - // report a TIMEOUT status - var transaction_id_string = tx_id.getTransactionID(); //Get the transaction ID string to be used by the event processing - var promises = []; + //Get the transaction ID string to be used by the event processing + const transaction_id_string = tx_id.getTransactionID(); + + // create an array to hold on the asynchronous calls to be executed at the + // same time + const promises = []; - var sendPromise = channel.sendTransaction(request); - promises.push(sendPromise); //we want the send transaction first, so that we know where to check status + // this will send the proposal to the orderer during the execuction of + // the promise 'all' call. + const sendPromise = channel.sendTransaction(commit_request); + //we want the send transaction first, so that we know where to check status + promises.push(sendPromise); - // get an eventhub once the fabric client has a user assigned. The user - // is required bacause the event registration must be signed + // get an event hub that is associated with our peer let event_hub = channel.newChannelEventHub(peer); - // using resolve the promise so that result status may be processed - // under the then clause rather than having the catch clause process - // the status + // create the asynchronous work item let txPromise = new Promise((resolve, reject) => { + // setup a timeout of 30 seconds + // if the transaction does not get committed within the timeout period, + // report TIMEOUT as the status. This is an application timeout and is a + // good idea to not let the listener run forever. let handle = setTimeout(() => { event_hub.unregisterTxEvent(transaction_id_string); event_hub.disconnect(); - resolve({event_status : 'TIMEOUT'}); //we could use reject(new Error('Trnasaction did not complete within 30 seconds')); - }, 3000); + resolve({event_status : 'TIMEOUT'}); + }, 30000); + + // this will register a listener with the event hub. THe included callbacks + // will be called once transaction status is received by the event hub or + // an error connection arises on the connection. event_hub.registerTxEvent(transaction_id_string, (tx, code) => { - // this is the callback for transaction event status - // first some clean up of event listener + // this first callback is for transaction event status + + // callback has been called, so we can stop the timer defined above clearTimeout(handle); // now let the application know what happened - var return_status = {event_status : code, tx_id : transaction_id_string}; + const return_status = {event_status : code, tx_id : transaction_id_string}; if (code !== 'VALID') { console.error('The transaction was invalid, code = ' + code); resolve(return_status); // we could use reject(new Error('Problem with the tranaction, event status ::'+code)); @@ -133,30 +173,46 @@ Fabric_Client.newDefaultKeyValueStore({ path: store_path }, {disconnect: true} //disconnect when complete ); - event_hub.connect(); + // now that we have a protective timer running and the listener registered, + // have the event hub instance connect with the peer's event service + event_hub.connect(); + console.log('Registered transaction listener with the peer event service for transaction ID:'+ transaction_id_string); }); + + // set the event work with the orderer work so they may be run at the same time promises.push(txPromise); - return Promise.all(promises); - } else { - console.error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); - throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); - } -}).then((results) => { - console.log('Send transaction promise and event listener promise have completed'); - // check the results in the order the promises were added to the promise all list - if (results && results[0] && results[0].status === 'SUCCESS') { - console.log('Successfully sent transaction to the orderer.'); - } else { - console.error('Failed to order the transaction. Error code: ' + results[0].status); - } + // now execute both pieces of work and wait for both to complete + console.log('Sending endorsed transaction to the orderer'); + const results = await Promise.all(promises); + + // since we added the orderer work first, that will be the first result on + // the list of results + // success from the orderer only means that it has accepted the transaction + // you must check the event status or the ledger to if the transaction was + // committed + if (results[0].status === 'SUCCESS') { + console.log('Successfully sent transaction to the orderer'); + } else { + const message = util.format('Failed to order the transaction. Error code: %s', results[0].status); + console.error(message); + throw new Error(message); + } - if(results && results[1] && results[1].event_status === 'VALID') { - console.log('Successfully committed the change to the ledger by the peer'); - } else { - console.log('Transaction failed to be committed to the ledger due to ::'+results[1].event_status); + if (results[1] instanceof Error) { + console.error(message); + throw new Error(message); + } else if (results[1].event_status === 'VALID') { + console.log('Successfully committed the change to the ledger by the peer'); + console.log('\n\n - try running "node query.js" to see the results'); + } else { + const message = util.format('Transaction failed to be committed to the ledger due to : %s', results[1].event_status) + console.error(message); + throw new Error(message); + } + } catch(error) { + console.log('Unable to invoke ::'+ error.toString()); } -}).catch((err) => { - console.error('Failed to invoke successfully :: ' + err); -}); + console.log('\n\n --- invoke.js - end'); +};