Skip to content

Commit

Permalink
[FABN-1148] Allow offline signer for token (part 2)
Browse files Browse the repository at this point in the history
- Add more unit tests
- Remove console.log
- Update javascript doc

Change-Id: I4f203829399f23a75eb2267b1e977afcd66331e6
Signed-off-by: Wenjian Qiao <[email protected]>
  • Loading branch information
wenjianqiao committed Feb 21, 2019
1 parent 1cc1feb commit 83846e5
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 21 deletions.
18 changes: 16 additions & 2 deletions fabric-client/lib/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3274,7 +3274,7 @@ const Channel = class {
* timeout in the config settings.
* @returns {Promise} A Promise for the {@link CommandResponse}
*/
static async sendSignedTokenCommand(request, timeout) {
async sendSignedTokenCommand(request, timeout) {
const method = 'sendSignedTokenCommand';
logger.debug('%s - start', method);

Expand Down Expand Up @@ -3417,7 +3417,21 @@ const Channel = class {
const method = 'sendSignedTokenTransaction';
logger.debug('%s - start', method);

const signed_envelope = token_utils.toEnvelope(request.signature, request.tokentx_bytes);
if (!request) {
throw Error(util.format('Missing required "request" parameter on the %s call', method));
}
if (!request.signature) {
throw new Error(util.format('Missing required "signature" in request on the %s call', method));
}
if (!request.payload_bytes) {
throw new Error(util.format('Missing required "payload_bytes" in request on the %s call', method));
}
if (!request.txId) {
throw new Error(util.format('Missing required "txId" in request on the %s call', method));
}

const signed_envelope = token_utils.toEnvelope(request.signature, request.payload_bytes);

if (this._commit_handler) {
const params = {
signed_envelope,
Expand Down
20 changes: 11 additions & 9 deletions fabric-client/lib/TokenClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

'use strict';

const Channel = require('./Channel');
const TransactionID = require('./TransactionID');
const util = require('util');
const clientUtils = require('./client-utils.js');
Expand Down Expand Up @@ -348,26 +347,28 @@ const TokenClient = class {
/**
* Sends signed token command to peer
*
* @param {SignedCommandRequest} request containing signedCommand and targets.
* The signed command would be sent to peer directly.
* @param {number} timeout the timeout setting passed on sendSignedCommand
* @param {SignedCommandRequest} request - Required.
* Must contain command_bytes and signature properties.
* @param {number} timeout - Optional. The timeout setting passed on sendSignedCommand.
* @returns {Promise} A Promise for a "CommandResponse" message returned by
* the prover peer.
*/
async sendSignedTokenCommand(request, timeout) {
logger.debug('sendSignedTokenCommand - start');

// copy request to protect user input
const sendRequest = Object.assign({}, request);
if (!sendRequest.targets) {
sendRequest.targets = this._targets;
}
return Channel.sendSignedTokenCommand(sendRequest, timeout);
return this._channel.sendSignedTokenCommand(sendRequest, timeout);
}

/**
* @typedef {Object} SignedTokenTransactionRequest
* This object contains properties that will be used for broadcasting a token transaction.
* @property {Buffer} signature - Required. The signature.
* @property {Buffer} payload_bytes - Required. The token transaction payload bytes.
* @property {Buffer} payload_bytes - Required. Payload bytes for the token transaction.
* @property {Buffer} signature - Required. Signer's signature for payload_bytes.
* @property {TransactionID} txId - Required. Transaction ID to use for this request.
* @property {Orderer | string} orderer - Optional. The orderer that will receive this request,
* when not provided, the transaction will be sent to the orderers assigned to this channel instance.
Expand All @@ -376,8 +377,8 @@ const TokenClient = class {
/**
* send the signed token transaction
*
* @param {SignedTokenTransactionRequest} request - Required. The signed token transaction.
* Must contain 'signature' and 'tokentx_bytes' properties.
* @param {SignedTokenTransactionRequest} request - Required.
* Must contain 'payload_bytes', 'signature' and 'txId' properties.
* @param {Number} timeout - A number indicating milliseconds to wait on the
* response before rejecting the promise with a timeout error. This
* overrides the default timeout of the Orderer instance and the global
Expand All @@ -389,6 +390,7 @@ const TokenClient = class {
* submitted transaction.
*/
async sendSignedTokenTransaction(request, timeout) {
logger.debug('sendSignedTokenTransaction - start');
return this._channel.sendSignedTokenTransaction(request, timeout);
}

Expand Down
4 changes: 2 additions & 2 deletions fabric-client/lib/token-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,6 @@ exports.toSignedCommand = (signature, command_bytes) => ({signature: signature,
/**
* convert to protos.common.Envelope
* @param signature
* @param tokentx_bytes
* @param payload_bytes
*/
exports.toEnvelope = (signature, tokentx_bytes) => ({signature: signature, payload: tokentx_bytes});
exports.toEnvelope = (signature, payload_bytes) => ({signature: signature, payload: payload_bytes});
234 changes: 234 additions & 0 deletions fabric-client/test/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3027,6 +3027,117 @@ describe('Channel', () => {
});
});

describe('#sendSignedTokenCommand', () => {
let sandbox;
let revert;
let mockRequest;
let signedCommand;
let _getTargetsStub;
let sendTokenCommandToPeerStub;

const channelId = 'mychannel';
const timeout = 100;
const mockCommandResponse = new fabprotos.token.CommandResponse();
const mockTargets = [sinon.createStubInstance(Peer)];
const clientStub = sinon.createStubInstance(Client);

beforeEach(() => {
const FakeLogger = {
debug : () => {},
error: () => {}
};
debugStub = sinon.stub(FakeLogger, 'debug');

revert = [];
sandbox = sinon.createSandbox();

sendTokenCommandToPeerStub = sandbox.stub();
sendTokenCommandToPeerStub.returns(mockCommandResponse);

revert.push(ChannelRewire.__set__('token_utils.sendTokenCommandToPeer', sendTokenCommandToPeerStub));
revert.push(ChannelRewire.__set__('logger', FakeLogger));

// create channel instance
channel = new ChannelRewire(channelId, clientStub);

_getTargetsStub = sinon.stub(channel, '_getTargets');
_getTargetsStub.returns(mockTargets);

mockRequest = {command_bytes: 'fake-command', signature: 'fake-signature'};
signedCommand = {command: 'fake-command', signature: 'fake-signature'};
});

afterEach(() => {
if (revert.length) {
revert.forEach(Function.prototype.call, Function.prototype.call);
}
sandbox.restore();
});

it('should return a command response', async () => {
const response = await channel.sendSignedTokenCommand(mockRequest, timeout);
expect(response).to.equal(mockCommandResponse);

sinon.assert.calledOnce(_getTargetsStub);
sinon.assert.calledOnce(sendTokenCommandToPeerStub);
sinon.assert.calledWith(_getTargetsStub, undefined, Constants.NetworkConfig.PROVER_PEER_ROLE);
sinon.assert.calledWith(sendTokenCommandToPeerStub, mockTargets, signedCommand, timeout);
sinon.assert.calledWith(debugStub, '%s - start');
});

it('should throw an error if request is mssing', async () => {
try {
mockRequest.tokenTransaction = undefined;
await channel.sendSignedTokenCommand();
should.fail();
} catch (err) {
err.message.should.equal('Missing required "request" parameter on the sendSignedTokenCommand call');
}
});

it('should throw an error if request.command_bytes is mssing', async () => {
try {
mockRequest.command_bytes = undefined;
await channel.sendSignedTokenCommand(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('Missing required "command_bytes" in request on the sendSignedTokenCommand call');
}
});

it('should throw an error if request.signature is mssing', async () => {
try {
mockRequest.signature = undefined;
await channel.sendSignedTokenCommand(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('Missing required "signature" in request on the sendSignedTokenCommand call');
}
});

it('should throw an error if getTargets fails', async () => {
try {
const fakeError = new Error('forced get targets error');
_getTargetsStub.throws(fakeError);
await channel.sendSignedTokenCommand(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('forced get targets error');
}
});

it('should throw an error if sendTokenCommandToPeerStub fails', async () => {
try {
const fakeError = new Error('forced send to peer error');
sendTokenCommandToPeerStub.throws(fakeError);
await channel.sendSignedTokenCommand(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('forced send to peer error');
}
});
});

describe('#sendTokenTransaction', () => {
let sandbox;
let revert;
Expand Down Expand Up @@ -3161,6 +3272,129 @@ describe('Channel', () => {
});
});

describe('#sendSignedTokenTransaction', () => {
let sandbox;
let revert;
let mockRequest;
let clientStub;
let ordererStub;
let txIdStub;
let envelope;

const channelId = 'mychannel';
const timeout = 100;
const mockResponse = {status: 'SUCCESS'};

beforeEach(() => {
const FakeLogger = {
debug : () => {},
error: () => {}
};
debugStub = sinon.stub(FakeLogger, 'debug');

revert = [];
sandbox = sinon.createSandbox();

// prepare stubs
revert.push(ChannelRewire.__set__('logger', FakeLogger));

ordererStub = sinon.createStubInstance(Orderer);
ordererStub.sendBroadcast.returns(mockResponse);

clientStub = sinon.createStubInstance(Client);
clientStub.getTargetOrderer.returns(ordererStub);

// create channel instance
channel = new ChannelRewire(channelId, clientStub);

// prepare mockRequest
txIdStub = sinon.createStubInstance(TransactionID);
mockRequest = {payload_bytes: 'fake-payload', signature: 'fake-signature', txId: txIdStub};
envelope = {payload: 'fake-payload', signature: 'fake-signature'};
});

afterEach(() => {
if (revert.length) {
revert.forEach(Function.prototype.call, Function.prototype.call);
}
sandbox.restore();
});

it('should return a response with SUCCESS status', async () => {
sinon.spy(clientStub.getTargetOrderer);
sinon.spy(ordererStub.sendBroadcast);

const response = await channel.sendSignedTokenTransaction(mockRequest, timeout);
expect(response.status).to.equal('SUCCESS');

sinon.assert.calledWith(clientStub.getTargetOrderer, undefined, [], channelId);
sinon.assert.calledWith(ordererStub.sendBroadcast, envelope, timeout);
sinon.assert.calledWith(debugStub, '%s - start');
});

it('should throw an error if request is mssing', async () => {
try {
mockRequest.tokenTransaction = undefined;
await channel.sendSignedTokenTransaction();
should.fail();
} catch (err) {
err.message.should.equal('Missing required "request" parameter on the sendSignedTokenTransaction call');
}
});

it('should throw an error if request.signature is mssing', async () => {
try {
mockRequest.signature = undefined;
await channel.sendSignedTokenTransaction(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('Missing required "signature" in request on the sendSignedTokenTransaction call');
}
});

it('should throw an error if request.payload_bytes is mssing', async () => {
try {
mockRequest.payload_bytes = undefined;
await channel.sendSignedTokenTransaction(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('Missing required "payload_bytes" in request on the sendSignedTokenTransaction call');
}
});

it('should throw an error if request.txId is mssing', async () => {
try {
mockRequest.txId = undefined;
await channel.sendSignedTokenTransaction(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('Missing required "txId" in request on the sendSignedTokenTransaction call');
}
});

it('should throw an error if client getTargetOrderer throws an error', async () => {
try {
const fakeError = new Error('forced get orderer error');
clientStub.getTargetOrderer.throws(fakeError);
await channel.sendSignedTokenTransaction(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('forced get orderer error');
}
});

it('should throw an error if order sendBroadcast throws an error', async () => {
try {
const fakeError = new Error('forced send broadcast error');
ordererStub.sendBroadcast.throws(fakeError);
await channel.sendSignedTokenTransaction(mockRequest, timeout);
should.fail();
} catch (err) {
err.message.should.equal('forced send broadcast error');
}
});
});

describe('#_buildSignedTokenCommand', () => {
let clientStub;
let signingIdentityStub;
Expand Down
Loading

0 comments on commit 83846e5

Please sign in to comment.