UNPKG

@siglesiasg/node-red-hyperledger-fabric-gateway

Version:

Hyperledger Fabric 2.x nodes for node-red with latest fabric-gateway library

274 lines (228 loc) 9.23 kB
import * as grpc from '@grpc/grpc-js'; import { connect, ConnectOptions, Gateway, GrpcClient, Identity, ProposalOptions, Signer, signers } from '@hyperledger/fabric-gateway'; import { Buffer } from 'buffer'; import * as crypto from 'crypto'; import { promises as fs } from 'fs'; import { Node, NodeAPI, NodeDef, NodeMessageInFlow } from 'node-red'; import { ConnectionConfigModel } from 'src/models/connection-config.model'; import { ChaincodeNodeDef } from 'src/nodes/chaincode-config-node.def'; import { FabricChannelDef } from 'src/nodes/config/fabric-channel.def'; import { closeGateway, getGateway } from './fabric-connection-pool'; import { FabricDecoderType } from './fabric-decoder'; import { addResultToPayload, getConfigValidate } from './node-red-utils'; export async function newGrpcConnection(connectionConfig: ConnectionConfigModel): Promise<GrpcClient> { let tlsCredentials; if (connectionConfig.gateway.peer.tls) { const tlsRootCert = Buffer.from(Buffer.from(connectionConfig.gateway.peer.tls, 'base64').toString('utf8'), 'utf8'); tlsCredentials = grpc.credentials.createSsl(tlsRootCert); } else { tlsCredentials = grpc.credentials.createInsecure(); } return new grpc.Client(connectionConfig.gateway.peer.url, tlsCredentials, connectionConfig.gateway.peer.grpcOptions as any); } export async function newIdentity(connectionConfig: ConnectionConfigModel): Promise<{ identity: Identity, signer: Signer }> { let credentials; let privateKey; if (connectionConfig.identity.certType === 'embeded') { try { credentials = Buffer.from(Buffer.from(connectionConfig.identity.cert, 'base64').toString('utf8'), 'utf8'); const privateKeyPem = Buffer.from(connectionConfig.identity.privateKey, 'base64').toString('utf8'); privateKey = crypto.createPrivateKey(privateKeyPem); } catch (err) { throw new Error(`Unable to build identity from embeded cert. Are certs in base64?. ${err}`); } } else if (connectionConfig.identity.certType === 'files') { credentials = await fs.readFile(connectionConfig.identity.certPath); const privateKeyPem = await fs.readFile(connectionConfig.identity.privateKeyPath); privateKey = crypto.createPrivateKey(privateKeyPem); } else if (connectionConfig.identity.certType === 'microfab') { try { const dataufab = await fetch(connectionConfig.identity.microfabUrl + '/ak/api/v1/components'); const bodyJson = await dataufab.json(); const ufabIdentity = findCertById(bodyJson, connectionConfig.identity.microfabId); credentials = Buffer.from(Buffer.from(ufabIdentity.cert, 'base64').toString('utf8'), 'utf8'); const privateKeyPem = Buffer.from(ufabIdentity.private_key, 'base64').toString('utf8'); privateKey = crypto.createPrivateKey(privateKeyPem); } catch (err) { throw new Error(`Unable to build identity from microfab. ${err}`); } } else { throw new Error('Unable to get identity with cert type: ' + connectionConfig.identity.certType + '. Not implemented'); } return { identity: { mspId: connectionConfig.identity.mspId.mspId, credentials }, signer: signers.newPrivateKeySigner(privateKey) }; } export async function buildGatewayConnection(grpcClient: GrpcClient, connectionConfig: ConnectionConfigModel): Promise<Gateway> { const identity = await newIdentity(connectionConfig); const options: ConnectOptions = { identity: identity.identity, signer: identity.signer, client: grpcClient, evaluateOptions: () => { return { deadline: Date.now() + 5000 }; // 5 seconds }, endorseOptions: () => { return { deadline: Date.now() + 15000 }; // 15 seconds }, submitOptions: () => { return { deadline: Date.now() + 5000 }; // 5 seconds }, commitStatusOptions: () => { return { deadline: Date.now() + 60000 }; // 1 minute }, }; return connect(options); } export function getTransactionName( configTransaction: string, payload: { transactionName?: string } | { transaction: { name: string} }, ) { const payAsAny = payload as any; let transactionName; if (payAsAny && payAsAny.transactionName) { transactionName = payAsAny.transactionName; } else if (payAsAny && payAsAny.transaction?.name) { transactionName = payAsAny.transaction?.name; } else { transactionName = configTransaction; } if (!transactionName) { throw Error('Transaction name not defined in config nor payload'); } return transactionName; } /** * Return the value if it is defined; otherwise thrown an error. * @param value A value that might not be defined. * @param message Error message if the value is not defined. */ export function assertDefined<T>(value: T | null | undefined, message: string): T { if (value === undefined) { throw new Error(message); } return value; } export async function closeConnection(this: Node<NodeDef>, connection: ConnectionConfigModel, done: () => void) { try { await closeGateway(this, connection); this.status({ fill: 'grey', shape: 'dot', text: 'Closed' }); this.debug('Closed'); done(); } catch (error: any) { this.status({ fill: 'red', shape: 'dot', text: error }); done(); } } export async function invokeChaincode( RED: NodeAPI, node: Node<NodeDef>, msg: NodeMessageInFlow, decoder: FabricDecoderType, actionType: string, connection: ConnectionConfigModel, config: ChaincodeNodeDef, ) { const channelName = getConfigValidate(RED, config.channelSelector).channel; const contractName = getConfigValidate(RED, config.contractSelector).contract; const transactionName = getTransactionName(config.transaction, msg.payload); const proposal = getTransactionData(config.args, config.transientData, msg.payload); return await invokeChaincodeInternal(RED, node, msg, decoder, actionType, connection, channelName, contractName, transactionName, proposal); } export async function invokeChaincodeGeneric( RED: NodeAPI, node: Node<NodeDef>, msg: NodeMessageInFlow, decoder: FabricDecoderType, actionType: string, connection: ConnectionConfigModel, channelSelector: string, contractName: string, transactionName: string, args?: string[], ) { const fabricChannelDef: FabricChannelDef = getConfigValidate(RED, channelSelector); const proposal: ProposalOptions = {arguments: args}; return await invokeChaincodeInternal(RED, node, msg, decoder, actionType, connection, fabricChannelDef.channel, contractName, transactionName, proposal); } async function invokeChaincodeInternal( RED: NodeAPI, node: Node<NodeDef>, msg: NodeMessageInFlow, decoder: FabricDecoderType, actionType: string, connection: ConnectionConfigModel, channelName: string, contractName: string, transactionName: string, proposal: ProposalOptions, ) { const gateway = await getGateway(node, connection); const network = gateway.getNetwork(channelName); const contract = network.getContract(contractName); let getResult; if (actionType === 'submit') { getResult = await contract.submit(transactionName, proposal); } else if (actionType === 'evaluate') { getResult = await contract.evaluate(transactionName, proposal); } else { throw new Error('Undefined action type: ' + actionType); } addResultToPayload(RED, msg, transactionName, proposal, decoder(getResult)); } function getTransactionData( configArgs: string, configTransient: string, payload: { transaction?: { args?: string[], transient?: string[] }} ): ProposalOptions { return { arguments: getTransactionArg(), transientData: getTransactionTransient() }; function getTransactionArg() { let transactionArgs; if (payload?.transaction?.args) { transactionArgs = payload.transaction.args; } else if (payload?.transaction) { transactionArgs = payload.transaction; } else if (configArgs) { try { transactionArgs = JSON.parse(configArgs); } catch (e) { throw new Error('Configuration Args is not an array! Check config'); } } else { transactionArgs = payload; } if (transactionArgs && !Array.isArray(transactionArgs)) { throw new Error('Configuration Args is not an array!. Check payload config.'); } return transactionArgs; } function getTransactionTransient() { let transientData; if (payload?.transaction?.transient) { transientData = payload.transaction.transient; } else if (configTransient) { try { transientData = JSON.parse(configTransient); } catch (e) { throw new Error('Transient args is not an object! Check config'); } } if (transientData && !Array.isArray(transientData)) { throw new Error('Transient args is not an array. Check payload.transaction.transient'); } return transientData; } } function findCertById(data: any, id: string) { try { for (const item of data) { if (item.id === id && item.type === 'identity') { return item; } } } catch (err) { throw new Error('Unable to get Identity from microfab. Id: ' + id + '. Error: ' + err); } throw new Error('Unable to get Identity from microfab. Id: ' + id); }