Skip to content

Commit

Permalink
NodeSDK convert to new protos and add invoke and query
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
harrisob committed Nov 4, 2016
1 parent cf80346 commit 74aaa9a
Show file tree
Hide file tree
Showing 10 changed files with 782 additions and 127 deletions.
198 changes: 176 additions & 22 deletions lib/Member.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var grpc = require('grpc');

var _ccProto = grpc.load(__dirname + '/protos/chaincode.proto').protos;
var _ccProposalProto = grpc.load(__dirname + '/protos/chaincode_proposal.proto').protos;
var _ccTransProto = grpc.load(__dirname + '/protos/chaincode_transaction.proto').protos;
var _transProto = grpc.load(__dirname + '/protos/fabric_transaction.proto').protos;
var _headerProto = grpc.load(__dirname + '/protos/fabric_transaction_header.proto').protos;
var _proposalProto = grpc.load(__dirname + '/protos/fabric_proposal.proto').protos;
var _responseProto = grpc.load(__dirname + '/protos/fabric_proposal_response.proto').protos;
Expand Down Expand Up @@ -332,54 +334,82 @@ var Member = class {

/**
* Sends the orderer an endorsed proposal.
* The caller must use the proposal response returned from the endorser along
* with the original proposal request sent to the endorser.
*
* @param {Object} request An object containing the data:
* TODO explain data object
* @returns Promise for the sendTransaction
* @param {ProposalResponse} proposalResponse - A ProposalResponse object containing
* the response from the endorsement (see fabric_proposal_response.proto)
* @param {Proposal} chaincodeProposal - A Proposal object containing the original
* request for endorsement (see fabric_proposal.proto)
* @returns Promise for an acknowledgement from the orderer of successfully submitted transaction
*/
sendTransaction(data) {
sendTransaction(proposalResponse, chaincodeProposal) {
logger.debug('Member.sendTransaction - start :: chain '+this._chain);

// Verify that data is being passed in
if (!data) {
logger.error('Member.sendTransaction - input data missing');
return Promise.reject(new Error('missing data in broadcast request'));
if (!proposalResponse) {
logger.error('Member.sendTransaction - input proposalResponse missing');
return Promise.reject(new Error('Missing proposalResponse object parameter'));
}
if (!chaincodeProposal) {
logger.error('Member.sendTransaction - input chaincodeProposal missing');
return Promise.reject(new Error('Missing chaincodeProposal object parameter'));
}
// verify that we have an orderer configured
if(!this._chain.getOrderer()) {
logger.error('Member.sendTransaction - no orderer defined');
return Promise.reject(new Error('no Orderer defined'));
}

logger.debug('Member.sendTransaction - chain ::'+this._chain);
//logger.debug('Member.sendTransaction - proposalResponse %j', proposalResponse);
//logger.debug('Member.sendTransaction - chaincodePropsoal %j', chaincodeProposal);

var endorsements = [];
endorsements.push(proposalResponse.endorsement);
var chaincodeEndorsedAction = new _ccTransProto.ChaincodeEndorsedAction();
chaincodeEndorsedAction.setProposalResponsePayload(proposalResponse.payload);
chaincodeEndorsedAction.setEndorsements(endorsements);

var chaincodeActionPayload = new _ccTransProto.ChaincodeActionPayload();
chaincodeActionPayload.setAction(chaincodeEndorsedAction);
chaincodeActionPayload.setChaincodeProposalPayload(chaincodeProposal.payload);

var transactionAction = new _transProto.TransactionAction();
transactionAction.setHeader(chaincodeProposal.header);
transactionAction.setPayload(chaincodeActionPayload.toBuffer());

var transaction2 = new _transProto.Transaction2();
var actions = [];
actions.push(transactionAction);
transaction2.setActions(actions);

var orderer = this._chain.getOrderer();
return orderer.sendBroadcast(data);
return orderer.sendBroadcast(transaction2.toBuffer());
}

/**
* Sends a deployment proposal to an endorser.
*
* @param {Object} request An object containing the following fields:
* endorserUrl: Peer URL
* target : Endorsing Peer Object
* chaincodePath : String
* fcn : String
* args : Strings
* @returns Promise for a ProposalResponse
*/
sendDeploymentProposal(request) {
if (!request.endorserUrl || request.endorserUrl === '') {
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "endorserUrl"');
return Promise.reject(new Error('missing endorserUrl in Deployment proposal request'));
}

// Verify that chaincodePath is being passed
if (!request.chaincodePath || request.chaincodePath === '') {
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "chaincodePath"');
return Promise.reject(new Error('missing chaincodePath in Deployment proposal request'));
}

if (!request.fcn || request.fnc === '') {
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "fcn" for the target function to call during chaincode initialization');
return Promise.reject(new Error('missing fcn in Deployment proposal request'));
// verify that the caller has included a peer object
if(!request.target) {
logger.error('Invalid input parameter to "sendDeploymentProposal": must have "target" object');
return Promise.reject(new Error('Missing "target" for the endorsing peer object in the Deployment proposal request'));
}
let peer = request.target;
var chaincode_id;

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

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

Expand Down Expand Up @@ -455,11 +486,11 @@ var Member = class {
payload: payload.toBuffer()
};

let peer = new Peer(request.endorserUrl);
return peer.sendProposal(proposal)
.then(
function(data) {
resolve(data);
function(response) {
response.chaincodeId = chaincode_id;
resolve([response, proposal]);
}
);
}
Expand All @@ -468,6 +499,128 @@ var Member = class {
}
).catch(
function(err) {
logger.error('Failed Deployment Proposal. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
* Sends a transaction proposal to an endorsing peer.
*
* @param {Object} request:
* target : {Object} Endorsing Peer object as the target of the request
* chaincodeId : {String} The id of the chaincode to perform the transaction proposal
* args : {Array} Arguments specific to the chaincode 'innvoke'
* @returns Promise for a ProposalResponse
*/
sendTransactionProposal(request) {
logger.debug('Member.sendTransactionProposal - start');

// verify that the caller has included a peer object
if(!request.target) {
logger.error('Missing "target" endorser peer object in the Transaction proposal request');
return Promise.reject(new Error('Missing "target" for endorser peer object in the Transaction proposal request'));
}

if(!request.chaincodeId) {
logger.error('Missing chaincode ID in the Transaction proposal request');
return Promise.reject(new Error('Missing chaincode ID in the Transaction proposal request'));
}

// args is not optional because we need for transaction to execute
if (!request.args) {
logger.error('Missing arguments in Transaction proposal request');
return Promise.reject(new Error('Missing arguments in Transaction proposal request'));
}

var args = [];
// leaving this for now... but this call is always an invoke and we are not telling caller to include 'fcn' any longer
args.push(Buffer.from(request.fcn ? request.fcn : 'invoke', 'utf8'));
logger.debug('Member.sendTransactionProposal - adding function arg:%s', request.fcn ? request.fcn : 'invoke');

for (let i=0; i<request.args.length; i++) {
args.push(Buffer.from(request.args[i], 'utf8'));
logger.debug('Member.sendTransactionProposal - adding arg:%s', request.args[i]);
}

let invokeSpec = {
type: _ccProto.ChaincodeSpec.Type.GOLANG,
chaincodeID: {
name: request.chaincodeId
},
ctorMsg: {
args: args
}
};

// construct the ChaincodeInvocationSpec
let cciSpec = new _ccProto.ChaincodeInvocationSpec();
cciSpec.setChaincodeSpec(invokeSpec);
cciSpec.setIdGenerationAlg('');

// construct the enveloping Proposal object
// - the header part of the proposal
let headerExt = new _ccProposalProto.ChaincodeHeaderExtension();
let header = new _headerProto.Header();
header.setType(_headerProto.Header.Type.CHAINCODE);
header.setExtensions(headerExt.toBuffer());
//header.setChainID()

// - the payload part of the proposal for chaincode deploy is ChaincodeProposalPayload
let payload = new _ccProposalProto.ChaincodeProposalPayload();
payload.setInput(cciSpec.toBuffer());

let proposal = {
header: header.toBuffer(),
payload: payload.toBuffer()
};

let peer = request.target;
return peer.sendProposal(proposal)
.then(
function(response) {
return Promise.resolve([response,proposal]);
}
).catch(
function(err) {
logger.error('Failed Transaction Proposal. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
* Sends a proposal to an endorsing peer that will be handled by the chaincode.
* This request will be presented to the chaincode 'invoke' and must understand
* from the arguments that this is a query request. The chaincode must also return
* results in the byte array format and the caller will have to be able to decode
* these results
*
* @param {Object} request:
* target : {Object} Endorsing Peer object as the target of the request
* chaincodeId : {String} The id of the chaincode to perform the query
* args : {Array} Arguments for the 'invoke' function call on the chaincode
* that represent a query invocation
* @returns Promise for a byte array results from the chaincode
*/
queryByChaincode(request) {
logger.debug('Member.sendQueryProposal - start');

return this.sendTransactionProposal(request)
.then(
function(results) {
var response = results[0];
var proposal = results[1];
logger.debug('Member-sendQueryProposal - response %j', response);
if(response.response && response.response.payload) {
return Promise.resolve(response.response.payload);
}
return Promise.reject(new Error('Payload results are missing from the chaincode query'));
}
).catch(
function(err) {
logger.error('Failed Query by chaincode. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
Expand Down Expand Up @@ -553,6 +706,7 @@ function packageChaincode(chaincodePath, fcn, args) {
}
).catch(
function(err) {
logger.error('Failed to build chaincode package: %s', err.stack ? err.stack : err);
reject(err);
}
);
Expand Down
23 changes: 10 additions & 13 deletions lib/Orderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var Orderer = class extends Remote {
/**
* Send a BroadcastMessage to the orderer service.
*
* @param {Object} data to be included in the BroadcastMessage
* @param {byte} data to be included in the BroadcastMessage
* see the ./proto/atomicbroadcast/ab.proto
* @returns Promise for a BroadcastResponse
* see the ./proto/atomicbroadcast/ab.proto
Expand All @@ -71,41 +71,37 @@ var Orderer = class extends Remote {
}

var self = this;
var data = new Buffer(send_data);

// Build up the broadcast message
// This will be fleshed out we add more functionality and send fully
// structured requests, with all fields filled in.
var _broadcastMessage = {Data: data};

// show some of what we have
logger.debug('Orderer.sendBroadcast - _broadcastMessage = %j', data);
var _broadcastMessage = new _abProto.BroadcastMessage();
_broadcastMessage.setData(send_data);

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

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

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

if(response.Status) {
if (response.Status === 'SUCCESS') {
logger.debug('Orderer.sendBroadcast - resolve with %s', response.Status);
return resolve(response);
} else {
logger.debug('Orderer.sendBroadcast - reject with %s', response.Status);
logger.error('Orderer.sendBroadcast - reject with %s', response.Status);
return reject(new Error(response.Status));
}
}
else {
logger.debug('Orderer.sendBroadcast ERROR - reject with invalid response from the orderer');
logger.error('Orderer.sendBroadcast ERROR - reject with invalid response from the orderer');
return reject(new Error('SYSTEM_ERROR'));
}

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

broadcast.on('error', function (err) {
logger.debug('Orderer.sendBroadcast - on error: %j',err);
if(err && err.code) {
if(err.code == 14) {
logger.error('Orderer.sendBroadcast - on error: %j',err.stack ? err.stack : err);
return reject(new Error('SERVICE_UNAVAILABLE'));
}
}
logger.debug('Orderer.sendBroadcast - on error: %j',err.stack ? err.stack : err);
return reject(new Error(err));
});

broadcast.write(_broadcastMessage);
broadcast.end();
logger.debug('Orderer.sendBroadcast - write/end complete');
logger.debug('Orderer.sendBroadcast - sent message');
});
}

Expand Down
10 changes: 4 additions & 6 deletions lib/Peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,13 @@ var Peer = class extends Remote {
// The rpc specification on the peer side is:
// rpc ProcessProposal(Proposal) returns (ProposalResponse) {}
return new Promise(function(resolve, reject) {
self._endorserClient.processProposal(proposal, function(err, response) {
self._endorserClient.processProposal(proposal, function(err, proposalResponse) {
if (err) {
reject(new Error(err));
} else {
if (response) {
logger.info('Received proposal response: code - %s', JSON.stringify(response.response.status));
// return the original proposal payload along with the response, so that
// it can be used to construct the Transaction object
resolve([response, proposal.payload]);
if (proposalResponse) {
logger.info('Received proposal response: code - %s', JSON.stringify(proposalResponse.response));
resolve(proposalResponse);
} else {
logger.error('GRPC client failed to get a proper response from the peer.');
reject(new Error('GRPC client failed to get a proper response from the peer.'));
Expand Down
Loading

0 comments on commit 74aaa9a

Please sign in to comment.