Skip to content

Commit

Permalink
Cheery-pick chaincode server feature to release-2.x (#166)
Browse files Browse the repository at this point in the history
* [FABCN-408] Separate message handler from client (#155)

This patch divides the ChaincodeSupportClient class into two classes:
ChaincodeSupportClient, which represents a chaincode client that
connects to a peer, and ChaincodeMessageHandler, which handles messages
in communciation with the peer through a stream.

Because the latter part is common in both client and server model,
the ChaincodeMessageHandler class will be used in the future
implementation for a chaincode gRPC server.

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

Co-authored-by: Matthew B White <mbwhite@users.noreply.github.com>

* [FABCN-409] Chaincode gRPC server w/o TLS (#159)

This patch adds the ChaincodeServer class to support the server mode of
chaincode.
It also adds the server() method, which creates a new instance of
the ChaincodeServer class, to the Shim class, the entrypoint of the
fabric-shim library.

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

* [FABCN-411] Add server command to CLI (#161)

This patch adds a "server" command to the "fabric-chaincode-node" CLI.
The command starts the contracts as a chaincode server.

Example: fabric-chaincode-node server --chaincode-address 0.0.0.0:9999 \
           --chaincode-id mycc_v0:a1233bb13227a05932

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

* [FABCN-413] Add e2e test for chaincode server (#162)

This patch adds new e2e test for the chaincode gRPC server feature.

The test performs as following:
  - Create a package which contains server and cc information
  - Build a container image of the chaincode
  - Install the package into peers
  - Obtain the installed package ID from the peers
  - Start the chaincode container with the package ID
  - Approve and commit the chaincode definition
  - Invoke and query the chaincode

"rush test:e2e" will perform both tests for both server and client mode.

This patch also modifies "rush start-fabric" to use external builder
scripts.

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

* [FABCN-414] Update TypeScript definition (#165)

This patch updates the type defintion file for TypeScript to add classes
and interfaces related to the chaincode server feature.

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

* [FABCN-412] TLS support for chaincode server (#164)

This patch adds TLS support for chaincode server.

To enable TLS, set tlsProps in the second argument for shim.server,
or add --chaincode-tls-cert-file and --chaincode-tls.key-file for CLI.

Client certificate validation can be enabled via tlsProps.clientCACerts
for shim.server or --chaincode-tls-client-cacert-file for CLI.

Also the -path options (for base64 encoded files) are supported.

Signed-off-by: Taku Shimosawa <taku.shimosawa@hal.hitachi.com>

Co-authored-by: Matthew B White <mbwhite@users.noreply.github.com>
  • Loading branch information
shimos and mbwhite authored Jun 17, 2020
1 parent ed41336 commit 577fde4
Show file tree
Hide file tree
Showing 30 changed files with 1,308 additions and 99 deletions.
27 changes: 25 additions & 2 deletions libraries/fabric-shim/lib/chaincode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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 <code>ChaincodeInterface</code>
* @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
Expand Down
106 changes: 106 additions & 0 deletions libraries/fabric-shim/lib/cmds/serverCommand.js
Original file line number Diff line number Diff line change
@@ -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;
};
19 changes: 13 additions & 6 deletions libraries/fabric-shim/lib/contract-spi/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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) {
Expand Down
45 changes: 35 additions & 10 deletions libraries/fabric-shim/lib/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 : {}';
}
}

Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 577fde4

Please sign in to comment.