From e3f90437c2524f1f825a7505f2882092886c498a Mon Sep 17 00:00:00 2001 From: zhaochy Date: Mon, 19 Mar 2018 16:55:31 +0800 Subject: [PATCH] [FAB-8942] fix type definition for 1.1 release Type information in v.1.1.0 is broken and not compilable with tsc. This CR fix the errors, and add tests with typescript remove all type definition to 'types' folder Change-Id: Ia042c94607ab93259872774cd3fb0da60aeb6d05 Signed-off-by: zhaochy --- .gitignore | 2 + build/tasks/ca.js | 4 +- build/tasks/eslint.js | 1 + build/tasks/test.js | 9 +- fabric-ca-client/index.d.ts | 154 ------ fabric-ca-client/package.json | 2 +- fabric-ca-client/types/index.d.ts | 189 +++++++ fabric-client/index.d.ts | 462 ----------------- fabric-client/package.json | 1 + fabric-client/types/base.d.ts | 15 + fabric-client/types/index.d.ts | 457 +++++++++++++++++ fabric-client/{ => types}/tsconfig.json | 3 +- package.json | 10 +- test/integration/network-config.js | 1 - test/typescript/test.ts | 634 ++++++++++++++++++++++++ test/typescript/tsconfig.json | 10 + 16 files changed, 1329 insertions(+), 625 deletions(-) delete mode 100644 fabric-ca-client/index.d.ts create mode 100644 fabric-ca-client/types/index.d.ts delete mode 100644 fabric-client/index.d.ts create mode 100644 fabric-client/types/base.d.ts create mode 100644 fabric-client/types/index.d.ts rename fabric-client/{ => types}/tsconfig.json (93%) create mode 100644 test/typescript/test.ts create mode 100644 test/typescript/tsconfig.json diff --git a/.gitignore b/.gitignore index bac608c4b8..2250c0df9d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ test/fixtures/src/github.com/example_cc/junk.go *.heapsnapshot .vscode .idea +test/typescript/**/*.js +test/typescript/**/*.js.map diff --git a/build/tasks/ca.js b/build/tasks/ca.js index 6ca54da03e..f143b72bf8 100644 --- a/build/tasks/ca.js +++ b/build/tasks/ca.js @@ -26,7 +26,9 @@ const DEPS = [ 'fabric-client/lib/msp/identity.js', 'fabric-client/lib/msp/msp.js', 'fabric-client/lib/protos/msp/identities.proto', - 'fabric-client/lib/protos/msp/msp_config.proto' + 'fabric-client/lib/protos/msp/msp_config.proto', + 'fabric-client/types/tsconfig.json', + 'fabric-client/types/base.d.ts' ]; gulp.task('ca', function() { diff --git a/build/tasks/eslint.js b/build/tasks/eslint.js index b28d45ee57..91517d4a8a 100644 --- a/build/tasks/eslint.js +++ b/build/tasks/eslint.js @@ -11,6 +11,7 @@ gulp.task('lint', function () { '**/*.js', 'fabric-client/**/*.js', 'fabric-ca-client/lib/*.js', + '!test/typescript/test.js', '!node_modules/**', '!fabric-client/node_modules/**', '!fabric-ca-client/node_modules/**', diff --git a/build/tasks/test.js b/build/tasks/test.js index f6fb18f462..b3afd45510 100644 --- a/build/tasks/test.js +++ b/build/tasks/test.js @@ -99,7 +99,14 @@ gulp.task('docker-ready', ['docker-clean'], shell.task([ 'docker-compose -f test/fixtures/docker-compose.yaml up -d' ])); -gulp.task('test', ['clean-up', 'lint', 'pre-test', 'docker-ready', 'ca'], function() { +gulp.task('compile', shell.task([ + 'npm run compile', +], { + verbose: true, // so we can see the docker command output + ignoreErrors: false // once compile failed, throw error +})); + +gulp.task('test', ['clean-up', 'lint', 'pre-test', 'compile', 'docker-ready', 'ca'], function() { // use individual tests to control the sequence they get executed // first run the ca-tests that tests all the member registration // and enrollment scenarios (good and bad calls). Then the rest diff --git a/fabric-ca-client/index.d.ts b/fabric-ca-client/index.d.ts deleted file mode 100644 index 3017c8ebc8..0000000000 --- a/fabric-ca-client/index.d.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - Copyright 2018 IBM All Rights Reserved. - - 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. -*/ - -import { BaseClient, ICryptoSuite, User } from "fabric-client"; - -interface TLSOptions { - trustedRoots: Buffer; - verify: boolean; -} - -interface IFabricCAService { - url: string; - tlsOptions?: TLSOptions; - caName?: string; - cryptoSuite?: ICryptoSuite; -} - -interface IKeyValueAttribute { - name: string; - value: string; - ecert?: boolean; -} - -interface IRegisterRequest { - enrollmentID: string; - enrollmentSecret?: string; - role?: string; - affiliation: string; - maxEnrollments?: number; - attrs?: IKeyValueAttribute[]; -} - -interface IAttributeRequest { - name: string; - optional: boolean; -} - -interface IEnrollmentRequest { - enrollmentID: string; - enrollmentSecret: string; - profile?: string; - attr_reqs?: IAttributeRequest[]; -} - -interface IReEnrollResponse { - key: string; - certificate: string; - rootCertificate: string; -} - -interface IRevokeRequest { - enrollmentID: string; - aki?: string; - serial?: string; - reason?: string; -} - -interface IRestriction { - revokedBefore?: Date; - revokedAfter?: Date; - expireBefore?: Date; - expireAfter?: Date; -} - -interface IIdentityRequest { - enrollmentID: string; - affiliation: string; - attrs?: IKeyValueAttribute[]; - type?: string; - enrollmentSecret?: string; - maxEnrollments?: number; - caname?: string; -} - -interface IServiceResponseMessage { - code: number; - message: string; -} - -interface IServiceResponse { - Success: boolean; - Result: any; - Errors: IServiceResponseMessage[]; - Messages: IServiceResponseMessage[]; -} - -interface IAffiliationRequest { - name: string; - caname?: string; - force?: boolean; -} - -declare enum HFCAIdentityType { - PEER = 'peer', - ORDERER = 'orderer', - CLIENT = 'client', - USER = 'user' -} - -declare enum HFCAIdentityAttributes { - HFREGISTRARROLES = 'hf.Registrar.Roles', - HFREGISTRARDELEGATEROLES = 'hf.Registrar.DelegateRoles', - HFREGISTRARATTRIBUTES = 'hf.Registrar.Attributes', - HFINTERMEDIATECA = 'hf.IntermediateCA', - HFREVOKER = 'hf.Revoker', - HFAFFILIATIONMGR = 'hf.AffiliationMgr', - HFGENCRL = 'hf.GenCRL' -} - -declare class AffiliationService { - create(req: IAffiliationRequest, registrar: User): Promise; - getOne(affiliation: string, registrar: User): Promise; - getAll(registrar: User): Promise; - delete(req: IAffiliationRequest, registrar: User): Promise; - update(affiliation: string, req: IAffiliationRequest, registrar: User): Promise; -} - -declare class IdentityService { - create(req: IIdentityRequest, registrar: User): Promise; - getOne(enrollmentID: string, registrar: User): Promise; - getAll(registrar: User): Promise; - delete(enrollmentID: string, registrar: User): Promise; - update(enrollmentID: string, req: IIdentityRequest, registrar: User): Promise; -} - -declare class FabricCAServices extends BaseClient { - constructor(url: string | IFabricCAService, tlsOptions?: TLSOptions, caName?: string, cryptoSuite?: ICryptoSuite); - getCaName(): string; - register(req: IRegisterRequest, registrar: User): Promise; - enroll(req: IEnrollmentRequest); - reenroll(currentUser: User, attr_reqs: IAttributeRequest[]): Promise; - revoke(request: IRevokeRequest, registrar: User): Promise; - generateCRL(request: IRestriction, registrar: User): Promise; - newIdentityService(): IdentityService; - newAffiliationService(): AffiliationService; - toString(): string; -} - -declare module 'fabric-ca-client' { - export = FabricCAServices; -} \ No newline at end of file diff --git a/fabric-ca-client/package.json b/fabric-ca-client/package.json index 783dc94a6a..1014068a95 100644 --- a/fabric-ca-client/package.json +++ b/fabric-ca-client/package.json @@ -10,7 +10,7 @@ "node": "^8.9.0", "npm": "^5.5.1" }, - "types": "./index.d.ts", + "types": "./types/index.d.ts", "dependencies": { "bn.js": "^4.11.3", "elliptic": "^6.2.3", diff --git a/fabric-ca-client/types/index.d.ts b/fabric-ca-client/types/index.d.ts new file mode 100644 index 0000000000..b4bc8f9683 --- /dev/null +++ b/fabric-ca-client/types/index.d.ts @@ -0,0 +1,189 @@ +/* + Copyright 2018 IBM All Rights Reserved. + + 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. +*/ + +import { ICryptoSuite, ICryptoKeyStore, IKeyValueStore, User } from "fabric-client"; +import { BaseClient } from './base'; + +declare class FabricCAServices extends BaseClient { + constructor(url: string | FabricCAServices.IFabricCAService, tlsOptions?: FabricCAServices.TLSOptions, caName?: string, cryptoSuite?: ICryptoSuite); + getCaName(): string; + register(req: FabricCAServices.IRegisterRequest, registrar: User): Promise; + enroll(req: FabricCAServices.IEnrollmentRequest): Promise; + reenroll(currentUser: User, attr_reqs: FabricCAServices.IAttributeRequest[]): Promise; + revoke(request: FabricCAServices.IRevokeRequest, registrar: User): Promise; + generateCRL(request: FabricCAServices.IRestriction, registrar: User): Promise; + newIdentityService(): FabricCAServices.IdentityService; + newAffiliationService(): FabricCAServices.AffiliationService; + toString(): string; +} + + +export = FabricCAServices; + +declare namespace FabricCAServices { + export interface TLSOptions { + trustedRoots: Buffer; + verify: boolean; + } + + export interface IFabricCAService { + url: string; + tlsOptions?: TLSOptions; + caName?: string; + cryptoSuite?: ICryptoSuite; + } + + export interface IKeyValueAttribute { + name: string; + value: string; + ecert?: boolean; + } + + export interface IRegisterRequest { + enrollmentID: string; + enrollmentSecret?: string; + role?: string; + affiliation: string; + maxEnrollments?: number; + attrs?: IKeyValueAttribute[]; + } + + export interface IAttributeRequest { + name: string; + optional: boolean; + } + + export interface IEnrollmentRequest { + enrollmentID: string; + enrollmentSecret: string; + profile?: string; + attr_reqs?: IAttributeRequest[]; + } + + export interface IKey { + getSKI(): string; + + /** + * Returns true if this key is a symmetric key, false is this key is asymmetric + * + * @returns {boolean} if this key is a symmetric key + */ + isSymmetric(): boolean; + + /** + * Returns true if this key is an asymmetric private key, false otherwise. + * + * @returns {boolean} if this key is an asymmetric private key + */ + isPrivate(): boolean; + + /** + * Returns the corresponding public key if this key is an asymmetric private key. + * If this key is already public, returns this key itself. + * + * @returns {module:api.Key} the corresponding public key if this key is an asymmetric private key. + * If this key is already public, returns this key itself. + */ + getPublicKey(): IKey; + + /** + * Converts this key to its PEM representation, if this operation is allowed. + * + * @returns {string} the PEM string representation of the key + */ + toBytes(): string; + } + + export interface IEnrollResponse { + key: IKey; + certificate: string; + rootCertificate: string; + } + + export interface IRevokeRequest { + enrollmentID: string; + aki?: string; + serial?: string; + reason?: string; + } + + export interface IRestriction { + revokedBefore?: Date; + revokedAfter?: Date; + expireBefore?: Date; + expireAfter?: Date; + } + + export interface IIdentityRequest { + enrollmentID: string; + affiliation: string; + attrs?: IKeyValueAttribute[]; + type?: string; + enrollmentSecret?: string; + maxEnrollments?: number; + caname?: string; + } + + export interface IServiceResponseMessage { + code: number; + message: string; + } + + export interface IServiceResponse { + Success: boolean; + Result: any; + Errors: IServiceResponseMessage[]; + Messages: IServiceResponseMessage[]; + } + + export interface IAffiliationRequest { + name: string; + caname?: string; + force?: boolean; + } + + export enum HFCAIdentityType { + PEER = 'peer', + ORDERER = 'orderer', + CLIENT = 'client', + USER = 'user' + } + + export enum HFCAIdentityAttributes { + HFREGISTRARROLES = 'hf.Registrar.Roles', + HFREGISTRARDELEGATEROLES = 'hf.Registrar.DelegateRoles', + HFREGISTRARATTRIBUTES = 'hf.Registrar.Attributes', + HFINTERMEDIATECA = 'hf.IntermediateCA', + HFREVOKER = 'hf.Revoker', + HFAFFILIATIONMGR = 'hf.AffiliationMgr', + HFGENCRL = 'hf.GenCRL' + } + export class AffiliationService { + create(req: IAffiliationRequest, registrar: User): Promise; + getOne(affiliation: string, registrar: User): Promise; + getAll(registrar: User): Promise; + delete(req: IAffiliationRequest, registrar: User): Promise; + update(affiliation: string, req: IAffiliationRequest, registrar: User): Promise; + } + + export class IdentityService { + create(req: IIdentityRequest, registrar: User): Promise; + getOne(enrollmentID: string, registrar: User): Promise; + getAll(registrar: User): Promise; + delete(enrollmentID: string, registrar: User): Promise; + update(enrollmentID: string, req: IIdentityRequest, registrar: User): Promise; + } +} \ No newline at end of file diff --git a/fabric-client/index.d.ts b/fabric-client/index.d.ts deleted file mode 100644 index b1484cbfba..0000000000 --- a/fabric-client/index.d.ts +++ /dev/null @@ -1,462 +0,0 @@ -/** - * Copyright 2017 Kapil Sachdeva All Rights Reserved. - * - * 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. - */ - -import { FabricCAServices } from 'fabric-ca-client'; - -declare enum Status { - UNKNOWN = 0, - SUCCESS = 200, - BAD_REQUEST = 400, - FORBIDDEN = 403, - NOT_FOUND = 404, - REQUEST_ENTITY_TOO_LARGE = 413, - INTERNAL_SERVER_ERROR = 500, - SERVICE_UNAVAILABLE = 503 -} - -type ChaincodeType = "golang" | "car" | "java" | "node"; - -interface ProtoBufObject { - toBuffer(): Buffer; -} - -interface KeyOpts { - ephemeral: boolean; -} - -interface ConnectionOptions { - pem?: string; - clientKey?: string; - clientCert?: string; - 'request-timeout'?: string; - 'ssl-target-name-override'?: string; - [propName: string]: any; -} - -interface ConfigSignature extends ProtoBufObject { - signature_header: Buffer; - signature: Buffer; -} - -interface ICryptoKey { - getSKI(): string; - isSymmetric(): boolean; - isPrivate(): boolean; - getPublicKey(): ICryptoKey; - toBytes(): string; -} - -interface ICryptoKeyStore { - getKey(ski: string): Promise; - putKey(key: ICryptoKey): Promise; -} - -interface IKeyValueStore { - getValue(name: string): Promise; - setValue(name: string, value: string): Promise; -} - -interface CryptoContent { - privateKey?: string; - privateKeyPEM?: string; - privateKeyObj?: ICryptoKey; - signedCert?: string; - signedCertPEM?: string; -} - -interface UserOptions { - username: string; - mspid: string; - cryptoContent: CryptoContent; - skipPersistence: boolean; -} - -interface ICryptoSuite { - decrypt(key: ICryptoKey, cipherText: Buffer, opts: any): Buffer; - deriveKey(key: ICryptoKey): ICryptoKey; - encrypt(key: ICryptoKey, plainText: Buffer, opts: any): Buffer; - getKey(ski: string): Promise; - generateKey(opts: KeyOpts): Promise; - hash(msg: string, opts: any): string; - importKey(pem: string, opts: KeyOpts): ICryptoKey | Promise; - sign(key: ICryptoKey, digest: Buffer): Buffer; - verify(key: ICryptoKey, signature: Buffer, digest: Buffer): boolean; -} - -interface ChannelRequest { - name: string; - orderer: Orderer; - envelope?: Buffer; - config?: Buffer; - txId?: TransactionId; - signatures: ConfigSignature[]; -} - -interface TransactionRequest { - proposalResponses: ProposalResponse[]; - proposal: Proposal; - txId?: TransactionId; -} - -interface BroadcastResponse { - status: string; -} - -interface IIdentity { - serialize(): Buffer; - getMSPId(): string; - isValid(): boolean; - getOrganizationUnits(): string; - verify(msg: Buffer, signature: Buffer, opts: any): boolean; -} - -interface ISigningIdentity { - sign(msg: Buffer, opts: any): Buffer; -} - -interface ChaincodeInstallRequest { - targets: Peer[]; - chaincodePath: string; - chaincodeId: string; - chaincodeVersion: string; - chaincodePackage?: Buffer; - chaincodeType?: ChaincodeType; - channelNames?: string[] | string; -} - -interface ChaincodeInstantiateUpgradeRequest { - targets?: Peer[]; - chaincodeType?: ChaincodeType; - chaincodeId: string; - chaincodeVersion: string; - txId: TransactionId; - transientMap?: any; - fcn?: string; - args?: string[]; - 'endorsement-policy'?: any; -} - -interface ChaincodeInvokeRequest { - targets?: Peer[]; - chaincodeId: string; - txId: TransactionId; - transientMap?: any; - fcn?: string; - args: string[]; -} - -interface ChaincodeQueryRequest { - targets?: Peer[]; - chaincodeId: string; - transientMap?: any; - fcn?: string; - args: string[]; -} - -interface ChaincodeInfo { - name: string; - version: string; - path: string; - input: string; - escc: string; - vscc: string; -} - -interface ChannelInfo { - channel_id: string; -} - -interface ChaincodeQueryResponse { - chaincodes: ChaincodeInfo[]; -} - -interface ChannelQueryResponse { - channels: ChannelInfo[]; -} - -interface OrdererRequest { - txId?: TransactionId; - orderer: string | Orderer; -} - -interface JoinChannelRequest { - txId: TransactionId; - targets: Peer[]; - block: Buffer; -} - -interface ResponseObject { - status: Status; - message: string; - payload: Buffer; -} - -interface Proposal { - header: ByteBuffer; - payload: ByteBuffer; - extension: ByteBuffer; -} - -interface Header { - channel_header: ByteBuffer; - signature_header: ByteBuffer; -} - -// Decoded block data -interface BlockData { - signature: Buffer; - payload: { header: any, data: any }; -} - -interface Block { - header: { number : number; - previous_hash : Buffer; - data_hash : Buffer; }; - data: { data : BlockData[] }; - metadata: { metadata : any }; -} - -interface ProposalResponse { - version: number; - timestamp: Date; - response: ResponseObject; - payload: Buffer; - endorsement: any; -} - -// Dummy classes for opaque handles for registerChaincodeEvent's -declare class ChaincodeEventHandle { -} - -declare class ChaincodeChannelEventHandle { -} - -interface ChaincodeEvent { - chaincode_id: string; - tx_id: string; - event_name: string; - payload: Buffer; -} - -type ProposalResponseObject = [Array, Proposal]; - -declare class Remote { - constructor(url: string, opts?: ConnectionOptions); - getName(): string; - setName(name: string): void; - getUrl(): string; -} - -declare class Peer extends Remote { - constructor(url: string, opts?: ConnectionOptions); - close(): void; - setRole(role: string, isIn: boolean): void; - isInRole(role: string) : boolean; - sendProposal(proposal: Proposal, timeout: number): Promise; -} - -declare class Orderer extends Remote { - constructor(url: string, opts?: ConnectionOptions); - close(): void; - sendBroadcast(envelope: Buffer): Promise; - sendDeliver(envelope: Buffer): Promise; -} - -declare class EventHub { - constructor(clientContext: Client); - connect(): void; - disconnect(): void; - getPeerAddr(): string; - setPeerAddr(url: string, opts: ConnectionOptions): void; - isconnected(): boolean; - registerBlockEvent(onEvent: (b: Block) => void, onError?: (err: Error) => void): number; - registerTxEvent(txId: string, onEvent: (txId: any, code: string) => void, onError?: (err: Error) => void): void; - registerChaincodeEvent(ccid: string, eventname: string, onEvent: (event: ChaincodeEvent) => void, onError?: (err: Error) => void): ChaincodeEventHandle; - unregisterBlockEvent(regNumber: number): void; - unregisterTxEvent(txId: string): void; - unregisterChaincodeEvent(handle: ChaincodeEventHandle): void; -} - -interface RegistrationOptions { - startBlock?: number; - endBlock?: number; - unregister?: boolean; - disconnect?: boolean; -} - -declare class ChannelEventHub { - constructor(channel: Channel, peer: Peer); - getPeerAddr(): string; - lastBlockNumber(): number; - isconnected(): boolean; - connect(full_block: boolean): void; - disconnect(): void; - checkConnection(force_reconnect: boolean): string; - registerChaincodeEvent(ccid: string, eventname: string, onEvent: (event: ChaincodeEvent, block_number?: number, tx_id?: string, tx_status?: string) => void, - onError?: (err: Error) => void, options?: RegistrationOptions): ChaincodeChannelEventHandle; - unregisterChaincodeEvent(handle: ChaincodeChannelEventHandle, throwError?: boolean): void; - registerBlockEvent(onEvent: (b: Block) => void, onError?: (err: Error) => void, options?: RegistrationOptions): number; - unregisterBlockEvent(block_registration_number: number, throwError: boolean): void; - registerTxEvent(txId: string, onEvent: (txId: string, code: string, block_number: number) => void, onError?: (err: Error) => void, options?: RegistrationOptions): string; - unregisterTxEvent(txId: string, throwError: boolean): void; -} - -interface MSPConstructorConfig { - rootCerts: IIdentity[]; - intermediateCerts: IIdentity[]; - admins: IIdentity[]; - signer: ISigningIdentity; - id: string; - orgs: string[]; - cryptoSuite: ICryptoSuite; -} - -declare class MSP { - constructor(config: MSPConstructorConfig); - deserializeIdentity(serializedIdentity: Buffer, storeKey: boolean): IIdentity | Promise; - getDefaultSigningIdentity(): ISigningIdentity; - getId(): string; - getOrganizationUnits(): string[]; - getPolicy(): any; - getSigningIdentity(identifier: string): ISigningIdentity; - toProtoBuf(): any; - validate(id: IIdentity): boolean; -} - -declare class MSPManager { - constructor(); - addMSP(config: any): MSP; - deserializeIdentity(serializedIdentity: Buffer): IIdentity; - getMSP(): MSP; - getMSPs(): any; - loadMSPs(mspConfigs: any): void; -} - -declare class Channel { - constructor(name: string, clientContext: Client); - initialize(config?: Buffer): Promise; - addOrderer(orderer: Orderer): void; - removeOrderer(orderer: Orderer): void; - addPeer(peer: Peer): void; - removePeer(peer: Peer): void; - getGenesisBlock(request: OrdererRequest): Promise; - getChannelConfig(): Promise; - joinChannel(request: JoinChannelRequest, timeout?: number): Promise; - sendInstantiateProposal(request: ChaincodeInstantiateUpgradeRequest, timeout?: number): Promise; - sendTransactionProposal(request: ChaincodeInvokeRequest, timeout?: number): Promise; - sendTransaction(request: TransactionRequest): Promise; - sendUpgradeProposal(request: ChaincodeInstantiateUpgradeRequest, timeout?: number): Promise; - queryByChaincode(request: ChaincodeQueryRequest): Promise; - queryBlock(blockNumber: number, target?: Peer, useAdmin?: boolean): Promise; - queryBlockByHash(block : Buffer, target?: Peer, useAdmin?: boolean): Promise; - queryTransaction(txId: string, target?: Peer, useAdmin?: boolean): Promise; - queryBlockByTxID(txId: string, target?: Peer, useAdmin?: boolean): Promise; - queryInstantiatedChaincodes(target: Peer, useAdmin?: boolean): Promise; - queryInfo(target: Peer, useAdmin?: boolean): Promise; - getOrderers(): Orderer[]; - getPeers(): Peer[]; - getOrganizations(): string[]; - getMSPManager(): MSPManager; - setMSPManager(manager: MSPManager): void; - newChannelEventHub(peer: Peer): ChannelEventHub; - getChannelEventHubsForOrg(org_name: string): ChannelEventHub[]; -} - -declare class TransactionId { - constructor(signer_or_userContext: IIdentity, admin: boolean); - getTransactionID(): string; - getNonce(): Buffer; - isAdmin(): boolean; -} - -interface UserConfig { - enrollmentID: string; - name: string - roles?: string[]; - affiliation?: string; -} - -declare class User { - constructor(cfg: string | UserConfig); - isEnrolled(): boolean; - getName(): string; - getRoles(): string[]; - setRoles(roles: string[]): void; - getAffiliation(): string; - setAffiliation(affiliation: string): void; - getIdentity(): IIdentity; - getSigningIdentity(): ISigningIdentity; - getCryptoSuite(): ICryptoSuite; - setCryptoSuite(suite: ICryptoSuite): void; - setEnrollment(privateKey: ICryptoKey, certificate: string, mspId: string): Promise; - fromString(): Promise; -} - -declare abstract class BaseClient { - constructor(); - static setLogger(logger: any): void; - static addConfigFile(path: string): void; - static getConfigSetting(name: string, default_value?: any): any; - static setConfigSetting(name: string, value: any): void; - static newCryptoSuite(): ICryptoSuite; - static newCryptoKeyStore(obj?: { path: string }): ICryptoKeyStore; - static newDefaultKeyValueStore(obj?: { path: string }): Promise; - static normalizeX509(raw: string): string; - setCryptoSuite(suite: ICryptoSuite): void; - getCryptoSuite(): ICryptoSuite; -} - -declare class Client extends BaseClient { - constructor(); - static loadFromConfig(config: any): Client; - loadFromConfig(config: any): void; - setTlsClientCertAndKey(clientCert: string, clientKey: Buffer): void; - addTlsClientCertAndKey(opts: any): void; - isDevMode(): boolean; - setDevMode(mode: boolean): void; - newChannel(name: string): Channel; - getChannel(name?: string, throwError?: boolean): Channel; - newPeer(url: string, opts: ConnectionOptions): Peer; - newEventHub(): EventHub; - getEventHub(peer_name: string): EventHub; - getEventHubsForOrg(org_name: string): EventHub[]; - getPeersForOrg(org_name: string): Peer[]; - newOrderer(url: string, opts: ConnectionOptions): Orderer; - getCertificateAuthority(): FabricCAServices; - getClientConfig(): any; - getMspid(): string; - newTransactionID(admin?: boolean): TransactionId; - extractChannelConfig(envelope: Buffer): Buffer; - signChannelConfig(config: Buffer): ConfigSignature; - createChannel(request: ChannelRequest): Promise; - updateChannel(request: ChannelRequest): Promise; - queryChannels(peer: Peer, useAdmin: boolean): Promise; - queryInstalledChaincodes(peer: Peer, useAdmin: boolean): Promise; - installChaincode(request: ChaincodeInstallRequest, timeout: number): Promise; - initCredentialStores(): Promise; - setStateStore(store: IKeyValueStore): void; - setAdminSigningIdentity(private_key: string, certificate: string, mspid: string): void; - saveUserToStateStore(): Promise; - setUserContext(user: User, skipPersistence?: boolean): Promise; - getUserContext(name: string, checkPersistence?: boolean): Promise | User; - loadUserFromStateStore(name: string): Promise; - getStateStore(): IKeyValueStore; - createUser(opts: UserOptions): Promise; -} - -declare module 'fabric-client' { - export = Client; -} diff --git a/fabric-client/package.json b/fabric-client/package.json index fa3c3e7d7e..562b8a2d7a 100644 --- a/fabric-client/package.json +++ b/fabric-client/package.json @@ -14,6 +14,7 @@ "node": "^8.9.0", "npm": "^5.5.1" }, + "types": "./types/index.d.ts", "dependencies": { "bn.js": "^4.11.3", "callsite": "^1.0.0", diff --git a/fabric-client/types/base.d.ts b/fabric-client/types/base.d.ts new file mode 100644 index 0000000000..5080ef53d9 --- /dev/null +++ b/fabric-client/types/base.d.ts @@ -0,0 +1,15 @@ +import { ICryptoSuite, ICryptoKeyStore, IKeyValueStore } from 'fabric-client'; + +export abstract class BaseClient { + constructor(); + static setLogger(logger: any): void; + static addConfigFile(path: string): void; + static getConfigSetting(name: string, default_value?: any): any; + static setConfigSetting(name: string, value: any): void; + static newCryptoSuite(): ICryptoSuite; + static newCryptoKeyStore(obj?: { path: string }): ICryptoKeyStore; + static newDefaultKeyValueStore(obj?: { path: string }): Promise; + static normalizeX509(raw: string): string; + setCryptoSuite(suite: ICryptoSuite): void; + getCryptoSuite(): ICryptoSuite; +} \ No newline at end of file diff --git a/fabric-client/types/index.d.ts b/fabric-client/types/index.d.ts new file mode 100644 index 0000000000..4f662007c3 --- /dev/null +++ b/fabric-client/types/index.d.ts @@ -0,0 +1,457 @@ +/** + * Copyright 2017 Kapil Sachdeva All Rights Reserved. + * + * 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. + */ + +import FabricCAServices = require('fabric-ca-client'); +import { BaseClient } from "./base"; + +interface ProtoBufObject { + toBuffer(): Buffer; +} + +// Dummy classes for opaque handles for registerChaincodeEvent's +declare class ChaincodeEventHandle { +} + +declare class ChaincodeChannelEventHandle { +} + +declare class Remote { + constructor(url: string, opts?: Client.ConnectionOptions); + getName(): string; + setName(name: string): void; + getUrl(): string; +} + +declare class Client extends BaseClient { + constructor(); + static loadFromConfig(config: any): Client; + loadFromConfig(config: any): void; + setTlsClientCertAndKey(clientCert: string, clientKey: string): void; + addTlsClientCertAndKey(opts: any): void; + isDevMode(): boolean; + setDevMode(mode: boolean): void; + newChannel(name: string): Client.Channel; + getChannel(name?: string, throwError?: boolean): Client.Channel; + newPeer(url: string, opts: Client.ConnectionOptions): Client.Peer; + newEventHub(): Client.EventHub; + getEventHub(peer_name: string): Client.EventHub; + getEventHubsForOrg(org_name: string): Client.EventHub[]; + getPeersForOrg(org_name: string): Client.Peer[]; + newOrderer(url: string, opts: Client.ConnectionOptions): Client.Orderer; + getCertificateAuthority(): FabricCAServices; + getClientConfig(): any; + getMspid(): string; + newTransactionID(admin?: boolean): Client.TransactionId; + extractChannelConfig(envelope: Buffer): Buffer; + signChannelConfig(config: Buffer): Client.ConfigSignature; + createChannel(request: Client.ChannelRequest): Promise; + updateChannel(request: Client.ChannelRequest): Promise; + queryChannels(peer: Client.Peer | string, useAdmin?: boolean): Promise; + queryInstalledChaincodes(peer: Client.Peer | string, useAdmin?: boolean): Promise; + installChaincode(request: Client.ChaincodeInstallRequest, timeout?: number): Promise; + initCredentialStores(): Promise; + setStateStore(store: Client.IKeyValueStore): void; + setAdminSigningIdentity(private_key: string, certificate: string, mspid: string): void; + saveUserToStateStore(): Promise; + setUserContext(user: Client.User | Client.UserContext, skipPersistence?: boolean): Promise; + getUserContext(name: string, checkPersistence?: boolean): Promise | Client.User; + loadUserFromStateStore(name: string): Promise; + getStateStore(): Client.IKeyValueStore; + createUser(opts: Client.UserOptions): Promise; +} +export = Client; + +declare namespace Client { + export enum Status { + UNKNOWN = 0, + SUCCESS = 200, + BAD_REQUEST = 400, + FORBIDDEN = 403, + NOT_FOUND = 404, + REQUEST_ENTITY_TOO_LARGE = 413, + INTERNAL_SERVER_ERROR = 500, + SERVICE_UNAVAILABLE = 503 + } + + export type ChaincodeType = "golang" | "car" | "java" | "node"; + export interface ICryptoKey { + getSKI(): string; + isSymmetric(): boolean; + isPrivate(): boolean; + getPublicKey(): ICryptoKey; + toBytes(): string; + } + + export interface ICryptoKeyStore { + getKey(ski: string): Promise; + putKey(key: ICryptoKey): Promise; + } + + export interface ICryptoSuite { + decrypt(key: ICryptoKey, cipherText: Buffer, opts: any): Buffer; + deriveKey(key: ICryptoKey): ICryptoKey; + encrypt(key: ICryptoKey, plainText: Buffer, opts: any): Buffer; + getKey(ski: string): Promise; + generateKey(opts: KeyOpts): Promise; + hash(msg: string, opts: any): string; + importKey(pem: string, opts: KeyOpts): ICryptoKey | Promise; + sign(key: ICryptoKey, digest: Buffer): Buffer; + verify(key: ICryptoKey, signature: Buffer, digest: Buffer): boolean; + } + export interface UserConfig { + enrollmentID: string; + name: string + roles?: string[]; + affiliation?: string; + } + export interface ConnectionOptions { + pem?: string; + clientKey?: string; + clientCert?: string; + 'request-timeout'?: string; + 'ssl-target-name-override'?: string; + [propName: string]: any; + } + + export class User { + constructor(cfg: string | UserConfig); + isEnrolled(): boolean; + getName(): string; + getRoles(): string[]; + setRoles(roles: string[]): void; + getAffiliation(): string; + setAffiliation(affiliation: string): void; + getIdentity(): IIdentity; + getSigningIdentity(): ISigningIdentity; + getCryptoSuite(): ICryptoSuite; + setCryptoSuite(suite: ICryptoSuite): void; + setEnrollment(privateKey: ICryptoKey, certificate: string, mspId: string): Promise; + fromString(): Promise; + } + + export class Channel { + constructor(name: string, clientContext: Client); + initialize(config?: Buffer): Promise; + addOrderer(orderer: Orderer): void; + removeOrderer(orderer: Orderer): void; + addPeer(peer: Peer): void; + removePeer(peer: Peer): void; + getGenesisBlock(request: OrdererRequest): Promise; + getChannelConfig(): Promise; + joinChannel(request: JoinChannelRequest, timeout?: number): Promise; + sendInstantiateProposal(request: ChaincodeInstantiateUpgradeRequest, timeout?: number): Promise; + sendTransactionProposal(request: ChaincodeInvokeRequest, timeout?: number): Promise; + sendTransaction(request: TransactionRequest): Promise; + sendUpgradeProposal(request: ChaincodeInstantiateUpgradeRequest, timeout?: number): Promise; + queryByChaincode(request: ChaincodeQueryRequest): Promise; + queryBlock(blockNumber: number, target?: Peer | string, useAdmin?: boolean): Promise; + queryBlockByHash(block: Buffer, target?: Peer | string, useAdmin?: boolean): Promise; + queryTransaction(txId: string, target?: Peer | string, useAdmin?: boolean): Promise; + queryBlockByTxID(txId: string, target?: Peer | string, useAdmin?: boolean): Promise; + queryInstantiatedChaincodes(target: Peer | string, useAdmin?: boolean): Promise; + queryInfo(target?: Peer | string, useAdmin?: boolean): Promise; + getOrderers(): Orderer[]; + getPeers(): Peer[]; + getOrganizations(): string[]; + getMSPManager(): MSPManager; + setMSPManager(manager: MSPManager): void; + newChannelEventHub(peer: Peer | string): ChannelEventHub; + getChannelEventHubsForOrg(org_name?: string): ChannelEventHub[]; + } + export interface IKeyValueStore { + getValue(name: string): Promise; + setValue(name: string, value: string): Promise; + } + + export interface ConfigSignature extends ProtoBufObject { + signature_header: Buffer; + signature: Buffer; + } + + export class TransactionId { + constructor(signer_or_userContext: IIdentity, admin: boolean); + getTransactionID(): string; + getNonce(): Buffer; + isAdmin(): boolean; + } + + export interface ChannelRequest { + name: string; + orderer: Orderer | string; + envelope?: Buffer; + config?: Buffer; + txId?: TransactionId; + signatures: ConfigSignature[] | string[]; + } + + export interface TransactionRequest { + proposalResponses: ProposalResponse[]; + proposal: Proposal; + admin?: boolean; + txId?: TransactionId; + } + + export interface BroadcastResponse { + status: string; + info: string; + } + + export type ProposalResponseObject = [Array, Client.Proposal]; + + export interface OrdererRequest { + txId: TransactionId; + orderer?: string | Orderer; + } + + export interface JoinChannelRequest { + txId: TransactionId; + targets?: Peer[] | string[]; + block: Block; + } + + export interface BlockData { + signature: Buffer; + payload: { header: any, data: any }; + } + + export interface BlockchainInfo { + height: any; + currentBlockHash: Buffer; + previousBlockHash: Buffer; + } + + export interface Block { + header: { + number: number; + previous_hash: Buffer; + data_hash: Buffer; + }; + data: { data: BlockData[] }; + metadata: { metadata: any }; + } + + export interface ProposalResponse { + version: number; + timestamp: Date; + response: ResponseObject; + payload: Buffer; + endorsement: any; + } + + export class ChannelEventHub { + constructor(channel: Channel, peer: Peer); + getPeerAddr(): string; + lastBlockNumber(): number; + isconnected(): boolean; + connect(full_block?: boolean): void; + disconnect(): void; + checkConnection(force_reconnect: boolean): string; + registerChaincodeEvent(ccid: string, eventname: string, onEvent: (event: ChaincodeEvent, block_number?: number, tx_id?: string, tx_status?: string) => void, + onError?: (err: Error) => void, options?: RegistrationOptions): ChaincodeChannelEventHandle; + unregisterChaincodeEvent(handle: ChaincodeChannelEventHandle, throwError?: boolean): void; + registerBlockEvent(onEvent: (block: Block) => void, onError?: (err: Error) => void, options?: RegistrationOptions): number; + unregisterBlockEvent(block_registration_number: number, throwError: boolean): void; + registerTxEvent(txId: string, onEvent: (txId: string, code: string, block_number: number) => void, onError?: (err: Error) => void, options?: RegistrationOptions): string; + unregisterTxEvent(txId: string, throwError?: boolean): void; + } + + export class Peer extends Remote { + constructor(url: string, opts?: ConnectionOptions); + close(): void; + setRole(role: string, isIn: boolean): void; + isInRole(role: string): boolean; + sendProposal(proposal: Proposal, timeout: number): Promise; + } + + export class Orderer extends Remote { + constructor(url: string, opts?: ConnectionOptions); + close(): void; + sendBroadcast(envelope: Buffer): Promise; + sendDeliver(envelope: Buffer): Promise; + } + + export class EventHub { + constructor(clientContext: Client); + connect(): void; + disconnect(): void; + getPeerAddr(): string; + setPeerAddr(url: string, opts: ConnectionOptions): void; + isconnected(): boolean; + registerBlockEvent(onEvent: (block: Block) => void, onError?: (err: Error) => void): number; + registerTxEvent(txId: string, onEvent: (txId: any, code: string) => void, onError?: (err: Error) => void): void; + registerChaincodeEvent(ccid: string, eventname: string, onEvent: (event: ChaincodeEvent) => void, onError?: (err: Error) => void): ChaincodeEventHandle; + unregisterBlockEvent(regNumber: number): void; + unregisterTxEvent(txId: string): void; + unregisterChaincodeEvent(handle: ChaincodeEventHandle): void; + } + interface MSPConstructorConfig { + rootCerts: IIdentity[]; + intermediateCerts: IIdentity[]; + admins: IIdentity[]; + signer: ISigningIdentity; + id: string; + orgs: string[]; + cryptoSuite: Client.ICryptoSuite; + } + + export class MSP { + constructor(config: MSPConstructorConfig); + deserializeIdentity(serializedIdentity: Buffer, storeKey: boolean): IIdentity | Promise; + getDefaultSigningIdentity(): ISigningIdentity; + getId(): string; + getOrganizationUnits(): string[]; + getPolicy(): any; + getSigningIdentity(identifier: string): ISigningIdentity; + toProtoBuf(): any; + validate(id: IIdentity): boolean; + } + + export class MSPManager { + constructor(); + addMSP(config: any): MSP; + deserializeIdentity(serializedIdentity: Buffer): IIdentity; + getMSP(): MSP; + getMSPs(): any; + loadMSPs(mspConfigs: any): void; + } + export interface ChaincodeInstallRequest { + targets?: Peer[] | string[]; + chaincodePath: string; + chaincodeId: string; + chaincodeVersion: string; + chaincodePackage?: Buffer; + chaincodeType?: ChaincodeType; + channelNames?: string[] | string; + txId: TransactionId; + } + + export interface ChaincodeInstantiateUpgradeRequest { + targets?: Peer[]; + chaincodeType?: ChaincodeType; + chaincodeId: string; + chaincodeVersion: string; + txId: TransactionId; + transientMap?: any; + fcn?: string; + args?: string[]; + 'endorsement-policy'?: any; + } + + export interface ChaincodeInvokeRequest { + targets?: Peer[]; + chaincodeId: string; + txId: TransactionId; + transientMap?: any; + fcn?: string; + args: string[]; + } + + export interface ChaincodeQueryRequest { + targets?: Peer[]; + chaincodeId: string; + transientMap?: any; + fcn?: string; + args: string[]; + } + + export interface KeyOpts { + ephemeral: boolean; + } + + export interface CryptoContent { + privateKey?: string; + privateKeyPEM?: string; + privateKeyObj?: Client.ICryptoKey; + signedCert?: string; + signedCertPEM?: string; + } + + export interface UserContext { + username: string; + password?: string; + } + + export interface UserOptions { + username: string; + mspid: string; + cryptoContent: CryptoContent; + skipPersistence: boolean; + } + + export interface IIdentity { + serialize(): Buffer; + getMSPId(): string; + isValid(): boolean; + getOrganizationUnits(): string; + verify(msg: Buffer, signature: Buffer, opts: any): boolean; + } + + export interface ISigningIdentity { + sign(msg: Buffer, opts: any): Buffer; + } + + export interface ChaincodeInfo { + name: string; + version: string; + path: string; + input: string; + escc: string; + vscc: string; + } + + export interface ChannelInfo { + channel_id: string; + } + + export interface ChaincodeQueryResponse { + chaincodes: ChaincodeInfo[]; + } + + export interface ChannelQueryResponse { + channels: ChannelInfo[]; + } + + export interface ResponseObject { + status: Client.Status; + message: string; + payload: Buffer; + } + + export interface Proposal { + header: ByteBuffer; + payload: ByteBuffer; + extension: ByteBuffer; + } + + export interface Header { + channel_header: ByteBuffer; + signature_header: ByteBuffer; + } + + export interface RegistrationOptions { + startBlock?: number; + endBlock?: number; + unregister?: boolean; + disconnect?: boolean; + } + + export interface ChaincodeEvent { + chaincode_id: string; + tx_id: string; + event_name: string; + payload: Buffer; + } +} diff --git a/fabric-client/tsconfig.json b/fabric-client/types/tsconfig.json similarity index 93% rename from fabric-client/tsconfig.json rename to fabric-client/types/tsconfig.json index 3ce4a897d6..7308db2d7d 100644 --- a/fabric-client/tsconfig.json +++ b/fabric-client/types/tsconfig.json @@ -20,7 +20,8 @@ } }, "files": [ - "index.d.ts" + "index.d.ts", + "base.d.ts" ], "formatCodeOptions": { "indentSize": 2, diff --git a/package.json b/package.json index 6adc33653f..4c31972f07 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,15 @@ "url": "http://gerrit.hyperledger.org/r/fabric-sdk-node" }, "scripts": { - "test": "gulp test-headless" + "test": "gulp test-headless", + "compile": "tsc --project test/typescript", + "compile:w": "tsc --project test/typescript --watch" }, "devDependencies": { + "@cloudant/cloudant": "^2.1.0", "@types/bytebuffer": "^5.0.34", "bn.js": "^4.11.8", "bunyan": "^1.8.10", - "@cloudant/cloudant": "^2.1.0", "del": "^2.2.2", "elliptic": "^6.3.2", "eslint": "^4.6.1", @@ -44,9 +46,9 @@ "tape-promise": "^1.1.0", "tar-fs": "^1.13.0", "targz": "^1.0.1", + "typescript": "2.7.2", "winston": "^2.2.0", - "x509": "^0.3.2", - "typescript": "^2.5.2" + "x509": "^0.3.2" }, "license": "Apache-2.0", "licenses": [ diff --git a/test/integration/network-config.js b/test/integration/network-config.js index 9fd4172cdc..7a24b8741d 100644 --- a/test/integration/network-config.js +++ b/test/integration/network-config.js @@ -326,7 +326,6 @@ test('\n\n***** use the connection profile file *****\n\n', function(t) { let tx_id = client.newTransactionID(true); instansiate_tx_id = tx_id; let request = { - chaincodePath: 'github.com/example_cc', chaincodeId: 'example', chaincodeVersion: 'v1', args: ['a', '100', 'b', '200'], diff --git a/test/typescript/test.ts b/test/typescript/test.ts new file mode 100644 index 0000000000..fb13a4bbc5 --- /dev/null +++ b/test/typescript/test.ts @@ -0,0 +1,634 @@ +import * as path from 'path'; +import * as test from 'tape'; +import * as fs from 'fs-extra'; +import * as util from 'util'; + +import Client = require('fabric-client'); +import FabricCAServices = require('fabric-ca-client'); + +const utils = require('fabric-client/lib/utils.js'); +const logger = utils.getLogger('connection profile'); + +import { + ConfigSignature, + TransactionId, + ChannelRequest, + BroadcastResponse, + Channel, + OrdererRequest, + JoinChannelRequest, + Block, + ProposalResponse, + ChaincodeInstallRequest, + ProposalResponseObject, + ChaincodeInstantiateUpgradeRequest, + TransactionRequest, + User, + ChaincodeInvokeRequest, + Proposal, + ChannelEventHub, + ChaincodeQueryRequest, + ChannelQueryResponse, + ChaincodeQueryResponse, + BlockchainInfo +} from 'fabric-client'; +import { IEnrollmentRequest } from 'fabric-ca-client'; + +const config_network: string = path.resolve(__dirname, 'test/fixtures/network.yaml'); +const config_org1: string = path.resolve(__dirname, 'test/fixtures/org1.yaml'); +const config_org2: string = path.resolve(__dirname, 'test/fixtures/org2.yaml'); +const channel_name: string = 'mychannel'; + +test('use the connection profile file', (t) => { + const client = Client.loadFromConfig(config_network); + t.pass('Successfully load config from network.yaml'); + + client.loadFromConfig(config_org1); + + let config = null; + let signatures = []; + let channel: Channel = null; + let genesis_block: any = null; + let instansiate_tx_id: TransactionId = null; + let query_tx_id: string = null; + + client.initCredentialStores() + .then(() => { + t.pass('Successfully created the key value store and crypto store based on the sdk config and connection profile'); + const fabca: FabricCAServices = client.getCertificateAuthority(); + const req: IEnrollmentRequest = { + enrollmentID: 'admin', + enrollmentSecret: 'adminpw', + profile: 'tls' + }; + return fabca.enroll(req); + }).then((enrollment: FabricCAServices.IEnrollResponse) => { + t.pass('Successfully called the CertificateAuthority to get the TLS material'); + const key = enrollment.key.toBytes(); + const cert = enrollment.certificate; + + // set the material on the client to be used when building endpoints for the user + client.setTlsClientCertAndKey(cert, key); + + let envelope_bytes = fs.readFileSync(path.join(__dirname, '../../fixtures/channel/mychannel3.tx')); + config = client.extractChannelConfig(envelope_bytes); + + let signature: ConfigSignature = client.signChannelConfig(config); + + let string_signature = signature.toBuffer().toString('hex'); + t.pass('Successfully signed config update by org1'); + // collect signature from org1 admin + signatures.push(string_signature); + t.pass('Successfully extracted the config update from the configtx envelope'); + return client.loadFromConfig(config_org2); + }).then(() => { + t.pass('Successfully loaded the client configuration for org2'); + + return client.initCredentialStores(); + }).then(() => { + t.pass('Successfully set the stores for org2'); + let signature: ConfigSignature = client.signChannelConfig(config); + let string_signature: string = signature.toBuffer().toString('hex'); + t.pass('Successfully signed config update by org2'); + // collect signature from org1 admin + signatures.push(signature); + t.pass('Successfully extracted the config update from the configtx envelope'); + + let txId: TransactionId = client.newTransactionID(true); + // build up the create request + let request: ChannelRequest = { + config, + signatures, + txId, + name: channel_name, + orderer: 'orderer.example.com', //this assumes we have loaded a connection profile + }; + return client.createChannel(request); //logged in as org2 + }).then((result: BroadcastResponse) => { + logger.debug('\n***\n completed the create \n***\n'); + + logger.debug(' response ::%j',result); + t.pass('Successfully send create channel request'); + if (result.status && result.status === 'SUCCESS') { + return sleep(10000); + } else { + t.fail('Failed to create the channel'); + throw new Error('Failed to create the channel. '); + } + }).then(() => { + t.pass('Successfully waited to make sure new channel was created.'); + channel = client.getChannel(channel_name); + + let txId = client.newTransactionID(true); + let request: OrdererRequest = { txId }; + return channel.getGenesisBlock(request); + }).then((block: Block) => { + t.pass('Successfully got the genesis block'); + genesis_block = block; + + let txId: TransactionId = client.newTransactionID(true); + let request: JoinChannelRequest = { + //targets: // this time we will leave blank so that we can use + // all the peers assigned to the channel ...some may fail + // if the submitter is not allowed, let's see what we get + block, + txId + }; + return channel.joinChannel(request); //admin from org2 + }).then((results: ProposalResponse[]) => { + // first of the results should not have good status as submitter does not have permission + if (results && results[0] && results[0].response && results[0].response.status == 200) { + t.fail('Successfully had peer in organization org1 join the channel'); + throw new Error('Should not have been able to join channel with this submitter'); + } else { + t.pass(' Submitter on "org2" Failed to have peer on org1 channel'); + } + + // second of the results should have good status + if (results && results[1] && results[1].response && results[1].response.status == 200) { + t.pass('Successfully had peer in organization org1 join the channel'); + } else { + t.fail(' Failed to join channel'); + throw new Error('Failed to join channel'); + } + + /* + * switch to organization org1 + */ + client.loadFromConfig(config_org1); + t.pass('Successfully loaded \'admin\' for org1'); + return client.initCredentialStores(); + }).then(() => { + t.pass('Successfully created the key value store and crypto store based on the config and connection profile'); + let txId: TransactionId = client.newTransactionID(true); + let request: JoinChannelRequest = { + targets: ['peer0.org1.example.com'], // this does assume that we have loaded a + // connection profile with a peer by this name + block: genesis_block, + txId, + }; + + return channel.joinChannel(request); //logged in as org1 + }).then((results: ProposalResponse[]) => { + if (results && results[0] && results[0].response && results[0].response.status == 200) { + t.pass(util.format('Successfully had peer in organization %s join the channel', 'org1')); + } else { + t.fail(' Failed to join channel on org1'); + throw new Error('Failed to join channel on org1'); + } + return sleep(10000); + }).then(() => { + t.pass('Successfully waited for peers to join the channel'); + process.env.GOPATH = path.join(__dirname, '../../fixtures'); + logger.debug(`Set GOPATH to ${process.env.GOPATH}`); + let txId: TransactionId = client.newTransactionID(true); + // send proposal to endorser + const request: ChaincodeInstallRequest = { + targets: ['peer0.org1.example.com'], + chaincodePath: 'github.com/example_cc', + chaincodeId: 'example', + chaincodeVersion: 'v1', + channelNames: 'mychannel3', //targets will based on peers in this channel + txId, + }; + + return client.installChaincode(request); + }).then((results: ProposalResponseObject) => { + if (results && results[0] && results[0][0].response && results[0][0].response.status == 200) { + t.pass('Successfully installed chain code on org1'); + } else { + t.fail(' Failed to install chaincode on org1'); + logger.debug('Failed due to: %j', results); + throw new Error('Failed to install chain code on org1'); + } + + /* + * I N S T A N S I A T E + */ + let txId: TransactionId = client.newTransactionID(true); + instansiate_tx_id = txId; + let request: ChaincodeInstantiateUpgradeRequest = { + chaincodeId: 'example', + chaincodeVersion: 'v1', + args: ['a', '100', 'b', '200'], + txId: txId + }; + + return channel.sendInstantiateProposal(request); // still have org2 admin signer + }).then((results: ProposalResponseObject) => { + var proposalResponses = results[0]; + var proposal = results[1]; + if (proposalResponses && proposalResponses[0].response && proposalResponses[0].response.status === 200) { + t.pass('Successfully sent Proposal and received ProposalResponse'); + var request: TransactionRequest = { + proposalResponses: proposalResponses, + proposal: proposal, + txId: instansiate_tx_id //required to indicate that this is an admin transaction + //orderer : not specifying, the first orderer defined in the + // connection profile for this channel will be used + }; + + return channel.sendTransaction(request); // still have org2 admin as signer + } else { + t.fail('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); + throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'); + } + }).then((response: BroadcastResponse) => { + if (!(response instanceof Error) && response.status === 'SUCCESS') { + t.pass('Successfully sent transaction to instantiate the chaincode to the orderer.'); + return sleep(10000); + } else { + t.fail('Failed to order the transaction to instantiate the chaincode. Error code: ' + response.status); + throw new Error('Failed to order the transaction to instantiate the chaincode. Error code: ' + response.status); + } + }).then(() => { + t.pass('Successfully waited for chaincode to startup'); + + /* + * S T A R T U S I N G + */ + /* + * switch to organization org2 + */ + + client.loadFromConfig('test/fixtures/org2.yaml'); + + return client.initCredentialStores(); + }).then(() => { + t.pass('Successfully created the key value store and crypto store based on the config and connection profile'); + + let ca: FabricCAServices = client.getCertificateAuthority(); + if (ca) { + t.equals(ca.getCaName(), 'ca-org2', 'checking that caname is correct for the newly created ca'); + } else { + t.fail('Failed - CertificateAuthority should have been created'); + } + + /* + * switch to organization org1 + */ + client.loadFromConfig('test/fixtures/org1.yaml'); + t.pass('Successfully loaded config for org1'); + + return client.initCredentialStores(); + }).then(() => { + t.pass('Successfully created the key value store and crypto store based on the config and network'); + + return client.setUserContext({ username: 'admin', password: 'adminpw' }); + }).then((admin: User) => { + t.pass('Successfully enrolled user \'admin\' for org1'); + + let ca: FabricCAServices = client.getCertificateAuthority(); + if (ca) { + t.equals(ca.getCaName(), 'ca-org1', 'checking that caname is correct after resetting the config'); + } else { + t.fail('Failed - CertificateAuthority should have been created'); + } + + return ca.register({ enrollmentID: 'user1', affiliation: 'org1' }, admin); + }).then((secret: string) => { + t.pass('Successfully registered user \'user1\' for org1'); + + return client.setUserContext({ username: 'user1', password: secret }); + }).then((user: User) => { + t.pass('Successfully enrolled user \'user1\' for org1'); + + // try again ...this time use a longer timeout + let tx_id: TransactionId = client.newTransactionID(); // get a non admin transaction ID + query_tx_id = tx_id.getTransactionID(); //save transaction string for later + let request: ChaincodeInvokeRequest = { + chaincodeId: 'example', + fcn: 'move', + args: ['a', 'b', '100'], + txId: tx_id + //targets - Letting default to all endorsing peers defined on the channel in the connection profile + }; + + return channel.sendTransactionProposal(request); //logged in as org1 user + }).then((results: ProposalResponseObject) => { + let proposalResponses: ProposalResponse[] = results[0]; + let proposal: Proposal = results[1]; + let all_good = true; + // Will check to be sure that we see two responses as there are two peers defined on this + // channel that are endorsing peers + let endorsed_responses = 0; + for (let i in proposalResponses) { + let one_good = false; + endorsed_responses++; + let proposal_response: ProposalResponse = proposalResponses[i]; + if (proposal_response.response && proposal_response.response.status === 200) { + t.pass('transaction proposal has response status of good'); + one_good = true; + } else { + t.fail('transaction proposal was bad'); + if (proposal_response.response && proposal_response.response.status) { + t.comment(' response status:' + proposal_response.response.status + + ' message:' + proposal_response.response.message); + } else { + t.fail('transaction response was unknown'); + logger.error('transaction response was unknown %s', proposal_response); + } + } + all_good = all_good && one_good; + } + t.equals(endorsed_responses, 2, 'Checking that there are the correct number of endorsed responses'); + if (!all_good) { + t.fail('Failed to send invoke Proposal or receive valid response. Response null or status is not 200. exiting...'); + throw new Error('Failed to send invoke Proposal or receive valid response. Response null or status is not 200. exiting...'); + } + let request: TransactionRequest = { + proposalResponses: proposalResponses, + proposal: proposal, + admin: true + }; + + let promises = []; + + // be sure to get an channel event hub the current user is authorized to use + let eventhub = channel.newChannelEventHub('peer0.org1.example.com'); + + let txPromise = new Promise((resolve, reject) => { + let handle = setTimeout(() => { + eventhub.unregisterTxEvent(query_tx_id); + eventhub.disconnect(); + t.fail('REQUEST_TIMEOUT --- eventhub did not report back'); + reject(new Error('REQUEST_TIMEOUT:' + eventhub.getPeerAddr())); + }, 30000); + + eventhub.registerTxEvent(query_tx_id, (tx, code, block_num) => { + clearTimeout(handle); + if (code !== 'VALID') { + t.fail('transaction was invalid, code = ' + code); + reject(new Error('INVALID:' + code)); + } else { + t.pass('transaction has been committed on peer ' + eventhub.getPeerAddr()); + resolve('COMMITTED'); + } + }, (error) => { + clearTimeout(handle); + t.fail('transaction event failed:' + error); + reject(error); + }, + { disconnect: true } //since this is a test and we will not be using later + ); + }); + // connect(true) to receive full blocks (user must have read rights to the channel) + // should connect after registrations so that there is an error callback + // to receive errors if there is a problem on the connect. + eventhub.connect(true); + + promises.push(txPromise); + promises.push(channel.sendTransaction(request)); + + return Promise.all(promises); + }).then((results) => { + let event_results = results[0]; // Promise all will return the results in order of the of Array + let sendTransaction_results = results[1]; + if (sendTransaction_results instanceof Error) { + t.fail('Failed to order the transaction: ' + sendTransaction_results); + throw sendTransaction_results; + } else if (sendTransaction_results.status === 'SUCCESS') { + t.pass('Successfully sent transaction to invoke the chaincode to the orderer.'); + } else { + t.fail('Failed to order the transaction to invoke the chaincode. Error code: ' + sendTransaction_results.status); + throw new Error('Failed to order the transaction to invoke the chaincode. Error code: ' + sendTransaction_results.status); + } + + return new Promise((resolve, reject) => { + // get a new ChannelEventHub when registering a listener + // with startBlock or endBlock when doing a replay + // The ChannelEventHub must not have been connected or have other + // listeners. + let channel_event_hub: ChannelEventHub = channel.newChannelEventHub('peer0.org1.example.com'); + + let handle = setTimeout(() => { + t.fail('Timeout - Failed to receive replay the event for event1'); + channel_event_hub.unregisterTxEvent(query_tx_id); + channel_event_hub.disconnect(); //shutdown down since we are done + }, 10000); + + channel_event_hub.registerTxEvent(query_tx_id, (txnid, code, block_num) => { + clearTimeout(handle); + t.pass('Event has been replayed with transaction code:' + code + ' for transaction id:' + txnid + ' for block_num:' + block_num); + resolve('Got the replayed transaction'); + }, (error) => { + clearTimeout(handle); + t.fail('Failed to receive event replay for Event for transaction id ::' + query_tx_id); + throw (error); + }, + // a real application would have remembered the last block number + // received and used that value to start the replay + // Setting the disconnect to true as we do not want to use this + // ChannelEventHub after the event we are looking for comes in + { startBlock: 0, disconnect: true } + ); + t.pass('Successfully registered transaction replay for ' + query_tx_id); + + channel_event_hub.connect(); //connect to receive filtered blocks + t.pass('Successfully called connect on the transaction replay event hub for filtered blocks'); + }); + }).then((results) => { + t.pass('Successfully checked channel event hub replay'); + + return new Promise((resolve, reject) => { + // Get the list of channel event hubs for the current organization. + // These will be peers with the "eventSource" role setting of true + // and not the peers that have an "eventURL" defined. Peers with the + // eventURL defined are peers with the legacy Event Hub that is on + // a different port than the peer services. The peers with the + // "eventSource" tag are running the channel-based event service + // on the same port as the other peer services. + let channel_event_hubs: ChannelEventHub[] = channel.getChannelEventHubsForOrg(); + // we should have the an channel event hub defined on the "peer0.org1.example.com" + t.equals(channel_event_hubs.length, 1, 'Checking that the channel event hubs has just one'); + + let channel_event_hub = channel_event_hubs[0]; + t.equals(channel_event_hub.getPeerAddr(), 'localhost:7051', ' channel event hub address '); + + let handle = setTimeout(() => { + t.fail('Timeout - Failed to receive replay the event for event1'); + channel_event_hub.unregisterTxEvent(query_tx_id); + channel_event_hub.disconnect(); //shutdown down since we are done + }, 10000); + + channel_event_hub.registerTxEvent(query_tx_id, (txnid, code, block_num) => { + clearTimeout(handle); + t.pass('Event has been replayed with transaction code:' + code + ' for transaction id:' + txnid + ' for block_num:' + block_num); + resolve('Got the replayed transaction'); + }, (error) => { + clearTimeout(handle); + t.fail('Failed to receive event replay for Event for transaction id ::' + query_tx_id); + throw (error); + }, + // a real application would have remembered the last block number + // received and used that value to start the replay + // Setting the disconnect to true as we do not want to use this + // ChannelEventHub after the event we are looking for comes in + { startBlock: 0, disconnect: true } + ); + t.pass('Successfully registered transaction replay for ' + query_tx_id); + + channel_event_hub.connect(); //connect to receive filtered blocks + t.pass('Successfully called connect on the transaction replay event hub for filtered blocks'); + }); + }).then((results) => { + t.pass('Successfully checked replay'); + // check that we can get the user again without password + // also verifies that we can get a complete user properly stored + // when using a connection profile + return client.setUserContext({ username: 'admin' }); + }).then((admin: User) => { + t.pass('Successfully loaded user \'admin\' from store for org1'); + + var request: ChaincodeQueryRequest = { + chaincodeId: 'example', + fcn: 'query', + args: ['b'] + }; + + return channel.queryByChaincode(request); //logged in as user on org1 + }).then((response_payloads) => { + // should only be one response ...as only one peer is defined as CHAINCODE_QUERY_ROLE + var query_responses = 0; + if (response_payloads) { + for (let i = 0; i < response_payloads.length; i++) { + query_responses++; + t.equal( + response_payloads[i].toString('utf8'), + '300', + 'checking query results are correct that user b has 300 now after the move'); + } + } else { + t.fail('response_payloads is null'); + throw new Error('Failed to get response on query'); + } + t.equals(query_responses, 1, 'Checking that only one response was seen'); + + return client.queryChannels('peer0.org1.example.com'); + }).then((results: ChannelQueryResponse) => { + logger.debug(' queryChannels ::%j', results); + let found = false; + for (let i in results.channels) { + logger.debug(' queryChannels has found %s', results.channels[i].channel_id); + if (results.channels[i].channel_id === channel_name) { + found = true; + } + } + if (found) { + t.pass('Successfully found our channel in the result list'); + } else { + t.fail('Failed to find our channel in the result list'); + } + + return client.queryInstalledChaincodes('peer0.org1.example.com', true); // use admin + }).then((results: ChaincodeQueryResponse) => { + logger.debug(' queryInstalledChaincodes ::%j', results); + let found = false; + for (let i in results.chaincodes) { + logger.debug(' queryInstalledChaincodes has found %s', results.chaincodes[i].name); + if (results.chaincodes[i].name === 'example') { + found = true; + } + } + if (found) { + t.pass('Successfully found our chaincode in the result list'); + } else { + t.fail('Failed to find our chaincode in the result list'); + } + + return channel.queryBlock(1); + }).then((results: Block) => { + logger.debug(' queryBlock ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number'); + + return channel.queryInfo(); + }).then((results: BlockchainInfo) => { + logger.debug(' queryInfo ::%j', results); + t.equals(3, results.height.low, 'Should be able to find our block height'); + + return channel.queryBlockByHash(results.previousBlockHash); + }).then((results: Block) => { + logger.debug(' queryBlockHash ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number by hash'); + + return channel.queryTransaction(query_tx_id); + }).then((results) => { + logger.debug(' queryTransaction ::%j', results); + t.equals(0, results.validationCode, 'Should be able to find our transaction validationCode'); + + return channel.queryBlock(1, 'peer0.org1.example.com'); + }).then((results: Block) => { + logger.debug(' queryBlock ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number with string peer name'); + + return channel.queryInfo('peer0.org1.example.com'); + }).then((results: BlockchainInfo) => { + logger.debug(' queryInfo ::%j', results); + t.equals(3, results.height.low, 'Should be able to find our block height with string peer name'); + + return channel.queryBlockByHash(results.previousBlockHash, 'peer0.org1.example.com'); + }).then((results: Block) => { + logger.debug(' queryBlockHash ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number by hash with string peer name'); + + return channel.queryTransaction(query_tx_id, 'peer0.org1.example.com'); + }).then((results) => { + logger.debug(' queryTransaction ::%j', results); + t.equals(0, results.validationCode, 'Should be able to find our transaction validationCode with string peer name'); + + return channel.queryBlock(1, 'peer0.org1.example.com', true); + }).then((results: Block) => { + logger.debug(' queryBlock ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number by admin'); + + return channel.queryInfo('peer0.org1.example.com', true); + }).then((results: BlockchainInfo) => { + logger.debug(' queryInfo ::%j', results); + t.equals(3, results.height.low, 'Should be able to find our block height by admin'); + + return channel.queryBlockByHash(results.previousBlockHash, 'peer0.org1.example.com', true); + }).then((results: Block) => { + logger.debug(' queryBlockHash ::%j', results); + t.equals('1', results.header.number, 'Should be able to find our block number by hash by admin'); + + return channel.queryTransaction(query_tx_id, 'peer0.org1.example.com', true); + }).then((results) => { + logger.debug(' queryTransaction ::%j', results); + t.equals(0, results.validationCode, 'Should be able to find our transaction validationCode by admin'); + + let tx_id = client.newTransactionID(); // get a non admin transaction ID + var request: ChaincodeInvokeRequest = { + chaincodeId: 'example', + fcn: 'move', + args: ['a', 'b', '100'], + txId: tx_id + //targets - Letting default to all endorsing peers defined on the channel in the connection profile + }; + + // put in a very small timeout to force a failure, thereby checking that the timeout value was being used + return channel.sendTransactionProposal(request, 1); //logged in as org1 user + }).then((results: ProposalResponseObject) => { + var proposalResponses = results[0]; + for (var i in proposalResponses) { + let proposal_response = proposalResponses[i]; + if (proposal_response instanceof Error && proposal_response.toString().indexOf('REQUEST_TIMEOUT') > 0) { + t.pass('Successfully cause a timeout error by setting the timeout setting to 1'); + } else { + t.fail('Failed to get the timeout error'); + } + } + + return true; + }).then(() => { + t.pass('Testing has completed successfully'); + t.end(); + }).catch((err: Error) => { + t.fail(err.message); + t.end(); + throw err; + }) +}); + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/test/typescript/tsconfig.json b/test/typescript/tsconfig.json new file mode 100644 index 0000000000..27e65ab8a3 --- /dev/null +++ b/test/typescript/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2017", + "sourceMap": true + }, + "exclude": [ + "node_modules" + ] +}