Skip to content

Commit

Permalink
FABN-1083 NodeSDK allow commit/endorse handler
Browse files Browse the repository at this point in the history
Document how to use the commit and endorse handlers.
The sendTransaction method will be enhanced to call
the commit handler when defined and initialized.
The sendTransactionProposal method will be enhanced to
call the endorsement handler when defined and initialized.

Change-Id: I579ecfe446886f92380e5ec125554d7147d4da43
Signed-off-by: Bret Harrison <[email protected]>
  • Loading branch information
harrisob committed Jan 24, 2019
1 parent 317ed37 commit 058d51a
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 97 deletions.
71 changes: 10 additions & 61 deletions docs/tutorials/discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ To use the service the application will have to connect with just one peer.
* `channel.initialize()` - This method has been enhanced by adding an option to
query a peer using the new service discovery to initialize the channel object.
This method may be call at anytime to reinitialize the channel. When using discovery,
this may be used to assign a new target peer providing the discover service.
this may be used to assign a new target peer providing the discover service.
The initialize() method is also required to instantiate the handlers, by default
the handlers shipped with the fabric-client are designed to use the discovery
results.
* `channel.sendTransactionProposal()` - This method has been enhanced to use the
discovered peers to send the endorsement proposal.
* `channel.sendTransaction()` - This method has been enhanced to use the discovered
Expand Down Expand Up @@ -65,42 +68,6 @@ a custom handler to be used. This handler is used in the `sendTransaction` metho
to determine the orderers and how to send the transaction to be committed.
(default 'fabric-client/lib/impl/BasicCommitHandler.js')

### new `EndorsementHandler`
The sending of a proposal to be endorsed may be done using custom code. The
fabric-client will use by default the file called `DiscoveryEndorsementHandler`.
A different endorsement handler may be used by changing the configuration setting
"endorsement-handler" with the `setConfigSetting()` or placing a new line
in configuration JSON file that application has applied to the fabric-client
configuration. This will instantiate a handler
located at the path provide in the attribute for all channels initialized
after the call.
The handler may also be changed using the `endorsementHandler` attribute on the
`channel.initialize()` request call parameter. This will instantiate a handler
located at the path provide in the attribute just for this channel.
```
// set value in memory
Client.setConfigSetting('endorsement-handler', '/path/to/the/handler.js');
--or--
// the path to an additional config file
Client.addConfigFile('/path/to/config.json');
// the json file contains the following line
// "endorsement-handler": "/path/to/the/handler.js"
--or--
const request = {
target: peer,
discovery: true,
endorsementHandler: "/path/to/the/handler.js",
...
}
// request object contains the path to the handler
channel.initialize(request);
```
A endorsement handler must implement the `api.EndorsementHandler`. When the
channel is instantiated, the channel will read the path setting and create an
instance of the handler for use by the new channel instance.



#### How the `DiscoveryEndorsementHandler` works
The `sendTransactionProposal` will use the peers included in the "targets" to
endorse the proposal. If there is no "targets" parameter, the endorsement request
Expand Down Expand Up @@ -164,26 +131,6 @@ selected will likely change on every request.
Note: If the above behavior does not meet the needs of your organization a
custom handler may be used.

### new `CommitHandler`
The sending of the endorsements to be committed may be done using custom code.
The fabric-client will use by default the file called `BasicCommitHandler`.
The commit handler may be changed by changing the configuration setting
"commit-handler" by doing a `setConfigSetting()` or placing a new line
in configuration JSON file that application has applied to the fabric-client
configuration.
```
// set the config value in memory
Client.setConfigSetting('commit-handler', '/path/to/the/handler.js');
--or--
// path of an additional config file
Client.addConfigFile('/path/to/config.json');
// the json file contains the following line
// "commit-handler": "/path/to/the/handler.js"
```
A commit handler must implement the `api.CommitHandler`. When the
channel is instantiated, the channel will read the path setting and create an
instance of the handler for use by the new channel instance.

