| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474 |
1x
1x
1x
1x
1x
1x
1x
1x
42x
10x
42x
8x
42x
12x
12x
12x
10x
6x
9x
9x
1x
1x
48x
48x
5x
5x
2x
3x
3x
1x
2x
1x
1x
1x
2x
2x
2x
10x
10x
8x
8x
1x
7x
7x
7x
7x
7x
7x
7x
7x
7x
3x
3x
1x
2x
2x
1x
1x
1x
1x
1x
1x
1x
1x
1x
3x
1x
2x
2x
2x
2x
2x
2x
5x
1x
4x
4x
4x
4x
4x
4x
1x
4x
4x
4x
2x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
4x
4x
1x
3x
1x
2x
1x
1x
1x
4x
4x
1x
3x
3x
3x
1x
1x
2x
2x
2x
2x
2x
2x
2x
2x
2x
1x
1x
1x
1x
1x
1x
| /*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const { Certificate, CertificateUtil, Connection, Util } = require('composer-common');
const { EmbeddedContainer, EmbeddedContext, EmbeddedDataService } = require('composer-runtime-embedded');
const EmbeddedSecurityContext = require('./embeddedsecuritycontext');
const { Engine, InstalledBusinessNetwork } = require('composer-runtime');
const uuid = require('uuid');
// A map of installed chaincodes keyed by name and version
const installedChaincodes = new Map();
// A mapping of business networks to chaincode IDs.
const businessNetworks = {};
// A mapping of chaincode IDs to their instance objects.
const chaincodes = {};
/**
* Base class representing a connection to a business network.
* @protected
* @abstract
*/
class EmbeddedConnection extends Connection {
/**
* Clear any registered business networks and chaincodes.
*/
static reset() {
for (let id in businessNetworks) {
delete businessNetworks[id];
}
for (let id in chaincodes) {
delete chaincodes[id];
}
installedChaincodes.clear();
}
/**
* Add chaincode package to the list of installed chaincodes, keyed off of the name and version only.
* @param {BusinessNetworkDefinition} businessNetworkDefinition the business network definition which defines the chaincode
*/
static addInstalledChaincode(businessNetworkDefinition) {
const key = `${businessNetworkDefinition.getName()}@${businessNetworkDefinition.getVersion()}`;
installedChaincodes.set(key, businessNetworkDefinition);
}
/**
* Retrieve a business network definition chaincode keyed off name and version.∂
* @param {string} name The name of the chaincode to retrieve
* @param {string} version The version of the chaincode to retrieve
* @returns {BusinessNetworkDefinition} A business network definition chaincode or null if not found.
*/
static getInstalledChaincode(name, version) {
return installedChaincodes.get(`${name}@${version}`);
}
/**
* Add a business network.
* @param {string} businessNetworkIdentifier The business network identifier.
* @param {string} connectionProfile The connection profile name.
* @param {string} chaincodeUUID The chaincode UUID.
*/
static addBusinessNetwork(businessNetworkIdentifier, connectionProfile, chaincodeUUID) {
businessNetworks[`${businessNetworkIdentifier}@${connectionProfile}`] = chaincodeUUID;
}
/**
* Get the specified business network.
* @param {string} businessNetworkIdentifier The business network identifier.
* @param {string} connectionProfile The connection profile name.
* @return {string} The chaincode UUID.
*/
static getBusinessNetwork(businessNetworkIdentifier, connectionProfile) {
return businessNetworks[`${businessNetworkIdentifier}@${connectionProfile}`];
}
/**
* Add a chaincode.
* @param {string} chaincodeUUID The chaincode UUID.
* @param {Container} container The container.
* @param {Engine} engine The engine.
* @param {InstalledBusinessNetwork} ibn The Installed Business Network
*/
static addChaincode(chaincodeUUID, container, engine, ibn) {
chaincodes[chaincodeUUID] = {
uuid: chaincodeUUID,
container: container,
engine: engine,
installedBusinessNetwork : ibn
};
}
/**
* Get the specified chaincode.
* @param {string} chaincodeUUID The chaincode UUID.
* @return {object} The chaincode.
*/
static getChaincode(chaincodeUUID) {
return chaincodes[chaincodeUUID];
}
/**
* Create a new container.
* @return {Container} The new container.
*/
static createContainer() {
return new EmbeddedContainer();
}
/**
* Create a new engine.
* @param {Container} container The container.
* @return {Engine} The new engine.
*/
static createEngine(container) {
return new Engine(container);
}
/**
* Constructor.
* @param {ConnectionManager} connectionManager The owning connection manager.
* @param {string} connectionProfile The name of the connection profile associated with this connection
* @param {string} businessNetworkIdentifier The identifier of the business network for this connection
*/
constructor(connectionManager, connectionProfile, businessNetworkIdentifier) {
super(connectionManager, connectionProfile, businessNetworkIdentifier);
this.dataService = new EmbeddedDataService(null, true);
}
/**
* Terminate the connection to the business network.
*/
async disconnect() {
}
/**
* Login as a participant on the business network.
* @param {string} enrollmentID The enrollment ID of the participant.
* @param {string} enrollmentSecret The enrollment secret of the participant.
* @return {Promise} A promise that is resolved with a {@link SecurityContext}
* object representing the logged in participant, or rejected with a login error.
*/
async login(enrollmentID, enrollmentSecret) {
const identity = await this.testIdentity(enrollmentID, enrollmentSecret);
// no businessNetworkIdentitier implies fabric identity, not an identity
// that can be enrolled and should already be imported.
if (!this.businessNetworkIdentifier) {
return new EmbeddedSecurityContext(this, identity);
}
let chaincodeUUID = EmbeddedConnection.getBusinessNetwork(this.businessNetworkIdentifier, this.connectionProfile);
if (!chaincodeUUID) {
throw new Error(`No chaincode ID found for business network '${this.businessNetworkIdentifier}'`);
}
// simulate enrolment and import which would occur on a login if
// no imported credentials. Note that no enrolment counting is done in
// this simulation.
if (!identity.imported) {
identity.imported = true;
const identities = await this.getIdentities();
await identities.update(enrollmentID, identity);
}
const result = new EmbeddedSecurityContext(this, identity);
result.setChaincodeID(chaincodeUUID);
return result;
}
/**
* For the embedded connector, this is just a no-op, there is nothing to install. *** I Don't think this is true now ***
* @param {SecurityContext} securityContext The participant's security context.
* @param {string} businessNetworkDefinition The business network definition that will be started
* @param {Object} installOptions connector specific installation options
* @returns {Promise} returns a resolved promise to indicate success
*/
async install(securityContext, businessNetworkDefinition, installOptions) {
EmbeddedConnection.addInstalledChaincode(businessNetworkDefinition);
return Promise.resolve();
}
/**
* Start a business network.
* @param {HFCSecurityContext} securityContext The participant's security context.
* @param {string} businessNetworkIdentifier The identifier of the Business network that will be started in this installed runtime
* @param {string} businessNetworkVersion The version of the Business network that will be started in this installed runtime
* @param {string} startTransaction The serialized start transaction.
* @param {Object} startOptions connector specific start options
*/
async start(securityContext, businessNetworkIdentifier, businessNetworkVersion, startTransaction, startOptions) {
const installedChaincode = EmbeddedConnection.getInstalledChaincode(businessNetworkIdentifier, businessNetworkVersion);
if (!installedChaincode) {
throw new Error(`${businessNetworkIdentifier} version ${businessNetworkVersion} has not been installed`);
}
const installedBusinessNetwork = await InstalledBusinessNetwork.newInstance(installedChaincode);
const container = EmbeddedConnection.createContainer();
const identity = securityContext.getIdentity();
const chaincodeUUID = container.getUUID();
const engine = EmbeddedConnection.createEngine(container);
EmbeddedConnection.addBusinessNetwork(businessNetworkIdentifier, this.connectionProfile, chaincodeUUID);
EmbeddedConnection.addChaincode(chaincodeUUID, container, engine, installedBusinessNetwork);
let context = new EmbeddedContext(engine, identity, this, installedBusinessNetwork);
await engine.init(context, 'start', [startTransaction]);
}
/**
* Upgrade a business network. This connector implementation effectively allows you to
* switch between installed chaincodes, and doesn't complain even if you switch to the
* same chaincode.
* @param {HFCSecurityContext} securityContext The participant's security context.
* @param {string} businessNetworkIdentifier The name of the business network to upgrade.
* @param {String} businessNetworkVersion The version of the business network to upgrade.
* @param {Object} upgradeOptions connector specific start options
* @async
*/
async upgrade(securityContext, businessNetworkIdentifier, businessNetworkVersion, upgradeOptions) {
let installedChaincode = EmbeddedConnection.getInstalledChaincode(businessNetworkIdentifier, businessNetworkVersion);
if (!installedChaincode) {
throw new Error(`${businessNetworkIdentifier} version ${businessNetworkVersion} has not been installed`);
}
let chaincodeUUID = EmbeddedConnection.getBusinessNetwork(businessNetworkIdentifier, this.connectionProfile);
if (!chaincodeUUID) {
throw new Error(`Unable to upgrade, ${businessNetworkIdentifier} has not been started`);
}
const {container, engine} = EmbeddedConnection.getChaincode(chaincodeUUID);
let installedBusinessNetwork = await InstalledBusinessNetwork.newInstance(installedChaincode);
// This adds or replaces
EmbeddedConnection.addChaincode(chaincodeUUID, container, engine, installedBusinessNetwork);
const identity = securityContext.getIdentity();
let context = new EmbeddedContext(engine, identity, this, installedBusinessNetwork);
await engine.init(context, 'upgrade');
}
/**
* Test ("ping") the connection to the business network.
* @param {SecurityContext} securityContext The participant's security context.
* @return {Promise} A promise that is resolved once the connection to the
* business network has been tested, or rejected with an error.
*/
async ping(securityContext) {
const buffer = await this.queryChainCode(securityContext, 'ping', []);
return JSON.parse(buffer.toString());
}
/**
* Invoke a "query" chaincode function with the specified name and arguments.
* @param {SecurityContext} securityContext The participant's security context.
* @param {string} functionName The name of the chaincode function to invoke.
* @param {string[]} args The arguments to pass to the chaincode function.
* @return {Buffer} A buffer containing the data returned by the chaincode function,
* or null if no data was returned.
*/
async queryChainCode(securityContext, functionName, args) {
if (!this.businessNetworkIdentifier) {
throw new Error('No business network has been specified for this connection');
}
let identity = securityContext.getIdentity();
let chaincodeUUID = securityContext.getChaincodeID();
let chaincode = EmbeddedConnection.getChaincode(chaincodeUUID);
let context = new EmbeddedContext(chaincode.engine, identity, this, chaincode.installedBusinessNetwork);
const data = await chaincode.engine.query(context, functionName, args);
return !Util.isNull(data) ? Buffer.from(JSON.stringify(data)) : null;
}
/**
* Invoke a "invoke" chaincode function with the specified name and arguments.
* @param {SecurityContext} securityContext The participant's security context.
* @param {string} functionName The name of the chaincode function to invoke.
* @param {string[]} args The arguments to pass to the chaincode function.
* @param {Object} [additionalConnectorOptions] Additional connector specific options for this transaction.
* @return {Buffer} A buffer containing the data returned by the chaincode function,
* or null if no data was returned.
*/
async invokeChainCode(securityContext, functionName, args, additionalConnectorOptions = {}) {
if (!this.businessNetworkIdentifier) {
throw new Error('No business network has been specified for this connection');
}
let identity = securityContext.getIdentity();
let chaincodeUUID = securityContext.getChaincodeID();
let chaincode = EmbeddedConnection.getChaincode(chaincodeUUID);
let context = new EmbeddedContext(chaincode.engine, identity, this, chaincode.installedBusinessNetwork, additionalConnectorOptions);
const data = await chaincode.engine.invoke(context, functionName, args);
return !Util.isNull(data) ? Buffer.from(JSON.stringify(data)) : null;
}
/**
* Get the data collection that stores identities.
* @return {Promise} A promise that is resolved with the data collection
* that stores identities.
*/
async getIdentities() {
return await this.dataService.ensureCollection('identities');
}
/**
* Get the identity for the specified name.
* @param {string} identityName The name for the identity.
* @return {Promise} A promise that is resolved with the identity, or
* rejected with an error.
*/
async getIdentity(identityName) {
const identities = await this.getIdentities();
try {
return await identities.get(identityName);
} catch (error) {
if (identityName === 'admin') {
return await this._createAdminIdentity();
}
throw error;
}
}
/**
* Create the default admin identity.
* @return {Promise} A promise that is resolved with the admin identity when complete,
* or rejected with an error.
*/
async _createAdminIdentity() {
const { publicKey, privateKey, certificate } = CertificateUtil.generate({ commonName: 'admin' });
const certificateObj = new Certificate(certificate);
const identifier = certificateObj.getIdentifier();
const name = certificateObj.getName();
const issuer = certificateObj.getIssuer();
const identity = {
identifier,
name,
issuer,
secret: 'adminpw',
certificate,
publicKey,
privateKey,
imported: false,
options: {
issuer: true
}
};
const identities = await this.getIdentities();
await identities.add('admin', identity);
return identity;
}
/**
* Test the specified identity name and secret to ensure that it is valid.
* admin userid doesn't require a secret.
* @param {string} identityName The name for the identity.
* @param {string} identitySecret The secret for the identity.
* @return {Promise} A promise that is resolved if the user ID and secret
* is valid, or rejected with an error.
*/
async testIdentity(identityName, identitySecret) {
const identity = await this.getIdentity(identityName);
if (identity.imported) {
return identity;
} else if (identityName === 'admin') {
return identity;
} else if (identity.secret !== identitySecret) {
throw new Error(`The secret ${identitySecret} specified for the identity ${identityName} does not match the stored secret ${identity.secret}`);
} else {
return identity;
}
}
/**
* Return whether a registry check is required before executing createIdentity to prevent duplicates.
* @return {boolean} true.
*/
registryCheckRequired() {
return true;
}
/**
* Create a new identity for the specified name.
* @param {SecurityContext} securityContext The participant's security context.
* @param {string} identityName The name for the new identity.
* @param {object} [options] Options for the new identity.
* @param {boolean} [options.issuer] Whether or not the new identity should have
* permissions to create additional new identities. False by default.
* @param {string} [options.affiliation] Specify the affiliation for the new
* identity. Defaults to 'institution_a'.
* @return {Promise} A promise that is resolved with a generated user
* secret once the new identity has been created, or rejected with an error.
*/
async createIdentity(securityContext, identityName, options) {
const currentIdentity = securityContext.getIdentity();
if (!currentIdentity.options.issuer) {
throw new Error(`The identity ${currentIdentity.name} does not have permission to create a new identity ${identityName}`);
}
const identities = await this.getIdentities();
const exists = await identities.exists(identityName);
if (exists) {
const identity = await identities.get(identityName);
return {
userID: identityName,
userSecret: identity.secret
};
}
const { publicKey, privateKey, certificate } = CertificateUtil.generate({ commonName: identityName });
const certificateObj = new Certificate(certificate);
const identifier = certificateObj.getIdentifier();
const name = certificateObj.getName();
const issuer = certificateObj.getIssuer();
const secret = uuid.v4().substring(0, 8);
const identity = {
identifier,
name,
issuer,
secret,
certificate,
publicKey,
privateKey,
imported: false,
options: options || {}
};
await identities.add(identityName, identity);
return {
userID: identityName,
userSecret: identity.secret
};
}
/**
* Create a new transaction id
* Note: as this is not a real fabric it returns null to let the composer-common use uuid to create one.
* @param {SecurityContext} securityContext The participant's security context.
* @return {Promise} A promise that is resolved with a null
*/
async createTransactionId(securityContext) {
return null;
}
/**
* Undeploy a business network definition.
* @param {SecurityContext} securityContext The participant's security context.
* @param {String} networkName Name of the business network to remove
* @async
*/
async undeploy(securityContext, networkName) {
await this.dataService.removeAllData();
delete businessNetworks[networkName];
delete chaincodes[networkName];
}
/**
* Get the native API for this connection. The native API returned is specific
* to the underlying blockchain platform, and may throw an error if there is no
* native API available.
*/
getNativeAPI() {
throw new Error('native API not available when using the embedded connector');
}
}
module.exports = EmbeddedConnection;
|