bsp-network
Version:
SDK for writing node.js applications to interact with bsp network. This package encapsulates the APIs to connect to a bsp network, submit transactions and perform queries against the ledger.
280 lines (269 loc) • 12 kB
JavaScript
/**
* Copyright 2018, 2019 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { NetworkImpl: Network } = require('./network');
const NetworkConfig = require('./impl/ccp/networkconfig');
const { Client } = require('fabric-common');
const EventStrategies = require('./impl/event/defaulteventhandlerstrategies');
const QueryStrategies = require('./impl/query/defaultqueryhandlerstrategies');
const logger = require('./logger').getLogger('Gateway');
const grpc = require('@grpc/grpc-js');
/**
* @typedef {Object} Gateway~GatewayOptions
* @memberof module:fabric-network
* @property {module:fabric-network.Wallet} wallet The identity wallet implementation for use with
* this Gateway instance.
* @property {string} identity The identity in the wallet for all interactions on this Gateway instance.
* @property {string} [clientTlsIdentity] The identity in the wallet to use as the client TLS identity.
* @property {module:fabric-network.Gateway~TransactionOptions} [transaction]
* Options for the default event handler capability.
* @property {module:fabric-network.Gateway~QueryOptions} [query]
* Options for the default query handler capability.
* @property {module:fabric-network.Gateway~DiscoveryOptions} [discovery] Discovery options.
*/
/**
* @typedef {Object} Gateway~TransactionOptions
* @memberof module:fabric-network
* @property {number} [commitTimeout = 300] The timeout period in seconds to wait
* for commit notification to complete.
* @property {number} [endorseTimeout = 30] The timeout period in seconds to wait
* for the endorsement to complete.
* @property {?module:fabric-network.TxEventHandlerFactory} [strategy=MSPID_SCOPE_ALLFORTX]
* Event handling strategy to identify successful transaction commits. A null value indicates
* that no event handling is desired. The default is
* [MSPID_SCOPE_ALLFORTX]{@link module:fabric-network.EventHandlerStrategies}.
*/
/**
* @typedef {Object} Gateway~QueryOptions
* @memberof module:fabric-network
* @property {number} [timeout = 30] The timeout period in seconds to wait for the query to
* complete.
* @property {module:fabric-network.QueryHandlerFactory} [strategy=MSPID_SCOPE_SINGLE]
* Query handling strategy used to evaluate queries. The default is
* [MSPID_SCOPE_SINGLE]{@link module:fabric-network.QueryHandlerStrategies}.
*/
/**
* @typedef {Object} Gateway~DiscoveryOptions
* @memberof module:fabric-network
* @property {boolean} [enabled=true] True if discovery should be used; otherwise false.
* @property {boolean} [asLocalhost=false] Convert discovered host addresses to be 'localhost'.
* Will be needed when running a docker composed fabric network on the local system;
* otherwise should be disabled.
*/
/**
* The gateway peer provides the connection point for an application to access the Fabric network.
* It is instantiated using the default constructor.
* It can then be connected to a fabric network using the [connect]{@link #connect} method by
* passing either a common connection profile definition or an existing {@link Client} object.
* Once connected, it can then access individual Network instances (channels) using the
* [getNetwork]{@link #getNetwork} method which in turn can access the
* [smart contracts]{@link Contract} installed on a network and
* [submit transactions]{@link Contract#submitTransaction} to the ledger.
* @memberof module:fabric-network
*/
class Gateway {
static _mergeOptions(defaultOptions, suppliedOptions) {
for (const prop in suppliedOptions) {
if (typeof suppliedOptions[prop] === 'object' && suppliedOptions[prop] !== null) {
if (defaultOptions[prop] === undefined) {
defaultOptions[prop] = suppliedOptions[prop];
}
else {
Gateway._mergeOptions(defaultOptions[prop], suppliedOptions[prop]);
}
}
else {
defaultOptions[prop] = suppliedOptions[prop];
}
}
}
constructor() {
logger.debug('in Gateway constructor');
this.client = null;
this.wallet = null;
this.identityContext = null;
this.networks = new Map();
this.identity = null;
// initial options - override with the connect()
this.options = {
queryHandlerOptions: {
timeout: 30,
strategy: QueryStrategies.MSPID_SCOPE_SINGLE
},
eventHandlerOptions: {
endorseTimeout: 30,
commitTimeout: 300,
strategy: EventStrategies.MSPID_SCOPE_ALLFORTX
},
discovery: {
enabled: true,
asLocalhost: true
}
};
}
/**
* Connect to the Gateway with a connection profile or a prebuilt Client instance.
* @async
* @param {(string|object|Client)} config The configuration for this Gateway which can be:
* <ul>
* <li>A fully qualified common connection profile file path (String)</li>
* <li>A common connection profile JSON (Object)</li>
* <li>A pre-configured client instance</li>
* </ul>
* @param {module:fabric-network.Gateway~GatewayOptions} options - specific options
* for creating this Gateway instance
* @example
* const gateway = new Gateway();
* const wallet = new FileSystemWallet('./WALLETS/wallet');
* const ccpFile = fs.readFileSync('./network.json');
* const ccp = JSON.parse(ccpFile.toString());
* await gateway.connect(ccp, {
* identity: 'admin',
* wallet: wallet
* });
*/
async connect(config, options) {
const method = 'connect';
logger.debug('%s - start', method);
// grpc and pb Instance
const BspTransactionApiGrpc = require('./bsp_transaction_grpc_pb');
const BspTransactionApiPb = require('./bsp_transaction_pb');
// Create a new Aggregator Client for interacting with the aggregator.
// Create a new Query Client for interacting with the queryexecutor.
// select only the first aggregator
var aggKeys = Object.keys(config.aggregators);
var firstAggKey = aggKeys[0];
// setup for grpc client
const aggURL = config.aggregators[firstAggKey].url;
const aggCli = new BspTransactionApiGrpc.AggregatorClient(aggURL, grpc.credentials.createInsecure());
const queryCli = new BspTransactionApiGrpc.QueryExecutorClient(aggURL, grpc.credentials.createInsecure());
// setup for policy
const policy = config.aggregators[firstAggKey].policy;
// setup for client
const cid = config.client.url;
const shardid = config.client.shardid;
const clientid = config.client.clientid;
// setup for grpc client
this.BspTransactionApiPb = BspTransactionApiPb;
this.aggCli = aggCli;
this.queryCli = queryCli;
// setup for client
this.cid = cid;
this.shardid = shardid;
this.clientid = clientid;
// setup for policy
this.policy = policy;
if (!options || !options.wallet) {
logger.error('%s - A wallet must be assigned to a Gateway instance', method);
throw new Error('A wallet must be assigned to a Gateway instance');
}
Gateway._mergeOptions(this.options, options);
logger.debug('connection options: %j', options);
let load_ccp = false;
if (config && config.type === 'Client') {
// initialize from an existing Client object instance
logger.debug('%s - using existing client object', method);
this.client = config;
}
else {
// build a new client and once it has been configured with
// the passed in options it will be loaded with the ccp
this.client = new Client('gateway client');
load_ccp = true;
}
// setup an initial identity for the Gateway
if (options.identity) {
logger.debug('%s - setting identity', method);
this.identity = await this._getWalletIdentity(options.identity);
const provider = options.wallet.getProviderRegistry().getProvider(this.identity.type);
const user = await provider.getUserContext(this.identity, options.identity);
this.identityContext = this.client.newIdentityContext(user);
}
if (options.clientTlsIdentity) {
logger.debug('%s - setting tlsIdentity', method);
const tlsIdentity = await this._getWalletIdentity(options.clientTlsIdentity);
this.client.setTlsClientCertAndKey(tlsIdentity.credentials.certificate, tlsIdentity.credentials.privateKey);
}
else if (options.tlsInfo) {
logger.debug('%s - setting tlsInfo', method);
this.client.setTlsClientCertAndKey(options.tlsInfo.certificate, options.tlsInfo.key);
}
else {
logger.debug('%s - using self signed setting for tls', method);
this.client.setTlsClientCertAndKey();
}
if (load_ccp) {
logger.debug('%s - NetworkConfig loading client from ccp', method);
await NetworkConfig.loadFromConfig(this.client, config);
}
else {
logger.debug('%s - no connection profile - using client instance', method);
}
// apply any connection options to the client instance for use
// internally by the client instance when building a complete set
// of connection options for an endpoint
// these will be merged with those from the config (default.json)
if (options['connection-options']) {
this.client.centralized_options = options['connection-options'];
logger.debug('%s - assigned connection options');
}
logger.debug('%s - end', method);
}
async _getWalletIdentity(label) {
const identity = await this.options.wallet.get(label);
if (!identity) {
throw new Error(`Identity not found in wallet: ${label}`);
}
return identity;
}
/**
* Get the identity associated with the gateway connection.
* @returns {module:fabric-network.Identity} An identity.
*/
getIdentity() {
return this.identity;
}
/**
* Returns the set of options associated with the Gateway connection
* @returns {module:fabric-network.Gateway~GatewayOptions} The Gateway connection options
*/
getOptions() {
logger.debug('in getOptions');
return this.options;
}
/**
* Clean up and disconnect this Gateway connection in preparation for it to be discarded and garbage collected
*/
disconnect() {
logger.debug('in disconnect');
for (const network of this.networks.values()) {
network._dispose();
}
this.networks.clear();
}
/**
* Returns an object representing a network
* @param {string} networkName The name of the network (channel name)
* @returns {module:fabric-network.Network}
*/
async getNetwork(networkName) {
const method = 'getNetwork';
logger.debug('%s - start', method);
const existingNetwork = this.networks.get(networkName);
if (existingNetwork) {
logger.debug('%s - returning existing network:%s', method, networkName);
return existingNetwork;
}
logger.debug('%s - create network object and initialize', method);
const channel = this.client.getChannel(networkName);
const newNetwork = new Network(this, channel);
await newNetwork._initialize(this.options.discovery);
this.networks.set(networkName, newNetwork);
return newNetwork;
}
}
module.exports = Gateway;
//# sourceMappingURL=gateway.js.map