#### How the `BasicCommitHandler` works
The default handler that comes with the fabric-client will send to one orderer at
a time until it receives a successful submission of the transaction. Sending
Expand All @@ -193,17 +140,19 @@ sender has the authority to send the request. The response from the orderer
will indicate that the orderer has accepted the request. The `sendTransaction`
has an optional parameter `orderer` that indicates the orderer to send the
transaction. The handler will use the orderer as specified with the `orderer`
parameter and send to any other orderers. If no orderer is specified the handler
parameter and not send to any other orderers. If no orderer is specified the handler
will get the list of orderers assigned to the channel. These orderers may have
been assigned manually to the channel with a `channel.addOrderer()` call or
automatically when using the service discovery.
assigned automatically when using the service discovery.

### To Initialize


By default the fabric-client will not use the service discovery. To enable the
use of the service, set the config setting to true or use the discover parameter
on the `initialize()` call.

note: {@link Channel#initialize} must be run to both enable discovery and to
startup the handlers.

```
Client.setConfigSetting('initialize-with-discovery', true);
--or--
Expand Down
124 changes: 124 additions & 0 deletions docs/tutorials/handlers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@

This tutorial illustrates the use of the handlers by the Hyperledger Fabric Node.js Client as of 1.3.

For more information on:
* getting started with Hyperledger Fabric see
[Building your first network](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html).
* the configuration of a channel in Hyperledger Fabric and the internal
process of creating and updating see
[Hyperledger Fabric channel configuration](http://hyperledger-fabric.readthedocs.io/en/latest/configtx.html)
* [Service Discovery](https://hyperledger-fabric.readthedocs.io/en/latest/discovery-overview.html)

The following assumes an understanding of the Hyperledger Fabric network
(orderers and peers),
and of Node application development, including the use of the
Javascript `promise` and `async await`.

### Overview
The fabric-client provides the ability for custom code that will handle the
endorsement process and the submitting of endorsements to the orderer.
There are two plug points defined, one on the {@link Channel#sendTransactionProposal}
and one on the {@link Channel#sendTransaction}. The fabric-client will pass
control to the handler to complete the processing. The custom code may
decide to retry, try another end point, or use discovery to complete the
task.

see {@tutorial discovery} on how the default handlers are
used with discovery.

#### Modified API's that will use handlers
* `channel.initialize()` - This method has been enhanced to instantiate
instances of the handlers for use by the channel. The method will get the
paths from the system configuration settings to create and initialize them.
* `channel.sendTransactionProposal()` - This method has been enhanced to use
an `endorsement-handler` if one has been instantiated and initialized.
* `channel.sendTransaction()` - This method has been enhanced to use
a `commit-handler` if one has been instantiated and initialized.



#### New configuration settings
* `endorsement-handler` - string - The path to the endorsement handler. Allows for a
custom handler to be used. This handler is used in the
{@linkcode Channel#sendTransactionProposal sendTransactionProposal()}
method to determine the target peers and how to send the proposal.
(default 'fabric-client/lib/impl/DiscoveryEndorsementHandler.js')
* `commit-handler` - string - The path to the commit handler. Allows for
a custom handler to be used. This handler is used in the
{@linkcode Channel#sendTransaction sendTransaction()} method
to determine the orderers and how to send the transaction to be committed.
(default 'fabric-client/lib/impl/BasicCommitHandler.js')

### new Endorsement Handler
The sending of a proposal to be endorsed may be done using custom code. The
fabric-client will use by default the file called `DiscoveryEndorsementHandler`.
A different endorsement handler may be used by changing the configuration setting
"endorsement-handler" with the `setConfigSetting()` or placing a new line
in configuration JSON file that application has applied to the fabric-client
configuration. This will instantiate a handler
located at the path provide in the attribute for all channels initialized
after the call.
The default handler was designed to be used with discovery to provided automatic
selection of peers and fail over. When used without discovery the handler will
only send to the peers as defined in the targets parameter without fail over.
The handler may also be changed using the `endorsementHandler` attribute on the
`channel.initialize()` request call parameter. This will instantiate a handler
located at the path provide in the attribute just for this channel.
```
// set value in memory
Client.setConfigSetting('endorsement-handler', '/path/to/the/handler.js');
--or--
// the path to an additional config file
Client.addConfigFile('/path/to/config.json');
// the json file contains the following line
// "endorsement-handler": "/path/to/the/handler.js"
--or--
const request = {
...
endorsementHandler: "/path/to/the/handler.js",
...
}
// initialize must be run to use handlers.
channel.initialize(request);
```
A endorsement handler should extend the {@link EndorsementHandler}. When the
channel is initialized, the channel will read the path setting and create an
instance of the handler for use by the new channel instance.

### new `CommitHandler`
The sending of the endorsements to be committed may be done using custom code.
The fabric-client will use by default the file called `BasicCommitHandler`.
The commit handler may be changed by changing the configuration setting
"commit-handler" by doing a `setConfigSetting()` or placing a new line
in configuration JSON file that application has applied to the fabric-client
configuration.
The default handler was designed to be used with discovery to provided automatic
selection of orderers and fail over. When used without discovery the handler will
still provide fail over to all orderers assigned to the channel, sending to
each one in orderer until an orderer response successfully to the transaction
submission.
The handler may also be changed using the `commitHandler` attribute on the
`channel.initialize()` request call parameter. This will instantiate a handler
located at the path provide in the attribute just for this channel.
```
// set the config value in memory
Client.setConfigSetting('commit-handler', '/path/to/the/handler.js');
--or--
// path of an additional config file
Client.addConfigFile('/path/to/config.json');
// the json file contains the following line
// "commit-handler": "/path/to/the/handler.js"
--or--
const request = {
...
commitHandler: "/path/to/the/handler.js",
...
}
// initialize must be run to use handlers.
channel.initialize(request);
```
A commit handler should extend the {@link CommitHandler}. When the
channel is initialized, the channel will read the path setting and create an
instance of the handler for use by the new channel instance.

<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
3 changes: 3 additions & 0 deletions docs/tutorials/tutorials.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@
},
"grpc-settings": {
"title": "fabric-client: How to set gRPC settings"
},
"handlers": {
"title": "fabric-client: How to use the endorsement and commit handlers"
}
}
68 changes: 41 additions & 27 deletions fabric-client/lib/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const Channel = class {
this._last_discover_timestamp = null;
this._use_discovery = sdk_utils.getConfigSetting('initialize-with-discovery', false);
this._as_localhost = sdk_utils.getConfigSetting('discovery-as-localhost', true);
this._endorsement_handler = null; // will be setup during initialization
this._endorsement_handler = null;
this._commit_handler = null;

logger.debug('Constructed Channel instance: name - %s, network mode: %s', this._name, !this._devMode);
Expand All @@ -134,7 +134,7 @@ const Channel = class {
* When used with `targets` parameter, the peer referenced here will be
* added to the `targets` array.
* Default is to use the first ChannelPeer assigned to this channel.
* @property {Array.<(Peer | ChannelPeer)>} targets - Optional. The target peers to be used
* @property {string[] | Peer[] | ChannelPeer[]} targets - Optional. The target peers to be used
* to make the initialization requests for configuration information.
* When used with `target` parameter, the peer referenced there will be
* added to the `targets` array.
Expand Down Expand Up @@ -165,6 +165,10 @@ const Channel = class {
* This method retrieves the configuration from the orderer if no "config" parameter is passed in.
* Optionally a configuration may be passed in to initialize this channel without making the call
* to the orderer.
* <br><br>
* This method will also automatically load orderers and peers that represent
* the fabric network when using discovery. This application must provide a
* peer running the fabric discovery service.
*
* @param {InitializeRequest} request - Optional. a {@link InitializeRequest}
* @return {Promise} A Promise that will resolve when the action is complete
Expand Down Expand Up @@ -210,25 +214,9 @@ const Channel = class {
}
}

// setup the endorsement handler
if (!endorsement_handler_path && this._use_discovery) {
endorsement_handler_path = sdk_utils.getConfigSetting('endorsement-handler');
logger.debug('%s - using config setting for endorsement handler ::%s', method, endorsement_handler_path);
}
if (endorsement_handler_path) {
this._endorsement_handler = require(endorsement_handler_path).create(this);
await this._endorsement_handler.initialize();
}

// setup the commit handler
if (!commit_handler_path) {
commit_handler_path = sdk_utils.getConfigSetting('commit-handler');
logger.debug('%s - using config setting for commit handler ::%s', method, commit_handler_path);
}
if (commit_handler_path) {
this._commit_handler = require(commit_handler_path).create(this);
await this._commit_handler.initialize();
}
// setup the handlers
this._endorsement_handler = await this._build_handler(endorsement_handler_path, 'endorsement-handler');
this._commit_handler = await this._build_handler(commit_handler_path, 'commit-handler');

let results = null;
try {
Expand Down Expand Up @@ -2698,7 +2686,12 @@ const Channel = class {
logAndThrow(method, 'Missing "args" in Transaction proposal request');
}

if (!request.targets && this._endorsement_handler) {
// convert any names into peer objects or if empty find all
// endorsing peers added to this channel
request.targets = this._getTargets(request.targets, Constants.NetworkConfig.ENDORSING_PEER_ROLE);

// always use the handler if available (may not be just for discovery)
if (this._endorsement_handler) {
logger.debug('%s - running with endorsement handler', method);
const proposal = Channel._buildSignedProposal(request, this._name, this._clientContext);

Expand All @@ -2713,15 +2706,15 @@ const Channel = class {
request: request,
signed_proposal: proposal.signed,
timeout: timeout,
endorsement_hint: endorsement_hint
endorsement_hint: endorsement_hint,
use_discovery: this._use_discovery
};

const responses = await this._endorsement_handler.endorse(params);

return [responses, proposal.source];
} else {
logger.debug('%s - running without endorsement handler', method);
request.targets = this._getTargets(request.targets, Constants.NetworkConfig.ENDORSING_PEER_ROLE);

return Channel.sendTransactionProposal(request, this._name, this._clientContext, timeout);
}
Expand Down Expand Up @@ -2829,7 +2822,7 @@ const Channel = class {
* to the orderer for further processing. This is the 2nd phase of the transaction
* lifecycle in the fabric. The orderer will globally order the transactions in the
* context of this channel and deliver the resulting blocks to the committing peers for
* validation against the chaincode's endorsement policy. When the committering peers
* validation against the chaincode's endorsement policy. When the committing peers
* successfully validate the transactions, it will mark the transaction as valid inside
* the block. After all transactions in a block have been validated, and marked either as
* valid or invalid (with a [reason code]{@link https://github.com/hyperledger/fabric/blob/v1.0.0/protos/peer/transaction.proto#L125}),
Expand All @@ -2838,7 +2831,6 @@ const Channel = class {
* The caller of this method must use the proposal responses returned from the endorser along
* with the original proposal that was sent to the endorser. Both of these objects are contained
* in the {@link ProposalResponseObject} returned by calls to any of the following methods:
* <li>[installChaincode()]{@link Client#installChaincode}
* <li>[sendInstantiateProposal()]{@link Channel#sendInstantiateProposal}
* <li>[sendUpgradeProposal()]{@link Channel#sendUpgradeProposal}
* <li>[sendTransactionProposal()]{@link Channel#sendTransactionProposal}
Expand Down Expand Up @@ -2898,9 +2890,15 @@ const Channel = class {
const envelope = Channel.buildEnvelope(this._clientContext, chaincodeProposal, endorsements, proposalResponse, use_admin_signer);

if (this._commit_handler) {
// protect the users input
const param_request = Object.assign({}, request);
if (param_request.orderer) {
// check and convert to orderer object if a name
param_request.orderer = this._clientContext.getTargetOrderer(param_request.orderer, this.getOrderers(), this._name);
}
const params = {
signed_envelope: envelope,
request: request,
request: param_request,
timeout: timeout
};
return this._commit_handler.commit(params);
Expand Down Expand Up @@ -3503,6 +3501,22 @@ const Channel = class {
return JSON.stringify(state).toString();
}

async _build_handler(_handler_path, handler_name) {
const method = '_build_handler';
let handler_path = _handler_path;
let handler = null;
if (!handler_path) {
handler_path = sdk_utils.getConfigSetting(handler_name);
logger.debug('%s - using path %s for %s', method, handler_path, handler_name);
}
if (handler_path) {
handler = require(handler_path).create(this);
await handler.initialize();
logger.debug('%s - create instance of %s', method, handler_name);
}
return handler;
}

};

// internal utility method to decode and get the write set
Expand Down
Loading

0 comments on commit 058d51a

Please sign in to comment.