From e80093d78b7fce952fe692ca19c0bf8f9b7e8409 Mon Sep 17 00:00:00 2001 From: kth-tw <88102592+kth-tw@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:26:15 +0800 Subject: [PATCH] add discover to `bdk chaincode invoke` --- cicd/test_script/steps/chaincode.sh | 2 +- docs/COMMANDS.md | 24 +++---- src/command/chaincode/invoke.ts | 101 +++++++++++++++++++++------- src/model/type/chaincode.type.ts | 11 ++- src/service/chaincode.ts | 61 +++++++++++++++-- 5 files changed, 155 insertions(+), 44 deletions(-) diff --git a/cicd/test_script/steps/chaincode.sh b/cicd/test_script/steps/chaincode.sh index 28683c87..edc23147 100644 --- a/cicd/test_script/steps/chaincode.sh +++ b/cicd/test_script/steps/chaincode.sh @@ -21,7 +21,7 @@ bdk chaincode commit -C ${CHANNEL_NAME} -l ${CHAINCODE_LABEL} -I # discover # invoke init export_env 'peer' ${PEER_ORG_NAME_ORG0} ${PEER_ORG_DOMAIN_ORG0} 'peer0' -bdk chaincode invoke -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -I -f InitLedger --orderer ${ORDERER_ORG_URL_ORG1_ORDERER0} --peer-addresses ${PEER_ORG_URL_ORG0_PEER0} --peer-addresses ${PEER_ORG_URL_ORG1_PEER0} --peer-addresses ${PEER_ORG_URL_ORG2_PEER0} +bdk chaincode invoke -C ${CHANNEL_NAME} -n ${CHAINCODE_NAME} -I -f InitLedger # discover # [Org0] invoke & query export_env 'peer' ${PEER_ORG_NAME_ORG0} ${PEER_ORG_DOMAIN_ORG0} 'peer0' diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index baf6a006..bce10297 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -137,18 +137,18 @@ Description: 安裝 Chaincode Description: 執行 Chaincode function -| Options | Type | Description | Required | Default | -| ------------------------ | :-----: | -------------------------------------- | :------: | ------- | -| --help | boolean | Show help | | -| --version | boolean | Show version number | | -| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | -| -C, --channel-id | string | 選擇欲執行 Chaincode 在的 Channel 名稱 | | -| -n, --chaincode-name | string | 欲執行 Chaincode 的名稱 | | -| -I, --is-init | boolean | 是否要初始化 Chaincode | | false | -| -f, --chaincode-function | string | 執行 Chaincode 的 function | | -| -a, --args | array | 執行 Chaincode 需要的參數 | | -| --orderer | string | 選擇 Orderer 執行 Chaincode | | -| --peer-addresses | array | 需要簽名的 Peer address | | +| Options | Type | Description | Required | Default | +| ------------------------ | :-----: | ---------------------------------------------------- | :------: | ------- | +| --help | boolean | Show help | | | +| --version | boolean | Show version number | | | +| -i, --interactive | boolean | 是否使用 Cathay BDK 互動式問答 | | | +| -C, --channel-id | string | 選擇欲執行 Chaincode 在的 Channel 名稱 | | | +| -n, --chaincode-name | string | 欲執行 Chaincode 的名稱 | | | +| -I, --is-init | boolean | 是否要初始化 Chaincode | | false | +| -f, --chaincode-function | string | 執行 Chaincode 的 function | | | +| -a, --args | array | 執行 Chaincode 需要的參數 | | | +| --orderer | string | 選擇 Orderer 執行 Chaincode (若未輸入則使用discover) | | | +| --peer-addresses | array | 需要簽名的 Peer address (若未輸入則使用discover) | | | ### `bdk chaincode package` diff --git a/src/command/chaincode/invoke.ts b/src/command/chaincode/invoke.ts index bf598629..9fdc3b3a 100644 --- a/src/command/chaincode/invoke.ts +++ b/src/command/chaincode/invoke.ts @@ -1,7 +1,7 @@ import { Argv, Arguments } from 'yargs' import prompts from 'prompts' import { logger, onCancel } from '../../util' -import { ChaincodeInvokeType } from '../../model/type/chaincode.type' +import { ChaincodeInvokeType, ChaincodeInvokeWithoutDiscoverType } from '../../model/type/chaincode.type' import Channel from '../../service/channel' import Chaincode from '../../service/chaincode' import { committedChaincodeChoice, joinedChannelChoice } from '../../model/prompts/util' @@ -25,6 +25,7 @@ interface OptType { export const builder = (yargs: Argv) => { return yargs .example('bdk chaincode invoke --interactive', 'Cathay BDK 互動式問答') + .example('bdk chaincode invoke --channel-id fabcar --chaincode-name fabcar --is-init --chaincode-function InitLedger', '使用名稱為 InitLedger 的 function 初始化名稱為 fabcar 的 Chaincode,使用 discover 決定 orderer 與 peer') .example('bdk chaincode invoke --channel-id fabcar --chaincode-name fabcar --is-init --chaincode-function InitLedger --orderer orderer0.example.com:7050 --peer-addresses peer0.org1.example.com:7051 --peer-addresses peer0.org2.example.com:8051', '使用 orderer0.example.com:7050 和名稱為 InitLedger 的 function 初始化名稱為 fabcar 的 Chaincode,需要名稱為 Org1 和 Org2 的 Peer Org 簽名') .example('bdk chaincode invoke --channel-id fabcar --chaincode-name fabcar --chaincode-function CreateCar -a CAR_ORG1_PEER0 -a BMW -a X6 -a blue -a Org1 --orderer orderer0.example.com:7050 --peer-addresses peer0.org1.example.com:7051 --peer-addresses peer0.org2.example.com:8051', '使用 orderer0.example.com:7050 名稱為 fabcar 的 Chaincode 執行 CreateCar 的 function,需要 CAR_ORG1_PEER0、BMW、X6、blue、Org1 的參數和名稱為 Org1 和 Org2 的 Peer Org 簽名') .option('interactive', { type: 'boolean', description: '是否使用 Cathay BDK 互動式問答', alias: 'i' }) @@ -33,8 +34,8 @@ export const builder = (yargs: Argv) => { .option('is-init', { type: 'boolean', description: '是否要初始化 Chaincode', alias: 'I', default: false }) .option('chaincode-function', { type: 'string', description: '執行 Chaincode 的 function', alias: 'f' }) .option('args', { type: 'array', description: '執行 Chaincode 需要的參數', alias: 'a' }) - .option('orderer', { type: 'string', description: '選擇 Orderer 執行 Chaincode' }) - .option('peer-addresses', { type: 'array', description: '需要簽名的 Peer address' }) + .option('orderer', { type: 'string', description: '選擇 Orderer 執行 Chaincode (若未輸入則使用discover)' }) + .option('peer-addresses', { type: 'array', description: '需要簽名的 Peer address (若未輸入則使用discover)' }) } export const handler = async (argv: Arguments) => { @@ -43,7 +44,7 @@ export const handler = async (argv: Arguments) => { const chaincode = new Chaincode(config) const channel = new Channel(config) - let invokeChannelInput: ChaincodeInvokeType + let invokeChannelInput: ChaincodeInvokeType | ChaincodeInvokeWithoutDiscoverType if (argv.interactive) { const { channelId } = await prompts([ { @@ -54,9 +55,7 @@ export const handler = async (argv: Arguments) => { }, ], { onCancel }) - const channelGroup = await channel.getChannelGroup(channelId) - - const { chaincodeName, chaincodeFunction, args, isInit, orderer, peerAddresses } = await prompts([ + const { chaincodeName, chaincodeFunction, args, isInit } = await prompts([ { type: 'select', name: 'chaincodeName', @@ -89,27 +88,75 @@ export const handler = async (argv: Arguments) => { ], initial: 1, }, + ]) + const discoverOrderer = await prompts([ { type: 'select', - name: 'orderer', - message: 'Ordering service endpoint', - choices: channelGroup.orderer.map(x => ({ - title: x, - value: x, - })), + name: 'discoverOrderer', + message: 'Set orderer with discover?', + choices: [ + { + title: 'Yes', + value: true, + }, + { + title: 'No', + value: false, + }, + ], }, + ], { onCancel }) + + invokeChannelInput = { channelId, chaincodeName, chaincodeFunction, args, isInit } + + if (discoverOrderer) { + const channelGroup = await channel.getChannelGroup(channelId) + const { orderer } = await prompts([ + { + type: 'select', + name: 'orderer', + message: 'Ordering service endpoint', + choices: channelGroup.orderer.map(x => ({ + title: x, + value: x, + })), + }, + ], { onCancel }) + invokeChannelInput = { ...invokeChannelInput, orderer } + } + + const discoverPeer = await prompts([ { - type: 'multiselect', - name: 'peerAddresses', - message: 'The addresses of the peers to connect to', - choices: channelGroup.anchorPeer.map(x => ({ - title: x, - value: x, - })), + type: 'select', + name: 'discoverPeer', + message: 'Set peers with discover?', + choices: [ + { + title: 'Yes', + value: true, + }, + { + title: 'No', + value: false, + }, + ], }, - ]) - - invokeChannelInput = { channelId, chaincodeName, chaincodeFunction, args, isInit, orderer, peerAddresses } + ], { onCancel }) + if (discoverPeer) { + const channelGroup = await channel.getChannelGroup(channelId) + const { peerAddresses } = await prompts([ + { + type: 'multiselect', + name: 'peerAddresses', + message: 'The addresses of the peers to connect to', + choices: channelGroup.anchorPeer.map(x => ({ + title: x, + value: x, + })), + }, + ], { onCancel }) + invokeChannelInput = { ...invokeChannelInput, peerAddresses } + } } else { invokeChannelInput = { channelId: argv.channelId, @@ -117,8 +164,12 @@ export const handler = async (argv: Arguments) => { chaincodeFunction: argv.chaincodeFunction, args: argv.args || [], isInit: argv.isInit, - orderer: argv.orderer, - peerAddresses: argv.peerAddresses, + } + if (argv.orderer) { + invokeChannelInput = { ...invokeChannelInput, orderer: argv.orderer } + } + if (argv.peerAddresses) { + invokeChannelInput = { ...invokeChannelInput, peerAddresses: argv.peerAddresses } } } diff --git a/src/model/type/chaincode.type.ts b/src/model/type/chaincode.type.ts index b8a2cbcb..0852e276 100644 --- a/src/model/type/chaincode.type.ts +++ b/src/model/type/chaincode.type.ts @@ -84,11 +84,18 @@ export interface ChaincodeQueryType { /** * @requires isInit - [boolean] 是否要初始化 chaincode - * @requires orderer - [string] orderer 的 address 和 port - * @requires peerAddresses - [string array] peer address 和 port 的 array */ export interface ChaincodeInvokeType extends ChaincodeQueryType { isInit: boolean +} + +export type ChaincodeInvokeWithoutDiscoverType = ChaincodeInvokeType & ({ orderer: string } | { peerAddresses: string[] }) + +/** + * @requires orderer - [string] orderer 的 address 和 port + * @requires peerAddresses - [string array] peer address 和 port 的 array + */ +export interface ChaincodeInvokeStepInvokeOnInstanceType extends ChaincodeInvokeType { orderer: string peerAddresses: string[] } diff --git a/src/service/chaincode.ts b/src/service/chaincode.ts index f383fa5f..a550a6fc 100644 --- a/src/service/chaincode.ts +++ b/src/service/chaincode.ts @@ -1,11 +1,12 @@ import path from 'path' import FabricTools from '../instance/fabricTools' import FabricInstance from '../instance/fabricInstance' -import { ChaincodeApproveStepApproveOnInstanceType, ChaincodeApproveType, ChaincodeCommitStepCommitOnInstanceType, ChaincodeApproveWithoutDiscoverType, ChaincodeCommitType, ChaincodeInstallStepSavePackageIdType, ChaincodeInstallType, ChaincodeInvokeType, ChaincodePackageType, ChaincodeQueryType, ChaincodeCommitWithoutDiscoverType } from '../model/type/chaincode.type' +import { ChaincodeApproveStepApproveOnInstanceType, ChaincodeApproveType, ChaincodeCommitStepCommitOnInstanceType, ChaincodeApproveWithoutDiscoverType, ChaincodeCommitType, ChaincodeInstallStepSavePackageIdType, ChaincodeInstallType, ChaincodeInvokeType, ChaincodePackageType, ChaincodeQueryType, ChaincodeCommitWithoutDiscoverType, ChaincodeInvokeStepInvokeOnInstanceType, ChaincodeInvokeWithoutDiscoverType } from '../model/type/chaincode.type' import { ParserType, AbstractService } from './Service.abstract' import { logger } from '../util/logger' import { DockerResultType, InfraRunnerResultType } from '../instance/infra/InfraRunner.interface' import Discover from './discover' +import { randomFromArray } from '../util/utils' interface ChaincodeParser extends ParserType { installToPeer: (result: DockerResultType, options: {chaincodeLabel: string}) => string @@ -16,6 +17,8 @@ interface ChaincodeParser extends ParserType { approveStepDiscover: (result: DockerResultType) => string commitStepDiscoverChannelConfig: (result: DockerResultType) => string commitStepDiscoverPeers: (result: DockerResultType) => string[] + invokeStepDiscoverChannelConfig: (result: DockerResultType) => string + invokeStepDiscoverEndorsers: (result: DockerResultType) => string[] } export default class Chaincode extends AbstractService { @@ -40,6 +43,12 @@ export default class Chaincode extends AbstractService { }) return peers }, + invokeStepDiscoverChannelConfig: (result) => Discover.chooseOneRandomOrderer(Discover.parser.channelConfig(result)), + invokeStepDiscoverEndorsers: (result) => { + const endorserDiscoverResult = Discover.parser.chaincodeEndorsers(result) + const layout = randomFromArray(endorserDiscoverResult[0].Layouts) + return (Object.keys(layout.quantities_by_group).map(group => (randomFromArray(endorserDiscoverResult[0].EndorsersByGroups[group]).Endpoint))) + }, } /** @@ -56,9 +65,53 @@ export default class Chaincode extends AbstractService { * @description 執行 chaincode 上的 function 發送交易 * @returns 執行 chaincode function 的回覆 */ - public async invoke (payload: ChaincodeInvokeType): Promise { - logger.debug('invoke chaincode') - return await (new FabricInstance(this.config, this.infra)).invokeChaincode(payload.channelId, payload.chaincodeName, payload.chaincodeFunction, payload.args, payload.isInit, payload.orderer, payload.peerAddresses) + public async invoke (payload: ChaincodeInvokeType | ChaincodeInvokeWithoutDiscoverType): Promise { + let orderer: string + let peerAddresses: string[] + + if ('orderer' in payload) { + orderer = payload.orderer + } else { + const discoverChannelConfigResult = await this.invokeSteps().discoverChannelConfig(payload) + if (!('stdout' in discoverChannelConfigResult)) { + logger.error('this service only for docker infra') + throw new Error('this service for docker infra') + } + orderer = Chaincode.parser.commitStepDiscoverChannelConfig(discoverChannelConfigResult) + } + + if ('peerAddresses' in payload) { + peerAddresses = payload.peerAddresses + } else { + const discoverEndorsersResult = await this.invokeSteps().discoverEndorsers(payload) + if (!('stdout' in discoverEndorsersResult)) { + logger.error('this service only for docker infra') + throw new Error('this service for docker infra') + } + peerAddresses = Chaincode.parser.invokeStepDiscoverEndorsers(discoverEndorsersResult) + } + + return await this.invokeSteps().invokeOnInstance({ ...payload, orderer, peerAddresses }) + } + + /** + * @ignore + */ + public invokeSteps () { + return { + discoverEndorsers: async (payload: ChaincodeInvokeType): Promise => { + logger.debug('invoke chaincode step 1 (discover endorsers)') + return await (new Discover(this.config)).chaincodeEndorsers({ channel: payload.channelId, chaincode: payload.chaincodeName }) + }, + discoverChannelConfig: async (payload: ChaincodeInvokeType): Promise => { + logger.debug('invoke chaincode step 2 (discover channel config)') + return await (new Discover(this.config)).channelConfig({ channel: payload.channelId }) + }, + invokeOnInstance: async (payload: ChaincodeInvokeStepInvokeOnInstanceType): Promise => { + logger.debug('invoke chaincode step 3 (invoke)') + return await (new FabricInstance(this.config, this.infra)).invokeChaincode(payload.channelId, payload.chaincodeName, payload.chaincodeFunction, payload.args, payload.isInit, payload.orderer, payload.peerAddresses) + }, + } } /**