Skip to content

Commit

Permalink
NodeSDK - add Queries
Browse files Browse the repository at this point in the history
Add queryBlock(number), queryBlockByHash(hash), queryInfo(),
and queryTransaction(id) to 'Chain'. Update headless and
add new test script for network testing. Requires that
end-to-end be run first to provide block and transactions
to be queried.
Check primary peer to be sure on the chain's peer list

Change-Id: I57a8dbe62840bfb8d1b856a5a23e96b5bd1b8267
Signed-off-by: Bret Harrison <[email protected]>
  • Loading branch information
harrisob committed Feb 13, 2017
1 parent e10d4ec commit 1f22ed9
Show file tree
Hide file tree
Showing 5 changed files with 626 additions and 13 deletions.
266 changes: 255 additions & 11 deletions fabric-client/lib/Chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ var Chain = class {
this.cryptoPrimitives = utils.getCryptoSuite();

this._peers = [];
this._primary_peer = null; // if not set, will use the first peer on the list

this._orderers = [];

this._clientContext = clientContext;
Expand Down Expand Up @@ -227,6 +229,45 @@ var Chain = class {
return this._peers;
}

/**
* Set the primary peer
* The peer to use for doing queries.
* Peer must be a peer on this chain's peer list.
* Default: When no primary peer has been set the first peer
* on the list will be used.
* @param {Peer} peer An instance of the Peer class.
* @throws Error when peer is not on the existing peer list
*/
setPrimaryPeer(peer) {
if(peer) {
for (let i = 0; i < this._peers.length; i++) {
if (this._peers[i] === peer) {
this._primary_peer = this._peers[i];
return;
}
}
}
throw new Error('The primary peer must be on this chain\'s peer list');
}

/**
* Get the primary peer
* The peer to use for doing queries.
* Default: When no primary peer has been set the first peer
* on the list will be used.
* @returns {Peer} peer An instance of the Peer class.
*/
getPrimaryPeer() {
logger.debug('getPrimaryPeer :: start');
var result = this._primary_peer;
if(!result) {
result = this._peers[0];
logger.info(' Primary peer was not set, using %s',result);
}
// return what we found
return result;
}

/**
* Add orderer endpoint to a chain object, this is a local-only operation.
* A chain instance may choose to use a single orderer node, which will broadcast
Expand Down Expand Up @@ -814,28 +855,219 @@ var Chain = class {
/**
* Queries for various useful information on the state of the Chain
* (height, known peers).
* This query will be made to the primary peer.
* @returns {object} With height, currently the only useful info.
*/
queryInfo() {
//to do
logger.debug('queryInfo - start');
var request = {
targets: [this.getPrimaryPeer()],
chaincodeId : 'qscc',
chainId: '',
txId: utils.buildTransactionID(),
nonce: utils.getNonce(),
fcn : 'GetChainInfo',
args: [ this._name]
};
return this.sendTransactionProposal(request)
.then(
function(results) {
var responses = results[0];
logger.debug('queryInfo - got responses=' + responses.length);
if(responses && Array.isArray(responses)) {
//will only be one response as we are only querying the primary peer
if(responses.length > 1) {
return Promise.reject(new Error('Too many results returned'));
}
let response = responses[0];
if(response instanceof Error ) {
return Promise.reject(response);
}
if(response.response) {
logger.debug('queryInfo - response status %d:', response.response.status);
var chain_info = _ledgerProto.BlockchainInfo.decode(response.response.payload);
return Promise.resolve(chain_info);
}
// no idea what we have, lets fail it and send it back
return Promise.reject(response);
}
return Promise.reject(new Error('Payload results are missing from the query chain info'));
}
).catch(
function(err) {
logger.error('Failed Query chain info. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
* Queries the ledger for Block by block hash.
* This query will be made to the primary peer.
* @param {[byte]} block hash of the Block.
* @returns {object} Object containing the block.
*/
queryBlockByHash(blockHash) {
logger.debug('queryBlockByHash - start');
if(!blockHash) {
return Promise.reject( new Error('Blockhash bytes are required'));
}
var request = {
targets: [this.getPrimaryPeer()],
chaincodeId : 'qscc',
chainId: '',
txId: utils.buildTransactionID(),
nonce: utils.getNonce(),
fcn : 'GetBlockByHash',
args: [ this._name],
argbytes : blockHash
};
return this.sendTransactionProposal(request)
.then(
function(results) {
var responses = results[0];
logger.debug('queryBlockByHash - got response');
if(responses && Array.isArray(responses)) {
//will only be one response as we are only querying the primary peer
if(responses.length > 1) {
return Promise.reject(new Error('Too many results returned'));
}
let response = responses[0];
if(response instanceof Error ) {
return Promise.reject(response);
}
if(response.response) {
logger.debug('queryBlockByHash - response status %d:', response.response.status);
var block = _commonProto.Block.decode(response.response.payload);
logger.debug('queryBlockByHash - looking at block number:'+block.Header.Number);
return Promise.resolve(block);
}
// no idea what we have, lets fail it and send it back
return Promise.reject(response);
}
return Promise.reject(new Error('Payload results are missing from the query'));
}
).catch(
function(err) {
logger.error('Failed Query block. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
* Queries the ledger for Block by block number.
* This query will be made to the primary peer.
* @param {number} blockNumber The number which is the ID of the Block.
* @returns {object} Object containing the block.
*/
queryBlock(blockNumber) {
//to do
logger.debug('queryBlock - start blockNumber %s',blockNumber);
var block_number = null;
if(Number.isInteger(blockNumber) && blockNumber >= 0) {
block_number = blockNumber.toString();
} else {
return Promise.reject( new Error('Block number must be a postive integer'));
}
var request = {
targets: [this.getPrimaryPeer()],
chaincodeId : 'qscc',
chainId: '',
txId: utils.buildTransactionID(),
nonce: utils.getNonce(),
fcn : 'GetBlockByNumber',
args: [ this._name, block_number]
};
return this.sendTransactionProposal(request)
.then(
function(results) {
var responses = results[0];
logger.debug('queryBlock - got response');
if(responses && Array.isArray(responses)) {
//will only be one response as we are only querying the primary peer
if(responses.length > 1) {
return Promise.reject(new Error('Too many results returned'));
}
let response = responses[0];
if(response instanceof Error ) {
return Promise.reject(response);
}
if(response.response) {
logger.debug('queryBlock - response status %d:', response.response.status);
var block = _commonProto.Block.decode(response.response.payload);
logger.debug('queryBlock - looking at block number:'+block.Header.Number);
return Promise.resolve(block);
}
// no idea what we have, lets fail it and send it back
return Promise.reject(response);
}
return Promise.reject(new Error('Payload results are missing from the query'));
}
).catch(
function(err) {
logger.error('Failed Query block. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
* Queries the ledger for Transaction by number.
* This query will be made to the primary peer.
* @param {number} transactionID
* @returns {object} Transaction information containing the transaction.
*/
queryTransaction(transactionID) {
//to do
logger.debug('queryTransaction - start transactionID %s',transactionID);
var transaction_id = null;
if(transactionID) {
transaction_id = transactionID.toString();
} else {
return Promise.reject( new Error('Transaction id is required'));
}
var request = {
targets: [this.getPrimaryPeer()],
chaincodeId : 'qscc',
chainId: '',
txId: utils.buildTransactionID(),
nonce: utils.getNonce(),
fcn : 'GetTransactionByID',
args: [ this._name, transaction_id]
};
return this.sendTransactionProposal(request)
.then(
function(results) {
var responses = results[0];
logger.debug('queryTransaction - got response');
if(responses && Array.isArray(responses)) {
//will only be one response as we are only querying the primary peer
if(responses.length > 1) {
return Promise.reject(new Error('Too many results returned'));
}
let response = responses[0];
if(response instanceof Error ) {
return Promise.reject(response);
}
if(response.response) {
logger.debug('queryTransaction - response status %d:', response.response.status);
//logger.debug('queryTransaction - responses[i] -- %j:', responses[i]);
var envelope = _commonProto.Envelope.decode(response.response.payload);
//logger.debug('queryTransaction - envelope :: %j:', envelope);
var payload = _commonProto.Payload.decode(envelope.payload);
logger.debug('queryTransaction - transaction ID :: %s:', payload.header.chainHeader.txID);
return Promise.resolve(envelope);
}
// no idea what we have, lets fail it and send it back
return Promise.reject(response);
}
return Promise.reject(new Error('Payload results are missing from the query'));
}
).catch(
function(err) {
logger.error('Failed Transaction Query. Error: %s', err.stack ? err.stack : err);
return Promise.reject(err);
}
);
}

/**
Expand Down Expand Up @@ -956,13 +1188,13 @@ var Chain = class {
* @returns {Promise} A Promise for a `ProposalResponse`
*/
sendTransactionProposal(request) {
logger.debug('Chain.sendTransactionProposal - start');
logger.debug('sendTransactionProposal - start');
var errorMsg = null;

// Verify that a Peer has been added
if (this.getPeers().length < 1) {
errorMsg = 'Missing peer objects in Transaction proposal chain';
logger.error('Chain.sendDeploymentProposal error '+ errorMsg);
logger.error('sendDeploymentProposal error '+ errorMsg);
return Promise.reject(new Error(errorMsg));
}

Expand All @@ -974,20 +1206,27 @@ var Chain = class {
}

if(errorMsg) {
logger.error('Chain.sendTransactionProposal error '+ errorMsg);
logger.error('sendTransactionProposal error '+ errorMsg);
return Promise.reject(new Error(errorMsg));
}

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('Chain.sendTransactionProposal - adding function arg:%s', request.fcn ? request.fcn : 'invoke');
logger.debug('sendTransactionProposal - adding function arg:%s', request.fcn ? request.fcn : 'invoke');

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

//special case to support the bytes argument of the query by hash
if(request.argbytes) {
logger.debug('sendTransactionProposal - adding the argument :: argbytes');
args.push(request.argbytes);
}
else {
logger.debug('sendTransactionProposal - not adding the argument :: argbytes');
}
let invokeSpec = {
type: _ccProto.ChaincodeSpec.Type.GOLANG,
chaincodeID: {
Expand All @@ -1013,8 +1252,12 @@ var Chain = class {
header = buildHeader(userContext.getIdentity(), chainHeader, request.nonce);
proposal = self._buildProposal(invokeSpec, header);
let signed_proposal = self._signProposal(userContext.getSigningIdentity(), proposal);
var targets = self.getPeers();
if(request.targets) {
targets = request.targets;
}

return Chain._sendPeersProposal(self.getPeers(), signed_proposal);
return Chain._sendPeersProposal(targets, signed_proposal);
}
).then(
function(responses) {
Expand Down Expand Up @@ -1274,9 +1517,10 @@ var Chain = class {
var errorMsg = null;

if(request) {
var isQuery = request.chaincodeId == 'qscc';
if(!request.chaincodeId) {
errorMsg = 'Missing "chaincodeId" parameter in the proposal request';
} else if(!request.chainId) {
} else if(!request.chainId && !isQuery) {
errorMsg = 'Missing "chainId" parameter in the proposal request';
} else if(!request.txId) {
errorMsg = 'Missing "txId" parameter in the proposal request';
Expand Down
2 changes: 1 addition & 1 deletion fabric-client/lib/Peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ var Peer = class extends Remote {
reject(new Error(err));
} else {
if (proposalResponse) {
logger.info('Received proposal response: code - %s', JSON.stringify(proposalResponse.response));
logger.info('Received proposal response received: status - %s', proposalResponse.response.status);
resolve(proposalResponse);
} else {
logger.error('GRPC client failed to get a proper response from the peer.');
Expand Down
2 changes: 1 addition & 1 deletion test/unit/end-to-end.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ test('End-to-end flow of chaincode deploy, transaction invocation, and query', (
promise = promise.then((data) => {
logger.info('Executing step2');

tx_id = utils.buildTransactionID({length:12});
tx_id = '5678'; //utils.buildTransactionID({length:12});
nonce = utils.getNonce();
// send proposal to endorser
var request = {
Expand Down
Loading

0 comments on commit 1f22ed9

Please sign in to comment.