diff --git a/libraries/fabric-shim/lib/chaincode.js b/libraries/fabric-shim/lib/chaincode.js
index 1fb726e5..2ae51b67 100644
--- a/libraries/fabric-shim/lib/chaincode.js
+++ b/libraries/fabric-shim/lib/chaincode.js
@@ -14,7 +14,8 @@ const Logger = require('./logger');
const utils = require('./utils/utils');
const logger = Logger.getLogger('lib/chaincode.js');
-const Handler = require('./handler');
+const {ChaincodeSupportClient} = require('./handler');
+const ChaincodeServer = require('./server');
const Iterators = require('./iterators');
const ChaincodeStub = require('./stub');
const KeyEndorsementPolicy = require('./utils/statebased');
@@ -122,7 +123,7 @@ class Shim {
}
const chaincodeName = opts['chaincode-id-name'];
- const client = new Handler(chaincode, url, optsCpy);
+ const client = new ChaincodeSupportClient(chaincode, url, optsCpy);
const chaincodeID = {
name: chaincodeName
};
@@ -194,6 +195,28 @@ class Shim {
return Logger.getLogger(name);
}
+
+ /**
+ * @interface ChaincodeServerTLSProperties
+ * @property {Buffer} key Private key for TLS
+ * @property {Buffer} cert Certificate for TLS
+ * @property {Buffer} [clientCACerts] CA certificate for client certificates if mutual TLS is used.
+ */
+ /**
+ * @interface ChaincodeServerOpts
+ * @property {string} ccid Chaincode ID
+ * @property {string} address Listen address for the server
+ * @property {ChaincodeServerTLSProperties} [tlsProps] TLS properties if TLS is required.
+ */
+ /**
+ * Returns a new Chaincode server. Should be called when the chaincode is launched in a server mode.
+ * @static
+ * @param {ChaincodeInterface} chaincode User-provided object that must implement ChaincodeInterface
+ * @param {ChaincodeServerOpts} serverOpts Chaincode server options
+ */
+ static server(chaincode, serverOpts) {
+ return new ChaincodeServer(chaincode, serverOpts);
+ }
}
// special OID used by Fabric to save attributes in X.509 certificates
diff --git a/libraries/fabric-shim/lib/cmds/serverCommand.js b/libraries/fabric-shim/lib/cmds/serverCommand.js
new file mode 100644
index 00000000..d23b72db
--- /dev/null
+++ b/libraries/fabric-shim/lib/cmds/serverCommand.js
@@ -0,0 +1,106 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+
+'use strict';
+
+const fs = require('fs');
+
+exports.command = 'server [options]';
+exports.desc = 'Start the chaincode as a server';
+
+const validOptions = {
+ 'chaincode-address': {type: 'string', required: true},
+ 'grpc.max_send_message_length': {type: 'number', default: -1},
+ 'grpc.max_receive_message_length': {type: 'number', default: -1},
+ 'grpc.keepalive_time_ms': {type: 'number', default: 110000},
+ 'grpc.http2.min_time_between_pings_ms': {type: 'number', default: 110000},
+ 'grpc.keepalive_timeout_ms': {type: 'number', default: 20000},
+ 'grpc.http2.max_pings_without_data': {type: 'number', default: 0},
+ 'grpc.keepalive_permit_without_calls': {type: 'number', default: 1},
+ 'chaincode-id': {type: 'string', required: true},
+ 'chaincode-tls-cert-file': {type: 'string', conflicts: 'chaincode-tls-cert-path'},
+ 'chaincode-tls-cert-path': {type: 'string', conflicts: 'chaincode-tls-cert-file'},
+ 'chaincode-tls-key-file': {type: 'string', conflicts: 'chaincode-tls-key-path'},
+ 'chaincode-tls-key-path': {type: 'string', conflicts: 'chaincode-tls-key-file'},
+ 'chaincode-tls-client-cacert-file': {type: 'string', conflicts: 'chaincode-tls-client-cacert-path'},
+ 'chaincode-tls-client-cacert-path': {type: 'string', conflicts: 'chaincode-tls-client-cacert-file'},
+ 'module-path': {type: 'string', default: process.cwd()}
+};
+
+exports.validOptions = validOptions;
+
+exports.builder = function (yargs) {
+ yargs.options(validOptions);
+
+ yargs.usage('fabric-chaincode-node server --chaincode-address 0.0.0.0:9999 --chaincode-id mycc_v0:abcdef12345678...');
+
+ yargs.check((argv) => {
+ if (argv['chaincode-tls-key-file'] || argv['chaincode-tls-key-path'] ||
+ argv['chaincode-tls-cert-file'] || argv['chaincode-tls-cert-path']) {
+ // TLS should be enabled
+ if (!argv['chaincode-tls-key-file'] && !argv['chaincode-tls-key-path']) {
+ throw new Error('A TLS option is set but no key is specified');
+ }
+ if (!argv['chaincode-tls-cert-file'] && !argv['chaincode-tls-cert-path']) {
+ throw new Error('A TLS option is set but no cert is specified');
+ }
+ }
+ return true;
+ });
+
+ return yargs;
+};
+
+exports.handler = function (argv) {
+ const Bootstrap = require('../contract-spi/bootstrap');
+
+ return argv.thePromise = Bootstrap.bootstrap(true);
+};
+
+exports.getArgs = function (yargs) {
+ const argv = {};
+
+ for (const name in validOptions) {
+ argv[name] = yargs.argv[name];
+ }
+
+ // Load the cryptographic files if TLS is enabled
+ if (argv['chaincode-tls-key-file'] || argv['chaincode-tls-key-path'] ||
+ argv['chaincode-tls-cert-file'] || argv['chaincode-tls-cert-path']) {
+
+ const tlsProps = {};
+
+ if (argv['chaincode-tls-key-file']) {
+ tlsProps.key = fs.readFileSync(argv['chaincode-tls-key-file']);
+ } else {
+ tlsProps.key = Buffer.from(fs.readFileSync(argv['chaincode-tls-key-path']).toString(), 'base64');
+ }
+
+ if (argv['chaincode-tls-cert-file']) {
+ tlsProps.cert = fs.readFileSync(argv['chaincode-tls-cert-file']);
+ } else {
+ tlsProps.cert = Buffer.from(fs.readFileSync(argv['chaincode-tls-cert-path']).toString(), 'base64');
+ }
+
+ // If cacert option is specified, enable client certificate validation
+ if (argv['chaincode-tls-client-cacert-file']) {
+ tlsProps.clientCACerts = fs.readFileSync(argv['chaincode-tls-client-cacert-file']);
+ } else if (argv['chaincode-tls-client-cacert-path']) {
+ tlsProps.clientCACerts = Buffer.from(fs.readFileSync(argv['chaincode-tls-client-cacert-path']).toString(), 'base64');
+ }
+
+ argv.tlsProps = tlsProps;
+ }
+
+ // Translate the options to server options
+ argv.ccid = argv['chaincode-id'];
+ argv.address = argv['chaincode-address'];
+
+ delete argv['chaincode-id'];
+ delete argv['chaincode-address'];
+
+ return argv;
+};
diff --git a/libraries/fabric-shim/lib/contract-spi/bootstrap.js b/libraries/fabric-shim/lib/contract-spi/bootstrap.js
index f682018e..12affc42 100644
--- a/libraries/fabric-shim/lib/contract-spi/bootstrap.js
+++ b/libraries/fabric-shim/lib/contract-spi/bootstrap.js
@@ -14,6 +14,7 @@ const shim = require('../chaincode');
const ChaincodeFromContract = require('./chaincodefromcontract');
const Logger = require('../logger');
const StartCommand = require('../cmds/startCommand.js');
+const ServerCommand = require('../cmds/serverCommand.js');
const logger = Logger.getLogger('contracts-spi/bootstrap.js');
@@ -28,25 +29,31 @@ class Bootstrap {
* @ignore
* @param {Contract} contracts contract to register to use
*/
- static register(contracts, serializers, fileMetadata, title, version) {
+ static register(contracts, serializers, fileMetadata, title, version, opts, serverMode = false) {
// load up the meta data that the user may have specified
// this will need to passed in and rationalized with the
// code as implemented
const chaincode = new ChaincodeFromContract(contracts, serializers, fileMetadata, title, version);
- // say hello to the peer
- shim.start(chaincode);
+ if (serverMode) {
+ const server = shim.server(chaincode, opts);
+ server.start();
+ } else {
+ // say hello to the peer
+ shim.start(chaincode);
+ }
}
/**
*
* @ignore
+ * @param {boolean} serverMode set true if the chaincode should be started as a server
*/
- static async bootstrap() {
- const opts = StartCommand.getArgs(yargs);
+ static async bootstrap(serverMode = false) {
+ const opts = serverMode ? ServerCommand.getArgs(yargs) : StartCommand.getArgs(yargs);
const {contracts, serializers, title, version} = this.getInfoFromContract(opts['module-path']);
const fileMetadata = await Bootstrap.getMetadata(opts['module-path']);
- Bootstrap.register(contracts, serializers, fileMetadata, title, version);
+ Bootstrap.register(contracts, serializers, fileMetadata, title, version, opts, serverMode);
}
static getInfoFromContract(modulePath) {
diff --git a/libraries/fabric-shim/lib/handler.js b/libraries/fabric-shim/lib/handler.js
index 899fc132..e521f8aa 100644
--- a/libraries/fabric-shim/lib/handler.js
+++ b/libraries/fabric-shim/lib/handler.js
@@ -205,7 +205,7 @@ class MsgQueueHandler {
/*
- * The ChaincodeSupportClient class represents a the base class for all remote nodes, Peer, Orderer , and MemberServicespeer.
+ * The ChaincodeSupportClient class represents a chaincode gRPC client to the peer.
*/
class ChaincodeSupportClient {
@@ -269,11 +269,37 @@ class ChaincodeSupportClient {
this._stream.end();
}
+ chat(convStarterMsg) {
+ this._stream = this._client.register();
+
+ this._handler = new ChaincodeMessageHandler(this._stream, this.chaincode);
+ this._handler.chat(convStarterMsg);
+ }
+
+ /*
+ return a printable representation of this object
+ */
+ toString() {
+ return 'ChaincodeSupportClient : {' +
+ 'url:' +
+ this._url +
+ '}';
+ }
+}
+
+/**
+ * The ChaincodeMessageHandler class handles messages between peer and chaincode both in the chaincode server and client model.
+ */
+class ChaincodeMessageHandler {
+ constructor (stream, chaincode) {
+ this._stream = stream;
+ this.chaincode = chaincode;
+ }
+
// this is a long-running method that does not return until
// the conversation b/w the chaincode program and the target
// peer has been completed
chat(convStarterMsg) {
- this._stream = this._client.register();
this.msgQueueHandler = new MsgQueueHandler(this);
const stream = this._stream;
@@ -528,15 +554,11 @@ class ChaincodeSupportClient {
});
}
-
/*
- * return a printable representation of this object
- */
+ return a printable representation of this object
+ */
toString() {
- return 'ChaincodeSupportClient : {' +
- 'url:' +
- this._url +
- '}';
+ return 'ChaincodeMessageHandler : {}';
}
}
@@ -723,7 +745,10 @@ function parseResponse(handler, res, method) {
}
}
-module.exports = ChaincodeSupportClient;
+module.exports = {
+ ChaincodeSupportClient,
+ ChaincodeMessageHandler
+};
//
// The Endpoint class represents a remote grpc or grpcs target
diff --git a/libraries/fabric-shim/lib/server.js b/libraries/fabric-shim/lib/server.js
new file mode 100644
index 00000000..005c5a47
--- /dev/null
+++ b/libraries/fabric-shim/lib/server.js
@@ -0,0 +1,134 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+
+'use strict';
+
+const protoLoader = require('@grpc/proto-loader');
+const grpc = require('@grpc/grpc-js');
+const path = require('path');
+
+const fabprotos = require('../bundle');
+const {ChaincodeMessageHandler} = require('./handler');
+const logger = require('./logger').getLogger('lib/server.js');
+
+const PROTO_PATH = path.resolve(__dirname, '..', 'protos', 'peer', 'chaincode_shim.proto');
+const packageDefinition = protoLoader.loadSync(
+ PROTO_PATH,
+ {
+ keepCase: true,
+ longs: String,
+ enums: String,
+ defaults: true,
+ oneofs: true,
+ includeDirs: [
+ path.resolve(__dirname, '..', 'google-protos'),
+ path.resolve(__dirname, '..', 'protos')
+ ]
+ }
+);
+const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
+
+/**
+ * The ChaincodeServer class represents a chaincode gRPC server, which waits for connections from peers.
+ */
+class ChaincodeServer {
+ constructor(chaincode, serverOpts) {
+ // Validate arguments
+ if (typeof chaincode !== 'object' || chaincode === null) {
+ throw new Error('Missing required argument: chaincode');
+ }
+ if (typeof serverOpts !== 'object' || serverOpts === null) {
+ throw new Error('Missing required argument: serverOpts');
+ }
+ if (typeof chaincode.Init !== 'function' || typeof chaincode.Invoke !== 'function') {
+ throw new Error('The "chaincode" argument must implement Init() and Invoke() methods');
+ }
+ if (typeof serverOpts.ccid !== 'string') {
+ throw new Error('Missing required property in serverOpts: ccid');
+ }
+ if (typeof serverOpts.address !== 'string') {
+ throw new Error('Missing required property in serverOpts: address');
+ }
+ if (typeof serverOpts.tlsProps === 'object' && serverOpts.tlsProps !== null) {
+ if (typeof serverOpts.tlsProps.key !== 'object' || serverOpts.tlsProps.key === null) {
+ throw new Error('Missing required property in serverOpts.tlsProps: key');
+ }
+ if (typeof serverOpts.tlsProps.cert !== 'object' || serverOpts.tlsProps.cert === null) {
+ throw new Error('Missing required property in serverOpts.tlsProps: cert');
+ }
+
+ let clientCACerts;
+ if (typeof serverOpts.tlsProps.clientCACerts === 'object' && serverOpts.tlsProps.clientCACerts !== null) {
+ clientCACerts = serverOpts.tlsProps.clientCACerts;
+ } else {
+ clientCACerts = null;
+ }
+
+ this._credentials = grpc.ServerCredentials.createSsl(clientCACerts, [
+ {
+ private_key: serverOpts.tlsProps.key,
+ cert_chain: serverOpts.tlsProps.cert
+ }
+ ], clientCACerts === null ? false : true);
+ } else {
+ this._credentials = grpc.ServerCredentials.createInsecure();
+ }
+
+ // Create GRPC Server and register RPC handler
+ this._server = new grpc.Server();
+ const self = this;
+
+ this._server.addService(protoDescriptor.protos.Chaincode.service, {
+ connect: (stream) => {
+ self.connect(stream);
+ }
+ });
+
+ this._serverOpts = serverOpts;
+ this._chaincode = chaincode;
+ }
+
+ start() {
+ return new Promise((resolve, reject) => {
+ logger.debug('ChaincodeServer trying to bind to ' + this._serverOpts.address);
+
+ this._server.bindAsync(this._serverOpts.address, this._credentials, (error, port) => {
+ if (!error) {
+ logger.debug('ChaincodeServer successfully bound to ' + port);
+
+ this._server.start();
+ logger.debug('ChaincodeServer started.');
+
+ resolve();
+ } else {
+ logger.error('ChaincodeServer failed to bind to ' + this._serverOpts.address);
+ reject(error);
+ }
+ });
+ });
+ }
+
+ connect(stream) {
+ logger.debug('ChaincodeServer.connect called.');
+
+ try {
+ const client = new ChaincodeMessageHandler(stream, this._chaincode);
+ const chaincodeID = {
+ name: this._serverOpts.ccid
+ };
+
+ logger.debug('Start chatting with a peer through a new stream. Chaincode ID = ' + this._serverOpts.ccid);
+ client.chat({
+ type: fabprotos.protos.ChaincodeMessage.Type.REGISTER,
+ payload: fabprotos.protos.ChaincodeID.encode(chaincodeID).finish()
+ });
+ } catch (e) {
+ logger.warn('connection from peer failed: ' + e);
+ }
+ }
+}
+
+module.exports = ChaincodeServer;
diff --git a/libraries/fabric-shim/test/unit/chaincode.js b/libraries/fabric-shim/test/unit/chaincode.js
index 3f87ea2d..2e7170b1 100644
--- a/libraries/fabric-shim/test/unit/chaincode.js
+++ b/libraries/fabric-shim/test/unit/chaincode.js
@@ -21,8 +21,8 @@ const chaincodePath = '../../lib/chaincode.js';
const StartCommand = require('../../lib/cmds/startCommand.js');
const caPath = path.join(__dirname, 'test-ca.pem');
-const certPath = path.join(__dirname, 'test-cert.pem');
-const keyPath = path.join(__dirname, 'test-key.pem');
+const certPath = path.join(__dirname, 'test-cert.base64');
+const keyPath = path.join(__dirname, 'test-key.base64');
const ca = fs.readFileSync(caPath, 'utf8');
const key = fs.readFileSync(keyPath, 'utf8');
@@ -97,7 +97,7 @@ describe('Chaincode', () => {
});
it ('should start when passed init and invoke', () => {
- const handlerClass = Chaincode.__get__('Handler');
+ const handlerClass = Chaincode.__get__('ChaincodeSupportClient');
const chat = sandbox.stub(handlerClass.prototype, 'chat');
const myYargs = {'argv': {'$0': 'fabric-chaincode-node', 'peer.address': 'localhost:7051', 'chaincode-id-name': 'mycc'}};
@@ -140,8 +140,8 @@ describe('Chaincode', () => {
const myYargs = {'argv': {'$0': 'fabric-chaincode-node', 'peer.address': 'localhost:7051', 'chaincode-id-name': 'mycc', 'some-other-arg': 'another-arg', 'yet-another-bad-arg': 'arg'}};
Chaincode.__set__('yargs', myYargs);
- const handlerClass = Chaincode.__get__('Handler');
- Chaincode.__set__('Handler', MockHandler);
+ const handlerClass = Chaincode.__get__('ChaincodeSupportClient');
+ Chaincode.__set__('ChaincodeSupportClient', MockHandler);
const getArgsStub = sandbox.stub(StartCommand, 'getArgs').returns({
'peer.address': 'localhost:7051',
@@ -162,7 +162,7 @@ describe('Chaincode', () => {
expect(testOpts.hasOwnProperty('module-path')).to.be.false;
expect(testOpts.hasOwnProperty('peer.address')).to.be.true;
- Chaincode.__set__('Handler', handlerClass);
+ Chaincode.__set__('ChaincodeSupportClient', handlerClass);
getArgsStub.restore();
});
@@ -213,7 +213,7 @@ describe('Chaincode', () => {
it ('should call handler.chat() with the correct object and output a message', () => {
- const handlerClass = Chaincode.__get__('Handler');
+ const handlerClass = Chaincode.__get__('ChaincodeSupportClient');
const chat = sandbox.stub(handlerClass.prototype, 'chat');
process.env.CORE_TLS_CLIENT_KEY_PATH = keyPath;
@@ -247,8 +247,8 @@ describe('Chaincode', () => {
}
}
- const handlerClass = Chaincode.__get__('Handler');
- Chaincode.__set__('Handler', MockHandler);
+ const handlerClass = Chaincode.__get__('ChaincodeSupportClient');
+ Chaincode.__set__('ChaincodeSupportClient', MockHandler);
process.env.CORE_TLS_CLIENT_KEY_PATH = keyPath;
process.env.CORE_TLS_CLIENT_CERT_PATH = certPath;
@@ -262,7 +262,7 @@ describe('Chaincode', () => {
testOpts.cert.should.equal(cert);
testOpts.key.should.equal(key);
- Chaincode.__set__('Handler', handlerClass);
+ Chaincode.__set__('ChaincodeSupportClient', handlerClass);
});
});
});
@@ -371,4 +371,23 @@ describe('Chaincode', () => {
Logger.getLogger.restore();
});
});
+
+ describe('server()', () => {
+ before(() => {
+ Chaincode = rewire(chaincodePath);
+ });
+
+ it ('should create a ChaincodeServer instance', () => {
+ const mockObj = {_chaincode: {}, _serverOpts: {}};
+ const serverStub = sinon.stub().returns(mockObj);
+ Chaincode.__set__('ChaincodeServer', serverStub);
+
+ const mockChaincode = new Chaincode.ChaincodeInterface();
+ const serverOpts = {ccid: 'example-cc-id:1', address: '0.0.0.0:9999'};
+
+ expect(Chaincode.server(mockChaincode, serverOpts)).to.deep.equal(mockObj);
+ expect(serverStub.calledOnce).to.be.true;
+ expect(serverStub.firstCall.args).to.deep.equal([mockChaincode, serverOpts]);
+ });
+ });
});
diff --git a/libraries/fabric-shim/test/unit/cmds/serverCommand.js b/libraries/fabric-shim/test/unit/cmds/serverCommand.js
new file mode 100644
index 00000000..14426b2f
--- /dev/null
+++ b/libraries/fabric-shim/test/unit/cmds/serverCommand.js
@@ -0,0 +1,211 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+
+'use strict';
+
+const sinon = require('sinon');
+
+const chai = require('chai');
+const expect = chai.expect;
+const fs = require('fs');
+const path = require('path');
+
+const yargs = require('yargs');
+const Bootstrap = require('../../../lib/contract-spi/bootstrap');
+const chaincodeServerCommand = require('../../../lib/cmds/serverCommand.js');
+
+describe('server cmd', () => {
+ let sandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe('.builder', () => {
+ it('should configure the builder function', () => {
+ sandbox.stub(yargs, 'options');
+ sandbox.stub(yargs, 'usage');
+ sandbox.stub(yargs, 'check');
+
+ chaincodeServerCommand.builder(yargs);
+
+ expect(yargs.options.calledOnce).to.be.true;
+
+ const args = yargs.options.getCall(0).args[0];
+
+ expect(args['chaincode-address'].required).to.be.true;
+ expect(args['chaincode-id'].required).to.be.true;
+ expect(args['grpc.max_send_message_length'].default).to.deep.equal(-1);
+ expect(args['grpc.max_receive_message_length'].default).to.deep.equal(-1);
+ expect(args['grpc.keepalive_time_ms'].default).to.deep.equal(110000);
+ expect(args['grpc.http2.min_time_between_pings_ms'].default).to.deep.equal(110000);
+ expect(args['grpc.keepalive_timeout_ms'].default).to.deep.equal(20000);
+ expect(args['grpc.http2.max_pings_without_data'].default).to.deep.equal(0);
+ expect(args['grpc.keepalive_permit_without_calls'].default).to.deep.equal(1);
+ expect(args['module-path'].default).to.deep.equal(process.cwd());
+
+ expect(yargs.usage.calledOnce).to.be.true;
+ expect(yargs.check.calledOnce).to.be.true;
+ });
+ });
+
+ describe('.handle', () => {
+ it('should handle properly and call bootstrap', () => {
+ sandbox.stub(Bootstrap, 'bootstrap');
+
+ const argv = {};
+ chaincodeServerCommand.handler(argv);
+
+ expect(Bootstrap.bootstrap.calledOnce).to.be.true;
+ });
+ });
+
+ describe('.getArgs', () => {
+ const certFileEncoded = path.join(__dirname, '..', 'test-cert.base64');
+ const keyFileEncoded = path.join(__dirname, '..', 'test-key.base64');
+ const caFileEncoded = path.join(__dirname, '..', 'test-ca.base64');
+ const certFile = path.join(__dirname, '..', 'test-cert.pem');
+ const keyFile = path.join(__dirname, '..', 'test-key.pem');
+ const caFile = path.join(__dirname, '..', 'test-ca.pem');
+ const cert = Buffer.from(fs.readFileSync(certFileEncoded).toString(), 'base64');
+ const key = Buffer.from(fs.readFileSync(keyFileEncoded).toString(), 'base64');
+ const ca = Buffer.from(fs.readFileSync(caFileEncoded).toString(), 'base64');
+
+ it('should return the arguments properly', () => {
+ const argv = {
+ 'chaincode-address': '0.0.0.0:9999',
+ 'chaincode-id': 'test_id:1',
+ 'grpc.keepalive_time_ms': 1000,
+ 'module-path': '/tmp/example',
+ 'extra-options': 'something'
+ };
+
+ const ret = chaincodeServerCommand.getArgs({argv});
+
+ expect(ret.address).to.equal('0.0.0.0:9999');
+ expect(ret.ccid).to.equal('test_id:1');
+ expect(ret['grpc.keepalive_time_ms']).to.equal(1000);
+ expect(ret['module-path']).to.equal('/tmp/example');
+ expect(ret['chaincode-address']).to.be.undefined;
+ expect(ret['chaincode-id']).to.be.undefined;
+ expect(ret['extra-options']).to.be.undefined;
+ });
+
+ it('should return the TLS arguments properly', () => {
+ const argv = {
+ 'chaincode-address': '0.0.0.0:9999',
+ 'chaincode-id': 'test_id:1',
+ 'module-path': '/tmp/example',
+ 'chaincode-tls-cert-path': certFileEncoded,
+ 'chaincode-tls-key-path': keyFileEncoded
+ };
+
+ const ret = chaincodeServerCommand.getArgs({argv});
+
+ expect(ret.address).to.equal('0.0.0.0:9999');
+ expect(ret.ccid).to.equal('test_id:1');
+
+ expect(ret.tlsProps).to.deep.equal({
+ cert,
+ key
+ });
+ });
+
+ it('should return the mutual TLS arguments properly', () => {
+ const argv = {
+ 'chaincode-address': '0.0.0.0:9999',
+ 'chaincode-id': 'test_id:1',
+ 'module-path': '/tmp/example',
+ 'chaincode-tls-cert-path': certFileEncoded,
+ 'chaincode-tls-key-path': keyFileEncoded,
+ 'chaincode-tls-client-cacert-path': caFileEncoded
+ };
+
+ const ret = chaincodeServerCommand.getArgs({argv});
+
+ expect(ret.address).to.equal('0.0.0.0:9999');
+ expect(ret.ccid).to.equal('test_id:1');
+
+ expect(ret.tlsProps).to.deep.equal({
+ cert,
+ key,
+ clientCACerts: ca
+ });
+ });
+
+ it('should return the TLS arguments with PEM files properly', () => {
+ const argv = {
+ 'chaincode-address': '0.0.0.0:9999',
+ 'chaincode-id': 'test_id:1',
+ 'module-path': '/tmp/example',
+ 'chaincode-tls-cert-file': certFile,
+ 'chaincode-tls-key-file': keyFile,
+ 'chaincode-tls-client-cacert-file': caFile
+ };
+
+ const ret = chaincodeServerCommand.getArgs({argv});
+
+ expect(ret.address).to.equal('0.0.0.0:9999');
+ expect(ret.ccid).to.equal('test_id:1');
+
+ expect(ret.tlsProps).to.deep.equal({
+ cert,
+ key,
+ clientCACerts: ca
+ });
+ });
+ });
+
+ describe('parse arguments', () => {
+ it('should parse the arguments successfully', () => {
+ expect(() => {
+ chaincodeServerCommand.builder(yargs)
+ .exitProcess(false)
+ .parse('--chaincode-id test_id:1 --chaincode-address 0.0.0.0:9999');
+ }).not.to.throw();
+ });
+
+ it('should parse the arguments successfully with TLS options', () => {
+ expect(() => {
+ chaincodeServerCommand.builder(yargs)
+ .exitProcess(false)
+ .parse('--chaincode-id test_id:1 --chaincode-address 0.0.0.0:9999 ' +
+ '--chaincode-tls-key-file tls.key --chaincode-tls-cert-file tls.pem');
+ }).not.to.throw();
+ });
+
+ it('should throw when conflicting arguments are passed', () => {
+ expect(() => {
+ chaincodeServerCommand.builder(yargs)
+ .exitProcess(false)
+ .parse('--chaincode-id test_id:1 --chaincode-address 0.0.0.0:9999 ' +
+ '--chaincode-tls-key-file tls.key --chaincode-tls-key-path tls.pem');
+ }).to.throw();
+ });
+
+ it('should throw when only TLS key is passed', () => {
+ expect(() => {
+ chaincodeServerCommand.builder(yargs)
+ .exitProcess(false)
+ .parse('--chaincode-id test_id:1 --chaincode-address 0.0.0.0:9999 ' +
+ '--chaincode-tls-key-file tls.key');
+ }).to.throw();
+ });
+
+ it('should throw when only TLS cert is passed', () => {
+ expect(() => {
+ chaincodeServerCommand.builder(yargs)
+ .exitProcess(false)
+ .parse('--chaincode-id test_id:1 --chaincode-address 0.0.0.0:9999 ' +
+ '--chaincode-tls-cert-file tls.pem');
+ }).to.throw();
+ });
+ });
+});
diff --git a/libraries/fabric-shim/test/unit/contract-spi/bootstrap.js b/libraries/fabric-shim/test/unit/contract-spi/bootstrap.js
index bd1698b7..aa7d5cdf 100644
--- a/libraries/fabric-shim/test/unit/contract-spi/bootstrap.js
+++ b/libraries/fabric-shim/test/unit/contract-spi/bootstrap.js
@@ -59,6 +59,7 @@ describe('bootstrap.js', () => {
let sandbox;
let mockShim;
+ let mockServer;
let mockCmd;
let readFileStub;
let pathExistsStub;
@@ -73,7 +74,8 @@ describe('bootstrap.js', () => {
useCleanCache: true
});
sandbox = sinon.createSandbox();
- mockShim = {start : sandbox.stub()};
+ mockServer = {start: sandbox.stub()};
+ mockShim = {start : sandbox.stub(), server: sandbox.stub().returns(mockServer)};
getArgsStub = sandbox.stub();
mockCmd = {getArgs : getArgsStub};
@@ -84,6 +86,7 @@ describe('bootstrap.js', () => {
mockery.registerMock('yargs', {});
mockery.registerMock('../chaincode', mockShim);
mockery.registerMock('../cmds/startCommand.js', mockCmd);
+ mockery.registerMock('../cmds/serverCommand.js', mockCmd);
mockery.registerMock('./chaincodefromcontract', MockChaincodeFromContract);
mockery.registerMock('fs-extra', {pathExists:pathExistsStub, readFileSync : readFileStub});
@@ -103,6 +106,16 @@ describe('bootstrap.js', () => {
sinon.assert.calledOnce(mockShim.start);
});
+ it('should pass on the register to the shim in the server mode', async () => {
+ const opts = {ccid: 'abcdef', address: '0.0.0.0:9999'};
+ await Bootstrap.register([sc], {}, {}, 'some title', 'some version', opts, true);
+
+ sinon.assert.calledOnce(mockShim.server);
+ sinon.assert.calledOnce(mockServer.start);
+
+ expect(mockShim.server.getCall(0).args[1]).to.deep.equal(opts);
+ });
+
});
describe('#bootstrap', () => {
@@ -129,8 +142,24 @@ describe('bootstrap.js', () => {
sinon.assert.calledOnce(getMetadataStub);
sinon.assert.calledOnce(getInfoFromContractStub);
sinon.assert.calledOnce(registerStub);
- sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version');
+ sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version', {'module-path':'fakepath'}, false);
});
+
+ it ('should correctly call the register method in the server mode', async () => {
+ getMetadataStub.resolves({});
+ mockery.registerMock(path.resolve(process.cwd(), 'fakepath', 'entrypoint'), {contracts: [sc]});
+ const registerStub = sandbox.stub();
+ Bootstrap.register = registerStub;
+ getInfoFromContractStub.returns({contracts: [sc], serializers : {}, title: 'some title', version: 'some version'});
+
+ await Bootstrap.bootstrap(true);
+
+ sinon.assert.calledOnce(getMetadataStub);
+ sinon.assert.calledOnce(getInfoFromContractStub);
+ sinon.assert.calledOnce(registerStub);
+ sinon.assert.calledWith(registerStub, [sc], {}, {}, 'some title', 'some version', {'module-path':'fakepath'}, true);
+ });
+
});
describe('#getInfoFromContract', () => {
diff --git a/libraries/fabric-shim/test/unit/handler.js b/libraries/fabric-shim/test/unit/handler.js
index 7b622af6..5bcd5042 100644
--- a/libraries/fabric-shim/test/unit/handler.js
+++ b/libraries/fabric-shim/test/unit/handler.js
@@ -33,8 +33,8 @@ const mockChaincodeImpl = {
};
const ca = fs.readFileSync(path.join(__dirname, 'test-ca.pem'), 'utf8');
-const key = fs.readFileSync(path.join(__dirname, 'test-key.pem'), 'utf8');
-const cert = fs.readFileSync(path.join(__dirname, 'test-cert.pem'), 'utf8');
+const key = fs.readFileSync(path.join(__dirname, 'test-key.base64'), 'utf8');
+const cert = fs.readFileSync(path.join(__dirname, 'test-cert.base64'), 'utf8');
const mockOpts = {
pem: ca,
@@ -369,19 +369,19 @@ describe('Handler', () => {
describe('ChaincodeSupportClient', () => {
it ('should throw an error when chaincode not passed', () => {
expect(() => {
- new Handler();
+ new Handler.ChaincodeSupportClient();
}).to.throw(/Missing required argument: chaincode/);
});
it ('should throw an error if argument does not match chaincode format', () => {
expect(() => {
- new Handler({});
+ new Handler.ChaincodeSupportClient({});
}).to.throw(/The chaincode argument must implement the mandatory "Init\(\)" method/);
});
it ('should throw an error if argument only part matches chaincode format', () => {
expect(() => {
- new Handler({
+ new Handler.ChaincodeSupportClient({
Init: function() {}
});
}).to.throw(/The chaincode argument must implement the mandatory "Invoke\(\)" method/);
@@ -389,20 +389,20 @@ describe('Handler', () => {
it ('should throw an error if argument missing URL argument', () => {
expect(() => {
- new Handler(mockChaincodeImpl);
+ new Handler.ChaincodeSupportClient(mockChaincodeImpl);
}).to.throw(/Invalid URL: undefined/);
});
it ('should throw an error if URL argument does not use grpc as protocol', () => {
expect(() => {
- new Handler(mockChaincodeImpl, 'https://' + mockPeerAddress.base);
+ new Handler.ChaincodeSupportClient(mockChaincodeImpl, 'https://' + mockPeerAddress.base);
}).to.throw(/Invalid protocol: https. {2}URLs must begin with grpc:\/\/ or grpcs:\/\//);
});
it ('should set endpoint, client and default timeout', () => {
const credsSpy = sinon.spy(grpc.credentials, 'createInsecure');
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure);
expect(handler._request_timeout).to.deep.equal(30000);
expect(handler._endpoint.addr).to.deep.equal(mockPeerAddress.base);
@@ -414,7 +414,7 @@ describe('Handler', () => {
});
it ('should override the default request timeout if value passed', () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure, {
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure, {
'request-timeout': 123456
});
@@ -422,7 +422,7 @@ describe('Handler', () => {
});
it ('should store additional grpc options', () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure, {
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure, {
'grpc.max_send_message_length': 1,
'grpc.max_receive_message_length': 2,
'grpc.keepalive_time_ms': 3,
@@ -442,20 +442,20 @@ describe('Handler', () => {
});
it ('should preserve casing in handler addr', () => {
- const handler = new Handler(mockChaincodeImpl, 'grpc://' + mockPeerAddress.base.toUpperCase());
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, 'grpc://' + mockPeerAddress.base.toUpperCase());
expect(handler._endpoint.addr).to.deep.equal(mockPeerAddress.base.toUpperCase());
});
it ('should throw an error if connection secure and certificate not passed', () => {
expect(() => {
- new Handler(mockChaincodeImpl, mockPeerAddress.secure);
+ new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.secure);
}).to.throw(/PEM encoded certificate is required./);
});
it ('should throw an error if connection secure encoded private key not passed as opt', () => {
expect(() => {
- new Handler(mockChaincodeImpl, mockPeerAddress.secure, {
+ new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.secure, {
pem: ca
});
}).to.throw(/encoded Private key is required./);
@@ -463,7 +463,7 @@ describe('Handler', () => {
it ('should throw an error if connection secure encoded private key not passed as opt', () => {
expect(() => {
- new Handler(mockChaincodeImpl, mockPeerAddress.secure, {
+ new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.secure, {
pem: ca,
key: key
});
@@ -473,7 +473,7 @@ describe('Handler', () => {
it ('should set endpoint, client and default timeout for a secure connection', () => {
const credsSpy = sinon.spy(grpc.credentials, 'createSsl');
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.secure, mockOpts);
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.secure, mockOpts);
expect(handler._options.cert).to.deep.equal(mockOpts.cert);
expect(handler._request_timeout).to.deep.equal(30000);
@@ -488,7 +488,7 @@ describe('Handler', () => {
const opts = Object.assign({}, mockOpts);
opts['ssl-target-name-override'] = 'dummy override';
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.secure, opts);
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.secure, opts);
expect(handler._options['grpc.ssl_target_name_override']).to.deep.equal('dummy override');
expect(handler._options['grpc.default_authority']).to.deep.equal('dummy override');
@@ -496,7 +496,7 @@ describe('Handler', () => {
describe('close', () => {
it ('should call end on the stream', () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure);
handler._stream = {end: sinon.stub()};
handler.close();
@@ -505,6 +505,43 @@ describe('Handler', () => {
});
});
+ describe('chat', () => {
+ afterEach(() => {
+ Handler = rewire('../../../fabric-shim/lib/handler.js');
+ });
+
+ it ('should create an instance of ChaincodeMessageHandler and pass the argument', () => {
+ const mockChaincodeMessageHandler = sinon.spy(() => {
+ return sinon.createStubInstance(Handler.ChaincodeMessageHandler);
+ });
+ Handler.__set__('ChaincodeMessageHandler', mockChaincodeMessageHandler);
+
+ const mockStream = {write: sinon.stub(), on: sinon.stub()};
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure);
+ handler._client.register = sinon.stub().returns(mockStream);
+
+ handler.chat('starter message example');
+
+ expect(handler._client.register.calledOnce).to.be.true;
+ expect(mockChaincodeMessageHandler.calledWithNew).to.be.ok; // believe wrong
+ expect(mockChaincodeMessageHandler.calledWithNew()).to.be.false;
+ expect(handler._stream).to.deep.equal(mockStream);
+ expect(handler._handler).to.deep.equal(new mockChaincodeMessageHandler(mockStream, mockChaincodeImpl));
+ expect(handler._handler.chat.calledOnce).to.be.true;
+ expect(handler._handler.chat.firstCall.args).to.deep.equal(['starter message example']);
+ });
+ });
+
+ describe('toString', () => {
+ it ('should return ChaincodeSupportClient object as a string with the URL', () => {
+ const handler = new Handler.ChaincodeSupportClient(mockChaincodeImpl, mockPeerAddress.unsecure);
+
+ expect(handler.toString()).to.deep.equal(`ChaincodeSupportClient : {url:${mockPeerAddress.unsecure}}`);
+ });
+ });
+ });
+
+ describe('ChaincodeMessageHandler', () => {
describe('chat', () => {
afterEach(() => {
Handler = rewire('../../../fabric-shim/lib/handler.js');
@@ -519,12 +556,9 @@ describe('Handler', () => {
const mockStream = {write: sinon.stub(), on: sinon.stub()};
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
- handler._client.register = sinon.stub().returns(mockStream);
-
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.chat('some starter message');
- expect(handler._client.register.calledOnce).to.be.true;
expect(mockMsgQueueHandler.calledWithNew).to.be.ok; // believe wrong
expect(mockMsgQueueHandler.calledWithNew()).to.be.false;
expect(handler._stream).to.deep.equal(mockStream);
@@ -584,8 +618,7 @@ describe('Handler', () => {
mockStream = {write: (sinon.stub()), on: mockEventEmitter, cancel: sinon.stub(), end: sinon.stub()};
- handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
- handler._client.register = sinon.stub().returns(mockStream);
+ handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.chat('some starter message');
handleInitSpy = sinon.spy();
@@ -758,8 +791,7 @@ describe('Handler', () => {
const mockStream = {write: sinon.stub(), on: mockEventEmitter, cancel: sinon.stub(), end: sinon.stub()};
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
- handler._client.register = sinon.stub().returns(mockStream);
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.chat('some starter message');
eventReg.end();
@@ -778,8 +810,7 @@ describe('Handler', () => {
const mockStream = {write: sinon.stub(), on: mockEventEmitter, end: sinon.stub()};
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
- handler._client.register = sinon.stub().returns(mockStream);
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.chat('some starter message');
eventReg.error({});
@@ -795,8 +826,7 @@ describe('Handler', () => {
const mockStream = {write: sinon.stub(), on: mockEventEmitter, end: sinon.stub()};
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
- handler._client.register = sinon.stub().returns(mockStream);
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.chat('some starter message');
const error = new Error();
eventReg.error(error);
@@ -814,7 +844,8 @@ describe('Handler', () => {
const handleMessage = sinon.spy();
Handler.__set__('handleMessage', handleMessage);
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.handleInit('some message');
expect(handleMessage.calledOnce).to.be.true;
@@ -831,7 +862,8 @@ describe('Handler', () => {
const handleMessage = sinon.spy();
Handler.__set__('handleMessage', handleMessage);
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.handleTransaction('some message');
expect(handleMessage.calledOnce).to.be.true;
@@ -861,7 +893,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetState(collection, key, 'theChannelID', 'theTxID');
@@ -873,7 +906,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetState(collection, key, 'theChannelID', 'theTxID');
@@ -907,7 +941,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handlePutState(collection, key, value, 'theChannelID', 'theTxID');
@@ -919,7 +954,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handlePutState(collection, key, value, 'theChannelID', 'theTxID');
@@ -952,7 +988,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleDeleteState(collection, key, 'theChannelID', 'theTxID');
@@ -964,7 +1001,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen rejects', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleDeleteState(collection, key, 'theChannelID', 'theTxID');
@@ -1005,7 +1043,8 @@ describe('Handler', () => {
});
it('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handlePutStateMetadata(collection, key, metadataKey, ep, 'theChannelID', 'theTxID');
@@ -1017,7 +1056,8 @@ describe('Handler', () => {
});
it('should reject when _askPeerAndListen rejects', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handlePutStateMetadata(collection, key, metadataKey, ep, 'theChannelID', 'theTxID');
@@ -1049,7 +1089,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetPrivateDataHash(collection, key, 'theChannelID', 'theTxID');
@@ -1061,7 +1102,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen rejects', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetPrivateDataHash(collection, key, 'theChannelID', 'theTxID');
@@ -1093,7 +1135,8 @@ describe('Handler', () => {
});
it('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetStateMetadata(collection, key, 'theChannelID', 'theTxID');
@@ -1105,7 +1148,8 @@ describe('Handler', () => {
});
it('should reject when _askPeerAndListen rejects', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetStateMetadata(collection, key, 'theChannelID', 'theTxID');
@@ -1138,7 +1182,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetStateByRange(collection, startKey, endKey, 'theChannelID', 'theTxID');
@@ -1150,7 +1195,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetStateByRange(collection, startKey, endKey, 'theChannelID', 'theTxID');
@@ -1162,7 +1208,8 @@ describe('Handler', () => {
});
it ('should resolve with metadata when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const metadata = Buffer.from('metadata');
@@ -1201,7 +1248,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleQueryStateNext(id, 'theChannelID', 'theTxID');
@@ -1213,7 +1261,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleQueryStateNext(id, 'theChannelID', 'theTxID');
@@ -1244,7 +1293,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleQueryStateClose(id, 'theChannelID', 'theTxID');
@@ -1256,7 +1306,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleQueryStateClose(id, 'theChannelID', 'theTxID');
@@ -1288,7 +1339,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetQueryResult(collection, query, null, 'theChannelID', 'theTxID');
@@ -1300,7 +1352,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen rejects', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetQueryResult(collection, query, null, 'theChannelID', 'theTxID');
@@ -1312,7 +1365,8 @@ describe('Handler', () => {
});
it ('handleGetQueryResult with metadata should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const metadata = Buffer.from('some metadata');
@@ -1351,7 +1405,8 @@ describe('Handler', () => {
});
it ('should resolve when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves('some response');
const result = await handler.handleGetHistoryForKey(key, 'theChannelID', 'theTxID');
@@ -1363,7 +1418,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const result = handler.handleGetHistoryForKey(key, 'theChannelID', 'theTxID');
@@ -1404,7 +1460,8 @@ describe('Handler', () => {
});
it ('should return decoded response when chaincode message type COMPLETED', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves({type: fabprotos.protos.ChaincodeMessage.Type.COMPLETED, payload: 'some payload'});
const decodeStub = sandbox.stub(fabprotos.protos.Response, 'decode').returns('some response');
@@ -1419,7 +1476,8 @@ describe('Handler', () => {
});
it ('should throw an error when _askPeerAndListen resolves with an error', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves({type: fabprotos.protos.ChaincodeMessage.Type.ERROR, payload: 'some payload'});
const decodeStub = sandbox.stub(fabprotos.protos.Response, 'decode').returns('some response');
@@ -1433,7 +1491,8 @@ describe('Handler', () => {
});
it ('should reject when _askPeerAndListen resolves', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').rejects();
const decodeStub = sandbox.stub(fabprotos.protos.Response, 'decode').returns('some response');
@@ -1447,7 +1506,8 @@ describe('Handler', () => {
});
it ('should return nothing chaincode message type not COMPLETED or ERROR', async () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
const _askPeerAndListenStub = sandbox.stub(handler, '_askPeerAndListen').resolves({type: fabprotos.protos.ChaincodeMessage.Type.SOMETHING_ELSE, payload: 'some payload'});
const decodeStub = sandbox.stub(fabprotos.protos.Response, 'decode').returns('some response');
@@ -1466,7 +1526,8 @@ describe('Handler', () => {
const msg = 'some message';
const method = 'SomeMethod';
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
handler.msgQueueHandler = sinon.createStubInstance(MsgQueueHandler);
handler.msgQueueHandler.queueMsg.callsFake((qMsg) => {
@@ -1484,9 +1545,10 @@ describe('Handler', () => {
describe('toString', () => {
it ('should return ChaincodeSupportClient object as a string with the URL', () => {
- const handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ const mockStream = {write: sinon.stub(), end: sinon.stub()};
+ const handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
- expect(handler.toString()).to.deep.equal(`ChaincodeSupportClient : {url:${mockPeerAddress.unsecure}}`);
+ expect(handler.toString()).to.deep.equal('ChaincodeMessageHandler : {}');
});
});
});
@@ -1767,6 +1829,7 @@ describe('Handler', () => {
let saveStateQueryIterator;
let saveHistoryQueryIterator;
+ let mockStream;
before(() => {
saveStateQueryIterator = Handler.__get__('StateQueryIterator');
@@ -1789,7 +1852,8 @@ describe('Handler', () => {
txid: 'aTx'
};
- handler = new Handler(mockChaincodeImpl, mockPeerAddress.unsecure);
+ mockStream = {write: sinon.stub(), end: sinon.stub()};
+ handler = new Handler.ChaincodeMessageHandler(mockStream, mockChaincodeImpl);
});
after(() => {
diff --git a/libraries/fabric-shim/test/unit/iterators.js b/libraries/fabric-shim/test/unit/iterators.js
index 5d208108..4c3b768f 100644
--- a/libraries/fabric-shim/test/unit/iterators.js
+++ b/libraries/fabric-shim/test/unit/iterators.js
@@ -14,7 +14,7 @@ const rewire = require('rewire');
const Iterator = rewire('../../../fabric-shim/lib/iterators.js');
const StateQueryIterator = Iterator.StateQueryIterator;
const HistoryQueryIterator = Iterator.HistoryQueryIterator;
-const handler = require('../../../fabric-shim/lib/handler.js');
+const {ChaincodeMessageHandler} = require('../../../fabric-shim/lib/handler.js');
const fabprotos = require('../../bundle');
const channel_id = 'theChannelId';
@@ -26,7 +26,7 @@ describe('Iterator', () => {
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
- mockHandler = sandbox.createStubInstance(handler);
+ mockHandler = sandbox.createStubInstance(ChaincodeMessageHandler);
mockResponse = {};
});
diff --git a/libraries/fabric-shim/test/unit/server.js b/libraries/fabric-shim/test/unit/server.js
new file mode 100644
index 00000000..04c3520a
--- /dev/null
+++ b/libraries/fabric-shim/test/unit/server.js
@@ -0,0 +1,227 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+/* global describe it beforeEach afterEach before after */
+'use strict';
+
+const sinon = require('sinon');
+const chai = require('chai');
+chai.use(require('chai-as-promised'));
+const expect = chai.expect;
+const fs = require('fs');
+const path = require('path');
+const rewire = require('rewire');
+
+const fabprotos = require('../../bundle');
+const grpc = require('@grpc/grpc-js');
+
+const serverPath = '../../lib/server';
+let ChaincodeServer = rewire(serverPath);
+
+const mockChaincode = {Init: () => {}, Invoke: () => {}};
+
+describe('ChaincodeServer', () => {
+ const tlsKey = Buffer.from(fs.readFileSync(path.join(__dirname, 'test-key.pem')).toString(), 'base64');
+ const tlsCert = Buffer.from(fs.readFileSync(path.join(__dirname, 'test-cert.pem')).toString(), 'base64');
+ const tlsClientCA = fs.readFileSync(path.join(__dirname, 'test-ca.pem'));
+
+ let grpcServerStub;
+ const serverOpts = {
+ ccid: 'example-chaincode-id',
+ address: '0.0.0.0:9999'
+ };
+ const serverTLSOpts = {
+ ccid: 'example-chaincode-id',
+ address: '0.0.0.0:9999',
+ tlsProps: {
+ // test-cert.pem and test-key.pem are base64-encoded and need to decode to make Buffer
+ key: tlsKey,
+ cert: tlsCert
+ }
+ };
+ const serverMutualTLSOpts = {
+ ccid: 'example-chaincode-id',
+ address: '0.0.0.0:9999',
+ tlsProps: {
+ key: tlsKey,
+ cert: tlsCert,
+ clientCACerts: tlsClientCA
+ }
+ };
+ const mockCredentials = {type: 'insecure'};
+ const mockTLSCredentials = {type: 'secure'};
+ let insecureCredentialsStub;
+ let sslCredentialsStub;
+
+ let mockGrpcServerInstance;
+
+ beforeEach(() => {
+ mockGrpcServerInstance = {
+ addService: sinon.stub()
+ };
+
+ grpcServerStub = sinon.stub(grpc, 'Server').returns(mockGrpcServerInstance);
+ insecureCredentialsStub = sinon.stub(grpc.ServerCredentials, 'createInsecure').returns(mockCredentials);
+ sslCredentialsStub = sinon.stub(grpc.ServerCredentials, 'createSsl').returns(mockTLSCredentials);
+ });
+ afterEach(() => {
+ grpcServerStub.restore();
+ insecureCredentialsStub.restore();
+ sslCredentialsStub.restore();
+ });
+
+ describe('constructor', () => {
+ it('should create a gRPC server instance and call addService in the constructor', () => {
+ const server = new ChaincodeServer(mockChaincode, serverOpts);
+
+ expect(grpcServerStub.calledOnce).to.be.ok;
+ expect(server._server).to.deep.equal(mockGrpcServerInstance);
+ expect(server._server.addService.calledOnce).to.be.ok;
+ expect(server._chaincode).to.deep.equal(mockChaincode);
+ expect(server._serverOpts).to.deep.equal(serverOpts);
+ expect(server._credentials).to.deep.equal(mockCredentials);
+
+ expect(insecureCredentialsStub.calledOnce).to.be.ok;
+ });
+ it('should create a gRPC server instance with TLS credentials and call addService in the constructor', () => {
+ const server = new ChaincodeServer(mockChaincode, serverTLSOpts);
+
+ expect(grpcServerStub.calledOnce).to.be.ok;
+ expect(server._server).to.deep.equal(mockGrpcServerInstance);
+ expect(server._server.addService.calledOnce).to.be.ok;
+ expect(server._chaincode).to.deep.equal(mockChaincode);
+ expect(server._serverOpts).to.deep.equal(serverTLSOpts);
+ expect(server._credentials).to.deep.equal(mockTLSCredentials);
+
+ expect(sslCredentialsStub.calledOnce).to.be.ok;
+ expect(sslCredentialsStub.firstCall.args[0]).to.be.null;
+ expect(sslCredentialsStub.firstCall.args[1]).to.deep.equal([{
+ private_key: tlsKey,
+ cert_chain: tlsCert
+ }]);
+ expect(sslCredentialsStub.firstCall.args[2]).to.be.false;
+ });
+ it('should create a gRPC server instance with mutual TLS credentials and call addService in the constructor', () => {
+ const server = new ChaincodeServer(mockChaincode, serverMutualTLSOpts);
+
+ expect(grpcServerStub.calledOnce).to.be.ok;
+ expect(server._server).to.deep.equal(mockGrpcServerInstance);
+ expect(server._server.addService.calledOnce).to.be.ok;
+ expect(server._chaincode).to.deep.equal(mockChaincode);
+ expect(server._serverOpts).to.deep.equal(serverMutualTLSOpts);
+ expect(server._credentials).to.deep.equal(mockTLSCredentials);
+
+ expect(sslCredentialsStub.calledOnce).to.be.ok;
+ expect(sslCredentialsStub.firstCall.args[0]).to.deep.equal(tlsClientCA);
+ expect(sslCredentialsStub.firstCall.args[1]).to.deep.equal([{
+ private_key: tlsKey,
+ cert_chain: tlsCert,
+ }]);
+ expect(sslCredentialsStub.firstCall.args[2]).to.be.true;
+ });
+
+ it('should throw an error when chaincode is missing', () => {
+ expect(() => new ChaincodeServer(null, serverOpts)).to.throw('Missing required argument: chaincode');
+ });
+ it('should throw an error when chaincode implements only Invoke', () => {
+ expect(() => new ChaincodeServer({Invoke: sinon.stub()}, serverOpts))
+ .to.throw('The "chaincode" argument must implement Init() and Invoke() methods');
+ });
+ it('should throw an error when chaincode implements only Init', () => {
+ expect(() => new ChaincodeServer({Init: sinon.stub()}, serverOpts))
+ .to.throw('The "chaincode" argument must implement Init() and Invoke() methods');
+ });
+ it('should throw an error when serverOpts is missing', () => {
+ expect(() => new ChaincodeServer(mockChaincode)).to.throw('Missing required argument: serverOpts');
+ });
+ it('should throw an error when serverOpts.ccid is missing', () => {
+ expect(() => new ChaincodeServer(mockChaincode, {})).to.throw('Missing required property in serverOpts: ccid');
+ });
+ it('should throw an error when serverOpts.address is missing', () => {
+ expect(() => new ChaincodeServer(mockChaincode, {ccid: 'some id'})).to.throw('Missing required property in serverOpts: address');
+ });
+ it('should throw an error when serverOpts.tlsProps.key is missing', () => {
+ expect(() => new ChaincodeServer(mockChaincode, {ccid: 'some id', address: '0.0.0.0:9999', tlsProps: {}})).
+ to.throw('Missing required property in serverOpts.tlsProps: key');
+ });
+ it('should throw an error when serverOpts.tlsProps.cert is missing', () => {
+ expect(() => new ChaincodeServer(mockChaincode, {ccid: 'some id', address: '0.0.0.0:9999', tlsProps: {key: Buffer.from('a')}})).
+ to.throw('Missing required property in serverOpts.tlsProps: cert');
+ });
+ });
+
+ describe('start()', () => {
+ it('should call bindAsync and start', async () => {
+ const server = new ChaincodeServer(mockChaincode, serverOpts);
+
+ server._server = {
+ bindAsync: sinon.stub().callsFake((address, credentials, callback) => {
+ callback(null, 9999);
+ }),
+ start: sinon.stub()
+ };
+
+ expect(await server.start()).not.to.throw;
+ expect(server._server.bindAsync.calledOnce).to.be.ok;
+ expect(server._server.bindAsync.firstCall.args[0]).to.equal(serverOpts.address);
+ expect(server._server.bindAsync.firstCall.args[1]).to.equal(mockCredentials);
+ expect(server._server.start.calledOnce).to.be.ok;
+ });
+
+ it('should throw if bindAsync fails', async () => {
+ const server = new ChaincodeServer(mockChaincode, serverOpts);
+
+ server._server = {
+ bindAsync: sinon.stub().callsFake((address, credentials, callback) => {
+ callback('failed to bind', 9999);
+ }),
+ start: sinon.stub()
+ };
+ expect(server.start()).to.eventually.be.rejectedWith('failed to bind');
+ });
+ });
+
+ describe('connect()', () => {
+ it('should call connect', () => {
+ const mockHandler = {
+ chat: sinon.stub()
+ };
+ const mockHandlerStub = sinon.stub().returns(mockHandler);
+ ChaincodeServer.__set__('ChaincodeMessageHandler', mockHandlerStub);
+
+ const server = new ChaincodeServer(mockChaincode, serverOpts);
+
+ const serviceImpl = server._server.addService.firstCall.args[1];
+ const mockStream = {on: sinon.stub(), write: sinon.stub()};
+
+ expect(serviceImpl.connect(mockStream)).not.to.throw;
+ expect(mockHandlerStub.calledOnce).to.be.ok;
+ expect(mockHandler.chat.calledOnce).to.be.ok;
+ expect(mockHandler.chat.firstCall.args).to.deep.equal([{
+ type: fabprotos.protos.ChaincodeMessage.Type.REGISTER,
+ payload: fabprotos.protos.ChaincodeID.encode({
+ name: 'example-chaincode-id'
+ }).finish()
+ }]);
+ });
+
+ it('should not throw even if chat fails', () => {
+ const mockHandler = {
+ chat: sinon.stub().throws(new Error('Some error from chat'))
+ };
+ const mockHandlerStub = sinon.stub().returns(mockHandler);
+ ChaincodeServer.__set__('ChaincodeMessageHandler', mockHandlerStub);
+
+ const server = new ChaincodeServer(mockChaincode, serverOpts);
+
+ const serviceImpl = server._server.addService.firstCall.args[1];
+ const mockStream = {on: sinon.stub(), write: sinon.stub()};
+
+ expect(serviceImpl.connect(mockStream)).not.to.throw;
+ expect(mockHandlerStub.calledOnce).to.be.ok;
+ expect(mockHandler.chat.calledOnce).to.be.ok;
+ });
+ });
+});
diff --git a/libraries/fabric-shim/test/unit/test-ca.base64 b/libraries/fabric-shim/test/unit/test-ca.base64
new file mode 100644
index 00000000..fc88d63f
--- /dev/null
+++ b/libraries/fabric-shim/test/unit/test-ca.base64
@@ -0,0 +1,11 @@
+LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrekNCOVFJSkFKMFo4akZpUFBpOU1Bb0dD
+Q3FHU000OUJBTUNNQTR4RERBS0JnTlZCQU1NQTNSc2N6QWUKRncweU1EQTFNRGN4TmpNME1qRmFG
+dzB6TURBMU1EVXhOak0wTWpGYU1BNHhEREFLQmdOVkJBTU1BM1JzY3pDQgptekFRQmdjcWhrak9Q
+UUlCQmdVcmdRUUFJd09CaGdBRUFDb1VTM3pnOVFqNUNnUWVOQ1krOXNQTTJZV1lIVVVRClNCRS9v
+WXBncldWOEU4VHF0V2tXY2h3WFA0T29aQXE3Yk1KMmJOUUU1U3E2SVkrYlpyWXBPS2pmQVNweVM0
+cVIKNHhKZkN1bjdCSVpBallIdlZxbWN1RjhhSmFmaDhGOTNHQmprSUxIZ0hUcnRMTHNBcTZzQnB6
+RXVWSmxzdWYxaApMaEtuQ0FxdmZFdEMxSUJWTUFvR0NDcUdTTTQ5QkFNQ0E0R01BRENCaUFKQ0FO
+R1VwNDU5UDNhTWh0VFpkWEZxCm1jOFFWTTdySFIzWmxpOWttV3NHVmRKdmJVYnVIY1g2KzBBVTFT
+OFIyRGhQQThvZUJ1UVQ3ZGJvdllmd2V1VmIKUWZSM0FrSUFwYUtlc2lOOUxOeTlhclNCR1hURktK
+cXVCVDYzdjRiaTRmeUNOaGhkQzN3eEtldHNKMURDcElkcgpCbDhabmNKeFVNakNyZDdCTmxrQk5Q
+N2pDR2RLcFBrPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
diff --git a/libraries/fabric-shim/test/unit/test-cert.base64 b/libraries/fabric-shim/test/unit/test-cert.base64
new file mode 100644
index 00000000..0181cedc
--- /dev/null
+++ b/libraries/fabric-shim/test/unit/test-cert.base64
@@ -0,0 +1 @@
+LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNCOVFJSkFPZTRqL3NqV3ltRE1Bb0dDQ3FHU000OUJBTUNNQTR4RERBS0JnTlZCQU1NQTNSc2N6QWUKRncweU1EQTFNRGN4TmpNek5UUmFGdzB6TURBMU1EVXhOak16TlRSYU1BNHhEREFLQmdOVkJBTU1BM1JzY3pDQgptekFRQmdjcWhrak9QUUlCQmdVcmdRUUFJd09CaGdBRUFPcllIem5BZmVXZ3pVM3dVZ1Q1Ylk5NkUvT2ZMb3p4CitlUUFIL2Y5cDV3TUxuUzliMTJZczBHWitTNEdjSEYva1FBNlpoMVZBcFdKYnJ6MjFYVWhSbU5QQVRibDd3K2cKbytyazJ2cVZ5Y0E3dU1tUERlL0J2MVlldEh1WXZCd05vajVLVm9vVnVpUnJPOUlkU2N3ZUxkai9WOXoyQUkvQgpmUC9iN01aYWFwbUdUQjVFTUFvR0NDcUdTTTQ5QkFNQ0E0R0xBRENCaHdKQmVZQ1ZPclFWR3dmb1Q3OWRqRWpqCm1YVkVIL3hWcGk4b1ZhWkxVRm0yN2RldkYwb1ViZHowZSt2MzhZdkx6aERnWWh2MUtMQzhnYWxxaFdleTI2MmkKVVcwQ1FnRWRrUHFOYUZlbjF0WEQ5RWJoTjhSdVRyQWE3RGphNzZ3SWVMZUZSdFloZ0hCZlMvZmM0VWR3N1hWbgpQcG9nQ0xhM0ZMNkNDSUZIQWEyTU9kc3VMeld4V2c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
\ No newline at end of file
diff --git a/libraries/fabric-shim/test/unit/test-cert.pem b/libraries/fabric-shim/test/unit/test-cert.pem
index 0181cedc..11719b04 100644
--- a/libraries/fabric-shim/test/unit/test-cert.pem
+++ b/libraries/fabric-shim/test/unit/test-cert.pem
@@ -1 +1,11 @@
-LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrakNCOVFJSkFPZTRqL3NqV3ltRE1Bb0dDQ3FHU000OUJBTUNNQTR4RERBS0JnTlZCQU1NQTNSc2N6QWUKRncweU1EQTFNRGN4TmpNek5UUmFGdzB6TURBMU1EVXhOak16TlRSYU1BNHhEREFLQmdOVkJBTU1BM1JzY3pDQgptekFRQmdjcWhrak9QUUlCQmdVcmdRUUFJd09CaGdBRUFPcllIem5BZmVXZ3pVM3dVZ1Q1Ylk5NkUvT2ZMb3p4CitlUUFIL2Y5cDV3TUxuUzliMTJZczBHWitTNEdjSEYva1FBNlpoMVZBcFdKYnJ6MjFYVWhSbU5QQVRibDd3K2cKbytyazJ2cVZ5Y0E3dU1tUERlL0J2MVlldEh1WXZCd05vajVLVm9vVnVpUnJPOUlkU2N3ZUxkai9WOXoyQUkvQgpmUC9iN01aYWFwbUdUQjVFTUFvR0NDcUdTTTQ5QkFNQ0E0R0xBRENCaHdKQmVZQ1ZPclFWR3dmb1Q3OWRqRWpqCm1YVkVIL3hWcGk4b1ZhWkxVRm0yN2RldkYwb1ViZHowZSt2MzhZdkx6aERnWWh2MUtMQzhnYWxxaFdleTI2MmkKVVcwQ1FnRWRrUHFOYUZlbjF0WEQ5RWJoTjhSdVRyQWE3RGphNzZ3SWVMZUZSdFloZ0hCZlMvZmM0VWR3N1hWbgpQcG9nQ0xhM0ZMNkNDSUZIQWEyTU9kc3VMeld4V2c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
\ No newline at end of file
+-----BEGIN CERTIFICATE-----
+MIIBkjCB9QIJAOe4j/sjWymDMAoGCCqGSM49BAMCMA4xDDAKBgNVBAMMA3RsczAe
+Fw0yMDA1MDcxNjMzNTRaFw0zMDA1MDUxNjMzNTRaMA4xDDAKBgNVBAMMA3RsczCB
+mzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAOrYHznAfeWgzU3wUgT5bY96E/OfLozx
++eQAH/f9p5wMLnS9b12Ys0GZ+S4GcHF/kQA6Zh1VApWJbrz21XUhRmNPATbl7w+g
+o+rk2vqVycA7uMmPDe/Bv1YetHuYvBwNoj5KVooVuiRrO9IdScweLdj/V9z2AI/B
+fP/b7MZaapmGTB5EMAoGCCqGSM49BAMCA4GLADCBhwJBeYCVOrQVGwfoT79djEjj
+mXVEH/xVpi8oVaZLUFm27devF0oUbdz0e+v38YvLzhDgYhv1KLC8galqhWey262i
+UW0CQgEdkPqNaFen1tXD9EbhN8RuTrAa7Dja76wIeLeFRtYhgHBfS/fc4Udw7XVn
+PpogCLa3FL6CCIFHAa2MOdsuLzWxWg==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/libraries/fabric-shim/test/unit/test-key.base64 b/libraries/fabric-shim/test/unit/test-key.base64
new file mode 100644
index 00000000..d9368608
--- /dev/null
+++ b/libraries/fabric-shim/test/unit/test-key.base64
@@ -0,0 +1 @@
+LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1JSGNBZ0VCQkVJQmZXcmJQam9pdHcwd3AwUjA3dHdKeDhxaDZGenhpVFArekpFNEZHZ3EvTXh6Sy9kdUhIN2YKRjNzOWtmM1dkcWxYNlkwTnM2K3VRR2hmK2laODZtd01zaU9nQndZRks0RUVBQ09oZ1lrRGdZWUFCQURxMkI4NQp3SDNsb00xTjhGSUUrVzJQZWhQem55Nk04Zm5rQUIvMy9hZWNEQzUwdlc5ZG1MTkJtZmt1Qm5CeGY1RUFPbVlkClZRS1ZpVzY4OXRWMUlVWmpUd0UyNWU4UG9LUHE1TnI2bGNuQU83akpqdzN2d2I5V0hyUjdtTHdjRGFJK1NsYUsKRmJva2F6dlNIVW5NSGkzWS8xZmM5Z0NQd1h6LzIrekdXbXFaaGt3ZVJBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==
\ No newline at end of file
diff --git a/libraries/fabric-shim/test/unit/test-key.pem b/libraries/fabric-shim/test/unit/test-key.pem
index d9368608..f42cbdc2 100644
--- a/libraries/fabric-shim/test/unit/test-key.pem
+++ b/libraries/fabric-shim/test/unit/test-key.pem
@@ -1 +1,7 @@
-LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1JSGNBZ0VCQkVJQmZXcmJQam9pdHcwd3AwUjA3dHdKeDhxaDZGenhpVFArekpFNEZHZ3EvTXh6Sy9kdUhIN2YKRjNzOWtmM1dkcWxYNlkwTnM2K3VRR2hmK2laODZtd01zaU9nQndZRks0RUVBQ09oZ1lrRGdZWUFCQURxMkI4NQp3SDNsb00xTjhGSUUrVzJQZWhQem55Nk04Zm5rQUIvMy9hZWNEQzUwdlc5ZG1MTkJtZmt1Qm5CeGY1RUFPbVlkClZRS1ZpVzY4OXRWMUlVWmpUd0UyNWU4UG9LUHE1TnI2bGNuQU83akpqdzN2d2I5V0hyUjdtTHdjRGFJK1NsYUsKRmJva2F6dlNIVW5NSGkzWS8xZmM5Z0NQd1h6LzIrekdXbXFaaGt3ZVJBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQ==
\ No newline at end of file
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBfWrbPjoitw0wp0R07twJx8qh6FzxiTP+zJE4FGgq/MxzK/duHH7f
+F3s9kf3WdqlX6Y0Ns6+uQGhf+iZ86mwMsiOgBwYFK4EEACOhgYkDgYYABADq2B85
+wH3loM1N8FIE+W2PehPzny6M8fnkAB/3/aecDC50vW9dmLNBmfkuBnBxf5EAOmYd
+VQKViW689tV1IUZjTwE25e8PoKPq5Nr6lcnAO7jJjw3vwb9WHrR7mLwcDaI+SlaK
+FbokazvSHUnMHi3Y/1fc9gCPwXz/2+zGWmqZhkweRA==
+-----END EC PRIVATE KEY-----
\ No newline at end of file
diff --git a/libraries/fabric-shim/types/index.d.ts b/libraries/fabric-shim/types/index.d.ts
index b11dee63..64749079 100644
--- a/libraries/fabric-shim/types/index.d.ts
+++ b/libraries/fabric-shim/types/index.d.ts
@@ -45,6 +45,24 @@ declare module 'fabric-shim' {
static newLogger(name: string): Logger;
static start(chaincode: ChaincodeInterface): any;
static success(payload?: Uint8Array): ChaincodeResponse;
+ static server(chaincode: ChaincodeInterface, serverOpts: ChaincodeServerOpts): ChaincodeServer;
+ }
+
+ export class ChaincodeServer {
+ constructor(chaincode: ChaincodeInterface, serverOpts: ChaincodeServerOpts);
+ start(): Promise;
+ }
+
+ export interface ChaincodeServerOpts {
+ ccid: string;
+ address: string;
+ tlsProps: ChaincodeServerTLSProperties;
+ }
+
+ export interface ChaincodeServerTLSProperties {
+ key: Buffer;
+ cert: Buffer;
+ clientCACerts: Buffer;
}
export class ClientIdentity implements IClientIdentity {
diff --git a/test/chaincodes/server/.dockerignore b/test/chaincodes/server/.dockerignore
new file mode 100644
index 00000000..f8b6bbd6
--- /dev/null
+++ b/test/chaincodes/server/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+package.lock.json
+package
diff --git a/test/chaincodes/server/Dockerfile b/test/chaincodes/server/Dockerfile
new file mode 100644
index 00000000..8dbb1a7e
--- /dev/null
+++ b/test/chaincodes/server/Dockerfile
@@ -0,0 +1,7 @@
+FROM hyperledger/fabric-nodeenv:latest
+
+ADD . /opt/chaincode
+RUN cd /opt/chaincode; npm install
+
+WORKDIR /opt/chaincode
+ENTRYPOINT ["npm", "start"]
diff --git a/test/chaincodes/server/index.js b/test/chaincodes/server/index.js
new file mode 100644
index 00000000..7d1accc5
--- /dev/null
+++ b/test/chaincodes/server/index.js
@@ -0,0 +1,30 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+"use strict";
+
+const { Contract } = require('fabric-contract-api');
+
+class ServerTestChaincode extends Contract {
+ async unknownTransaction({stub}) {
+ const {fcn, params} = stub.getFunctionAndParameters();
+ throw new Error(`Could not find chaincode function: ${fcn}`);
+ }
+
+ constructor() {
+ super('org.mynamespace.server');
+ }
+
+ async putValue(ctx, value) {
+ await ctx.stub.putState('state1', Buffer.from(JSON.stringify(value)));
+ }
+
+ async getValue(ctx) {
+ const value = await ctx.stub.getState('state1');
+ return JSON.parse(value.toString());
+ }
+}
+
+exports.contracts = [ ServerTestChaincode ];
diff --git a/test/chaincodes/server/package.json b/test/chaincodes/server/package.json
new file mode 100644
index 00000000..997f8322
--- /dev/null
+++ b/test/chaincodes/server/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "chaincode",
+ "description": "Chaincode server",
+ "engines": {
+ "node": "^12.13.0",
+ "npm": ">=5.3.0"
+ },
+ "scripts": {
+ "start": "fabric-chaincode-node server"
+ },
+ "main": "index.js",
+ "engine-strict": true,
+ "engineStrict": true,
+ "version": "1.0.0",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "fabric-shim": "2.1.3-unstable",
+ "fabric-contract-api": "2.1.3-unstable"
+ }
+}
diff --git a/test/chaincodes/server/package/connection.json b/test/chaincodes/server/package/connection.json
new file mode 100644
index 00000000..96f2571f
--- /dev/null
+++ b/test/chaincodes/server/package/connection.json
@@ -0,0 +1,5 @@
+{
+ "address": "cc-server:9999",
+ "dial_timeout": "10s",
+ "tls_required": false
+}
diff --git a/test/chaincodes/server/package/metadata.json b/test/chaincodes/server/package/metadata.json
new file mode 100644
index 00000000..31b9bba2
--- /dev/null
+++ b/test/chaincodes/server/package/metadata.json
@@ -0,0 +1,5 @@
+{
+ "path": "",
+ "type": "external",
+ "label": "server_v0"
+}
diff --git a/test/e2e/scenario.js b/test/e2e/scenario.js
index 86c7aafd..a825327c 100644
--- a/test/e2e/scenario.js
+++ b/test/e2e/scenario.js
@@ -185,4 +185,7 @@ const installChaincode = async () => {
]);
};
-exports.default = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
+const clientTests = series(installChaincode, instantiateChaincode, invokeFunctions, queryFunctions);
+const serverTests = require('./server').default;
+
+exports.default = series(clientTests, serverTests);
diff --git a/test/e2e/server.js b/test/e2e/server.js
new file mode 100644
index 00000000..e3cddc28
--- /dev/null
+++ b/test/e2e/server.js
@@ -0,0 +1,165 @@
+/*
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+*/
+'use strict';
+
+const {series} = require('gulp');
+
+const util = require('util');
+const path = require('path');
+
+const { shell: runcmds , getTLSArgs, getPeerAddresses } = require('toolchain');
+const ip = require('ip');
+
+const CHANNEL_NAME = 'mychannel';
+
+const chaincodeDir = path.join(__dirname, '..', '..', 'test', 'chaincodes', 'server');
+
+async function packageChaincode() {
+ await runcmds([
+ util.format(
+ 'tar -C %s/package -cvzf %s/package/code.tar.gz connection.json',
+ chaincodeDir, chaincodeDir
+ ),
+ util.format(
+ 'tar -C %s/package -cvzf %s/package/chaincode.tar.gz code.tar.gz metadata.json',
+ chaincodeDir, chaincodeDir
+ ),
+ ]);
+}
+
+async function buildChaincode() {
+ const npmrc = path.join(chaincodeDir, '.npmrc');
+
+ await runcmds([
+ `echo "registry=http://${ip.address()}:4873" > ${npmrc}`,
+ util.format(
+ 'docker build --no-cache -t chaincode-e2e-server %s',
+ chaincodeDir
+ ),
+ `rm -f ${npmrc}`
+ ]);
+}
+
+async function installChaincode() {
+ const peerInstall = 'peer lifecycle chaincode install /opt/gopath/src/github.com/chaincode/server/package/chaincode.tar.gz';
+
+ await runcmds([
+ util.format(
+ 'docker exec %s %s',
+ 'org1_cli',
+ peerInstall
+ ),
+ util.format(
+ 'docker exec %s %s',
+ 'org2_cli',
+ peerInstall
+ )
+ ]);
+};
+
+function findPackageId(queryOutput, label) {
+ const output = JSON.parse(queryOutput);
+
+ const cc = output.installed_chaincodes.filter((chaincode) => chaincode.label === label);
+ if (cc.length !== 1) {
+ throw new Error('Failed to find installed chaincode');
+ }
+
+ return cc[0].package_id;
+}
+
+async function instantiateChaincode() {
+ const endorsementPolicy = '"OR (\'Org1MSP.member\', \'Org2MSP.member\')"';
+ const queryInstalled = util.format(
+ 'peer lifecycle chaincode queryinstalled --output json'
+ );
+ const sequence = 1;
+
+ const approveChaincode = util.format(
+ 'peer lifecycle chaincode approveformyorg -o %s %s -C %s -n %s -v %s --package-id %s --sequence %d --signature-policy %s',
+ 'orderer.example.com:7050',
+ getTLSArgs(),
+ CHANNEL_NAME,
+ 'server',
+ 'v0',
+ '%s', // To be filled in for each org
+ sequence,
+ endorsementPolicy
+ );
+
+ const outputs = await runcmds([
+ util.format(
+ 'docker exec %s %s',
+ 'org1_cli',
+ queryInstalled
+ ),
+ util.format(
+ 'docker exec %s %s',
+ 'org2_cli',
+ queryInstalled
+ ),
+ ]);
+
+ const packageIdOrg1 = findPackageId(outputs[0], 'server_v0');
+ const packageIdOrg2 = findPackageId(outputs[1], 'server_v0');
+
+ // TODO: Assuming the two package IDs are the same
+ await runcmds([
+ // Start the CC Server container
+ `docker run -e CORE_CHAINCODE_ID=${packageIdOrg1} -e CORE_CHAINCODE_ADDRESS=0.0.0.0:9999 -h cc-server --name cc-server -d --network node_default chaincode-e2e-server`,
+ // Approve the chaincode definition by each org
+ util.format('docker exec %s %s',
+ 'org1_cli',
+ util.format(approveChaincode, packageIdOrg1)
+ ),
+ util.format('docker exec %s %s',
+ 'org2_cli',
+ util.format(approveChaincode, packageIdOrg2)
+ ),
+ // Commit the chaincode definition
+ util.format('docker exec org1_cli peer lifecycle chaincode commit -o %s %s -C %s -n %s -v %s --sequence %d --signature-policy %s %s',
+ 'orderer.example.com:7050',
+ getTLSArgs(),
+ CHANNEL_NAME,
+ 'server',
+ 'v0',
+ sequence,
+ endorsementPolicy,
+ getPeerAddresses()
+ )
+ ]);
+}
+
+const invokeFunctions = async () => {
+ const args = util.format('docker exec org1_cli peer chaincode invoke %s -C %s -n %s -c %s --waitForEvent',
+ getTLSArgs(),
+ CHANNEL_NAME,
+ 'server',
+ '\'{"Args":["putValue","\'42\'"]}\'');
+
+ await runcmds([args]);
+};
+
+const queryFunctions = async () => {
+ const args = util.format('docker exec org1_cli peer chaincode query %s -C %s -n %s -c %s',
+ getTLSArgs(),
+ CHANNEL_NAME,
+ 'server',
+ '\'{"Args":["getValue"]}\'');
+
+ const ret = await runcmds([args]);
+
+ const response = JSON.parse(ret[0]);
+
+ if (response !== 42) {
+ throw new Error("Unexpected result from chaincode");
+ }
+}
+
+exports.default = series(
+ packageChaincode, buildChaincode, installChaincode, instantiateChaincode,
+ invokeFunctions, queryFunctions
+);
diff --git a/tools/toolchain/fabric.js b/tools/toolchain/fabric.js
index 66b79846..d1b44f44 100644
--- a/tools/toolchain/fabric.js
+++ b/tools/toolchain/fabric.js
@@ -78,9 +78,12 @@ const _docker_clean = async () => {
// stop and remove chaincode docker instances
'docker kill $(docker ps | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
'docker rm $(docker ps -a | grep "dev-peer0.org[12].example.com" | awk \'{print $1}\') || echo ok',
+ 'docker kill $(docker ps | grep "cc-server" | awk \'{print $1}\') || echo ok',
+ 'docker rm $(docker ps -a | grep "cc-server" | awk \'{print $1}\') || echo ok',
// remove chaincode images so that they get rebuilt during test
'docker rmi $(docker images | grep "^dev-peer0.org[12].example.com" | awk \'{print $3}\') || echo ok',
+ 'docker rmi $(docker images | grep "^chaincode-e2e-server" | awk \'{print $3}\') || echo ok',
// clean up all the containers created by docker-compose
util.format('docker-compose -f %s down --volumes', fs.realpathSync(path.join(dockerComposeDir, 'docker-compose-cli.yaml'))),
@@ -140,6 +143,10 @@ const _generate_config = async () => {
'docker exec cli cp /etc/hyperledger/fabric/core.yaml %s',
dockerCfgPath
),
+ util.format(
+ 'docker exec cli sed -i \'s/externalBuilders: \\[\\]/externalBuilders: [{path: \\/opt\\/chaincode, name: test}]/\' %s/core.yaml',
+ dockerCfgPath
+ ),
util.format(
'docker exec cli sh %s/rename_sk.sh',
dockerCfgPath
@@ -197,10 +204,18 @@ async function _channel_create() {
]);
}
+async function _peer_setup() {
+ // Install the 'jq' command in the peer containers to run external builder scripts.
+ await runcmds([
+ 'docker exec peer0.org1.example.com apk add jq',
+ 'docker exec peer0.org2.example.com apk add jq',
+ ]);
+}
+
const channelSetup = series(_channel_create, _channel_init);
// --
-const startFabric = series(dockerReady, channelSetup);
+const startFabric = series(dockerReady, _peer_setup, channelSetup);
exports.default = startFabric;
exports.stopFabric = series(_docker_clean);
diff --git a/tools/toolchain/network/docker-compose/docker-compose-base.yaml b/tools/toolchain/network/docker-compose/docker-compose-base.yaml
index a1047260..02e4a6eb 100644
--- a/tools/toolchain/network/docker-compose/docker-compose-base.yaml
+++ b/tools/toolchain/network/docker-compose/docker-compose-base.yaml
@@ -99,6 +99,8 @@ services:
command: peer node start --peer-chaincodedev=${DOCKER_DEVMODE}
volumes:
- /var/run/:/host/var/run/
+ - ../external:/opt/chaincode/bin:ro
+ - ../crypto-material/core.yaml:/etc/hyperledger/fabric/core.yaml:ro
clibase:
extends:
diff --git a/tools/toolchain/network/external/build b/tools/toolchain/network/external/build
new file mode 100755
index 00000000..6773ec16
--- /dev/null
+++ b/tools/toolchain/network/external/build
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+SOURCE="$1"
+OUTPUT="$3"
+
+if [ ! -f "${SOURCE}/connection.json" ]; then
+ echo "Error: ${SOURCE}/connection.json not found" 1>&2
+ exit 1
+fi
+
+cp "${SOURCE}/connection.json" "${OUTPUT}/connection.json"
+
+if [ -d "${SOURCE}/metadata" ]; then
+ cp -a ${SOURCE}/metadata ${OUTPUT}/metadata
+fi
+
+exit 0
diff --git a/tools/toolchain/network/external/detect b/tools/toolchain/network/external/detect
new file mode 100755
index 00000000..6ef69b54
--- /dev/null
+++ b/tools/toolchain/network/external/detect
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+METADIR="$2"
+
+if [ `jq -r .type "${METADIR}/metadata.json"` = "external" ]; then
+ exit 0
+fi
+
+exit 1
diff --git a/tools/toolchain/network/external/release b/tools/toolchain/network/external/release
new file mode 100755
index 00000000..b4ea54c1
--- /dev/null
+++ b/tools/toolchain/network/external/release
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright Hitachi America, Ltd. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+BUILD="$1"
+RELEASE="$2"
+
+if [ -d "${BUILD}/metadata" ]; then
+ cp -a "${BUILD}/metadata/*" "${RELEASE}/"
+fi
+
+if [ -f "${BUILD}/connection.json" ]; then
+ mkdir -p "${RELEASE}/chaincode/server"
+ cp "${BUILD}/connection.json" "${RELEASE}/chaincode/server"
+
+ # TODO: TLS
+
+ exit 0
+fi