Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit 74aaa9a

Browse files
committed
NodeSDK convert to new protos and add invoke and query
Updates to the NodeSDK to pull in the new GRPC protobuf definitions. Implement the invoke and query capabilities. Update the chaincode sample to allow for the new only 'invoke' call. Add and update tests. Change-Id: Ic314ef2b8f5f9e3722bde3f6e1969ca9048a0934 Signed-off-by: Bret Harrison <beharrison@nc.rr.com>
1 parent cf80346 commit 74aaa9a

File tree

10 files changed

+782
-127
lines changed

10 files changed

+782
-127
lines changed

lib/Member.js

Lines changed: 176 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ var grpc = require('grpc');
2525

2626
var _ccProto = grpc.load(__dirname + '/protos/chaincode.proto').protos;
2727
var _ccProposalProto = grpc.load(__dirname + '/protos/chaincode_proposal.proto').protos;
28+
var _ccTransProto = grpc.load(__dirname + '/protos/chaincode_transaction.proto').protos;
29+
var _transProto = grpc.load(__dirname + '/protos/fabric_transaction.proto').protos;
2830
var _headerProto = grpc.load(__dirname + '/protos/fabric_transaction_header.proto').protos;
2931
var _proposalProto = grpc.load(__dirname + '/protos/fabric_proposal.proto').protos;
3032
var _responseProto = grpc.load(__dirname + '/protos/fabric_proposal_response.proto').protos;
@@ -332,54 +334,82 @@ var Member = class {
332334

333335
/**
334336
* Sends the orderer an endorsed proposal.
337+
* The caller must use the proposal response returned from the endorser along
338+
* with the original proposal request sent to the endorser.
335339
*
336-
* @param {Object} request An object containing the data:
337-
* TODO explain data object
338-
* @returns Promise for the sendTransaction
340+
* @param {ProposalResponse} proposalResponse - A ProposalResponse object containing
341+
* the response from the endorsement (see fabric_proposal_response.proto)
342+
* @param {Proposal} chaincodeProposal - A Proposal object containing the original
343+
* request for endorsement (see fabric_proposal.proto)
344+
* @returns Promise for an acknowledgement from the orderer of successfully submitted transaction
339345
*/
340-
sendTransaction(data) {
346+
sendTransaction(proposalResponse, chaincodeProposal) {
347+
logger.debug('Member.sendTransaction - start :: chain '+this._chain);
348+
341349
// Verify that data is being passed in
342-
if (!data) {
343-
logger.error('Member.sendTransaction - input data missing');
344-
return Promise.reject(new Error('missing data in broadcast request'));
350+
if (!proposalResponse) {
351+
logger.error('Member.sendTransaction - input proposalResponse missing');
352+
return Promise.reject(new Error('Missing proposalResponse object parameter'));
353+
}
354+
if (!chaincodeProposal) {
355+
logger.error('Member.sendTransaction - input chaincodeProposal missing');
356+
return Promise.reject(new Error('Missing chaincodeProposal object parameter'));
345357
}
346358
// verify that we have an orderer configured
347359
if(!this._chain.getOrderer()) {
348360
logger.error('Member.sendTransaction - no orderer defined');
349361
return Promise.reject(new Error('no Orderer defined'));
350362
}
351363

352-
logger.debug('Member.sendTransaction - chain ::'+this._chain);
364+
//logger.debug('Member.sendTransaction - proposalResponse %j', proposalResponse);
365+
//logger.debug('Member.sendTransaction - chaincodePropsoal %j', chaincodeProposal);
366+
367+
var endorsements = [];
368+
endorsements.push(proposalResponse.endorsement);
369+
var chaincodeEndorsedAction = new _ccTransProto.ChaincodeEndorsedAction();
370+
chaincodeEndorsedAction.setProposalResponsePayload(proposalResponse.payload);
371+
chaincodeEndorsedAction.setEndorsements(endorsements);
372+
373+
var chaincodeActionPayload = new _ccTransProto.ChaincodeActionPayload();
374+
chaincodeActionPayload.setAction(chaincodeEndorsedAction);
375+
chaincodeActionPayload.setChaincodeProposalPayload(chaincodeProposal.payload);
376+
377+
var transactionAction = new _transProto.TransactionAction();
378+
transactionAction.setHeader(chaincodeProposal.header);
379+
transactionAction.setPayload(chaincodeActionPayload.toBuffer());
380+
381+
var transaction2 = new _transProto.Transaction2();
382+
var actions = [];
383+
actions.push(transactionAction);
384+
transaction2.setActions(actions);
353385

354386
var orderer = this._chain.getOrderer();
355-
return orderer.sendBroadcast(data);
387+
return orderer.sendBroadcast(transaction2.toBuffer());
356388
}
357389

358390
/**
359391
* Sends a deployment proposal to an endorser.
360392
*
361393
* @param {Object} request An object containing the following fields:
362-
* endorserUrl: Peer URL
394+
* target : Endorsing Peer Object
363395
* chaincodePath : String
364396
* fcn : String
365397
* args : Strings
366398
* @returns Promise for a ProposalResponse
367399
*/
368400
sendDeploymentProposal(request) {
369-
if (!request.endorserUrl || request.endorserUrl === '') {
370-
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "endorserUrl"');
371-
return Promise.reject(new Error('missing endorserUrl in Deployment proposal request'));
372-
}
373-
401+
// Verify that chaincodePath is being passed
374402
if (!request.chaincodePath || request.chaincodePath === '') {
375403
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "chaincodePath"');
376404
return Promise.reject(new Error('missing chaincodePath in Deployment proposal request'));
377405
}
378-
379-
if (!request.fcn || request.fnc === '') {
380-
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "fcn" for the target function to call during chaincode initialization');
381-
return Promise.reject(new Error('missing fcn in Deployment proposal request'));
406+
// verify that the caller has included a peer object
407+
if(!request.target) {
408+
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "target" object');
409+
return Promise.reject(new Error('Missing "target" for the endorsing peer object in the Deployment proposal request'));
382410
}
411+
let peer = request.target;
412+
var chaincode_id;
383413

384414
// args is optional because some chaincode may not need any input parameters during initialization
385415
if (!request.args) {
@@ -391,6 +421,7 @@ var Member = class {
391421
function(data) {
392422
var targzFilePath = data[0];
393423
var hash = data[1];
424+
chaincode_id = hash;
394425

395426
logger.debug('Successfully generated chaincode deploy archive and name hash (%s)', hash);
396427

@@ -455,11 +486,11 @@ var Member = class {
455486
payload: payload.toBuffer()
456487
};
457488

458-
let peer = new Peer(request.endorserUrl);
459489
return peer.sendProposal(proposal)
460490
.then(
461-
function(data) {
462-
resolve(data);
491+
function(response) {
492+
response.chaincodeId = chaincode_id;
493+
resolve([response, proposal]);
463494
}
464495
);
465496
}
@@ -468,6 +499,128 @@ var Member = class {
468499
}
469500
).catch(
470501
function(err) {
502+
logger.error('Failed Deployment Proposal. Error: %s', err.stack ? err.stack : err);
503+
return Promise.reject(err);
504+
}
505+
);
506+
}
507+
508+
/**
509+
* Sends a transaction proposal to an endorsing peer.
510+
*
511+
* @param {Object} request:
512+
* target : {Object} Endorsing Peer object as the target of the request
513+
* chaincodeId : {String} The id of the chaincode to perform the transaction proposal
514+
* args : {Array} Arguments specific to the chaincode 'innvoke'
515+
* @returns Promise for a ProposalResponse
516+
*/
517+
sendTransactionProposal(request) {
518+
logger.debug('Member.sendTransactionProposal - start');
519+
520+
// verify that the caller has included a peer object
521+
if(!request.target) {
522+
logger.error('Missing "target" endorser peer object in the Transaction proposal request');
523+
return Promise.reject(new Error('Missing "target" for endorser peer object in the Transaction proposal request'));
524+
}
525+
526+
if(!request.chaincodeId) {
527+
logger.error('Missing chaincode ID in the Transaction proposal request');
528+
return Promise.reject(new Error('Missing chaincode ID in the Transaction proposal request'));
529+
}
530+
531+
// args is not optional because we need for transaction to execute
532+
if (!request.args) {
533+
logger.error('Missing arguments in Transaction proposal request');
534+
return Promise.reject(new Error('Missing arguments in Transaction proposal request'));
535+
}
536+
537+
var args = [];
538+
// leaving this for now... but this call is always an invoke and we are not telling caller to include 'fcn' any longer
539+
args.push(Buffer.from(request.fcn ? request.fcn : 'invoke', 'utf8'));
540+
logger.debug('Member.sendTransactionProposal - adding function arg:%s', request.fcn ? request.fcn : 'invoke');
541+
542+
for (let i=0; i<request.args.length; i++) {
543+
args.push(Buffer.from(request.args[i], 'utf8'));
544+
logger.debug('Member.sendTransactionProposal - adding arg:%s', request.args[i]);
545+
}
546+
547+
let invokeSpec = {
548+
type: _ccProto.ChaincodeSpec.Type.GOLANG,
549+
chaincodeID: {
550+
name: request.chaincodeId
551+
},
552+
ctorMsg: {
553+
args: args
554+
}
555+
};
556+
557+
// construct the ChaincodeInvocationSpec
558+
let cciSpec = new _ccProto.ChaincodeInvocationSpec();
559+
cciSpec.setChaincodeSpec(invokeSpec);
560+
cciSpec.setIdGenerationAlg('');
561+
562+
// construct the enveloping Proposal object
563+
// - the header part of the proposal
564+
let headerExt = new _ccProposalProto.ChaincodeHeaderExtension();
565+
let header = new _headerProto.Header();
566+
header.setType(_headerProto.Header.Type.CHAINCODE);
567+
header.setExtensions(headerExt.toBuffer());
568+
//header.setChainID()
569+
570+
// - the payload part of the proposal for chaincode deploy is ChaincodeProposalPayload
571+
let payload = new _ccProposalProto.ChaincodeProposalPayload();
572+
payload.setInput(cciSpec.toBuffer());
573+
574+
let proposal = {
575+
header: header.toBuffer(),
576+
payload: payload.toBuffer()
577+
};
578+
579+
let peer = request.target;
580+
return peer.sendProposal(proposal)
581+
.then(
582+
function(response) {
583+
return Promise.resolve([response,proposal]);
584+
}
585+
).catch(
586+
function(err) {
587+
logger.error('Failed Transaction Proposal. Error: %s', err.stack ? err.stack : err);
588+
return Promise.reject(err);
589+
}
590+
);
591+
}
592+
593+
/**
594+
* Sends a proposal to an endorsing peer that will be handled by the chaincode.
595+
* This request will be presented to the chaincode 'invoke' and must understand
596+
* from the arguments that this is a query request. The chaincode must also return
597+
* results in the byte array format and the caller will have to be able to decode
598+
* these results
599+
*
600+
* @param {Object} request:
601+
* target : {Object} Endorsing Peer object as the target of the request
602+
* chaincodeId : {String} The id of the chaincode to perform the query
603+
* args : {Array} Arguments for the 'invoke' function call on the chaincode
604+
* that represent a query invocation
605+
* @returns Promise for a byte array results from the chaincode
606+
*/
607+
queryByChaincode(request) {
608+
logger.debug('Member.sendQueryProposal - start');
609+
610+
return this.sendTransactionProposal(request)
611+
.then(
612+
function(results) {
613+
var response = results[0];
614+
var proposal = results[1];
615+
logger.debug('Member-sendQueryProposal - response %j', response);
616+
if(response.response && response.response.payload) {
617+
return Promise.resolve(response.response.payload);
618+
}
619+
return Promise.reject(new Error('Payload results are missing from the chaincode query'));
620+
}
621+
).catch(
622+
function(err) {
623+
logger.error('Failed Query by chaincode. Error: %s', err.stack ? err.stack : err);
471624
return Promise.reject(err);
472625
}
473626
);
@@ -553,6 +706,7 @@ function packageChaincode(chaincodePath, fcn, args) {
553706
}
554707
).catch(
555708
function(err) {
709+
logger.error('Failed to build chaincode package: %s', err.stack ? err.stack : err);
556710
reject(err);
557711
}
558712
);

lib/Orderer.js

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ var Orderer = class extends Remote {
5656
/**
5757
* Send a BroadcastMessage to the orderer service.
5858
*
59-
* @param {Object} data to be included in the BroadcastMessage
59+
* @param {byte} data to be included in the BroadcastMessage
6060
* see the ./proto/atomicbroadcast/ab.proto
6161
* @returns Promise for a BroadcastResponse
6262
* see the ./proto/atomicbroadcast/ab.proto
@@ -71,41 +71,37 @@ var Orderer = class extends Remote {
7171
}
7272

7373
var self = this;
74-
var data = new Buffer(send_data);
7574

7675
// Build up the broadcast message
77-
// This will be fleshed out we add more functionality and send fully
78-
// structured requests, with all fields filled in.
79-
var _broadcastMessage = {Data: data};
80-
81-
// show some of what we have
82-
logger.debug('Orderer.sendBroadcast - _broadcastMessage = %j', data);
76+
var _broadcastMessage = new _abProto.BroadcastMessage();
77+
_broadcastMessage.setData(send_data);
8378

8479
// Send the endorsed proposals to the peer node (orderer) via grpc
8580
// The rpc specification on the peer side is:
8681
// rpc Broadcast(stream BroadcastMessage) returns (stream BroadcastResponse) {}
8782
return new Promise(function(resolve, reject) {
8883
var broadcast = self._ordererClient.broadcast();
8984

90-
setTimeout(function(){
85+
var broadcast_timeout = setTimeout(function(){
9186
logger.debug('Orderer.sendBroadcast - timed out after:%s', self._request_timeout);
9287
return reject(new Error('REQUEST_TIMEOUT'));
9388
}, self._request_timeout);
9489

9590
broadcast.on('data', function (response) {
9691
logger.debug('Orderer.sendBroadcast - on data response: %j', response);
92+
clearTimeout(broadcast_timeout);
9793

9894
if(response.Status) {
9995
if (response.Status === 'SUCCESS') {
10096
logger.debug('Orderer.sendBroadcast - resolve with %s', response.Status);
10197
return resolve(response);
10298
} else {
103-
logger.debug('Orderer.sendBroadcast - reject with %s', response.Status);
99+
logger.error('Orderer.sendBroadcast - reject with %s', response.Status);
104100
return reject(new Error(response.Status));
105101
}
106102
}
107103
else {
108-
logger.debug('Orderer.sendBroadcast ERROR - reject with invalid response from the orderer');
104+
logger.error('Orderer.sendBroadcast ERROR - reject with invalid response from the orderer');
109105
return reject(new Error('SYSTEM_ERROR'));
110106
}
111107

@@ -120,18 +116,19 @@ var Orderer = class extends Remote {
120116
});
121117

122118
broadcast.on('error', function (err) {
123-
logger.debug('Orderer.sendBroadcast - on error: %j',err);
124119
if(err && err.code) {
125120
if(err.code == 14) {
121+
logger.error('Orderer.sendBroadcast - on error: %j',err.stack ? err.stack : err);
126122
return reject(new Error('SERVICE_UNAVAILABLE'));
127123
}
128124
}
125+
logger.debug('Orderer.sendBroadcast - on error: %j',err.stack ? err.stack : err);
129126
return reject(new Error(err));
130127
});
131128

132129
broadcast.write(_broadcastMessage);
133130
broadcast.end();
134-
logger.debug('Orderer.sendBroadcast - write/end complete');
131+
logger.debug('Orderer.sendBroadcast - sent message');
135132
});
136133
}
137134

lib/Peer.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,13 @@ var Peer = class extends Remote {
5959
// The rpc specification on the peer side is:
6060
// rpc ProcessProposal(Proposal) returns (ProposalResponse) {}
6161
return new Promise(function(resolve, reject) {
62-
self._endorserClient.processProposal(proposal, function(err, response) {
62+
self._endorserClient.processProposal(proposal, function(err, proposalResponse) {
6363
if (err) {
6464
reject(new Error(err));
6565
} else {
66-
if (response) {
67-
logger.info('Received proposal response: code - %s', JSON.stringify(response.response.status));
68-
// return the original proposal payload along with the response, so that
69-
// it can be used to construct the Transaction object
70-
resolve([response, proposal.payload]);
66+
if (proposalResponse) {
67+
logger.info('Received proposal response: code - %s', JSON.stringify(proposalResponse.response));
68+
resolve(proposalResponse);
7169
} else {
7270
logger.error('GRPC client failed to get a proper response from the peer.');
7371
reject(new Error('GRPC client failed to get a proper response from the peer.'));

0 commit comments

Comments
 (0)