'use strict' ; import * as vscode from 'vscode' ; import { ChannelTreeItem } from './model/ChannelTreeItem' ; import { BlockchainTreeItem } from './model/BlockchainTreeItem' ; import { GatewayAssociatedTreeItem } from './model/GatewayAssociatedTreeItem' ; import { GatewayDissociatedTreeItem } from './model/GatewayDissociatedTreeItem' ; import { FabricGatewayConnectionManager } from '../fabric/FabricGatewayConnectionManager' ; import { BlockchainExplorerProvider } from './BlockchainExplorerProvider' ; import { TransactionTreeItem } from './model/TransactionTreeItem' ; import { InstantiatedChaincodeTreeItem } from './model/InstantiatedChaincodeTreeItem' ; import { ConnectedTreeItem } from './model/ConnectedTreeItem' ; import { MetadataUtil } from '../util/MetadataUtil' ; import { ContractTreeItem } from './model/ContractTreeItem' ; import { VSCodeBlockchainOutputAdapter } from '../logging/VSCodeBlockchainOutputAdapter' ; import { LocalGatewayTreeItem } from './model/LocalGatewayTreeItem' ; import { ExtensionCommands } from '../../ExtensionCommands' ; import { InstantiatedContractTreeItem } from './model/InstantiatedContractTreeItem' ; import { InstantiatedTreeItem } from './model/InstantiatedTreeItem' ; import { FabricSmartContractDefinition , IFabricGatewayConnection , LogType , FabricGatewayRegistryEntry , FabricGatewayRegistry , FabricEnvironmentRegistryEntry , FabricEnvironmentRegistry } from 'ibm-blockchain-platform-common' ; import { InstantiatedMultiContractTreeItem } from './model/InstantiatedMultiContractTreeItem' ; import { InstantiatedUnknownTreeItem } from './model/InstantiatedUnknownTreeItem' ; import { InstantiatedAssociatedTreeItem } from './model/InstantiatedAssociatedTreeItem' ; import { InstantiatedAssociatedContractTreeItem } from './model/InstantiatedAssociatedContractTreeItem' ; import { InstantiatedAssociatedChaincodeTreeItem } from './model/InstantiatedAssociatedChaincodeTreeItem' ; import { InstantiatedAssociatedMultiContractTreeItem } from './model/InstantiatedAssociatedMultiContractTreeItem' ; import { TextTreeItem } from './model/TextTreeItem' ; import { GatewayTreeItem } from './model/GatewayTreeItem' ; import { GatewayGroupTreeItem } from './model/GatewayGroupTreeItem' ; import { ExplorerUtil } from '../util/ExplorerUtil' ; import { LocalMicroEnvironment } from '../fabric/environments/LocalMicroEnvironment' ; import { LocalMicroEnvironmentManager } from '../fabric/environments/LocalMicroEnvironmentManager' ; export class BlockchainGatewayExplorerProvider implements BlockchainExplorerProvider { public tree : Array < BlockchainTreeItem > = [ ] ; private _onDidChangeTreeData : vscode . EventEmitter < any | undefined > = new vscode . EventEmitter < any | undefined > ( ) ; readonly onDidChangeTreeData : vscode . Event < any | undefined > = this . _onDidChangeTreeData . event ; private fabricGatewayRegistry : FabricGatewayRegistry = FabricGatewayRegistry . instance ( ) ; private instantiatedChaincodeTreeItems : Array < any > = [ ] ; constructor ( ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; FabricGatewayConnectionManager . instance ( ) . on ( 'connected' , async ( ) => { try { await this . connect ( ) ; } catch ( error ) { outputAdapter . log ( LogType . ERROR , `Error handling connected event: ${ error . message }` , `Error handling connected event: ${ error . toString ( ) }` ) ; } } ) ; FabricGatewayConnectionManager . instance ( ) . on ( 'disconnected' , async ( ) => { try { await this . disconnect ( ) ; } catch ( error ) { outputAdapter . log ( LogType . ERROR , `Error handling disconnected event: ${ error . message }` , `Error handling disconnected event: ${ error . toString ( ) }` ) ; } } ) ; } async refresh ( element ? ) { this . _onDidChangeTreeData . fire ( element ) ; } async connect ( ) { await vscode . commands . executeCommand ( 'setContext' , 'blockchain-gateway-connected' , true ) ; await this . refresh ( ) ; } async disconnect ( ) { await vscode . commands . executeCommand ( 'setContext' , 'blockchain-gateway-connected' , false ) ; await this . refresh ( ) ; } getTreeItem ( element ) { return element ; } async getChildren ( element ? ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; try { if ( element ) { if ( element instanceof GatewayGroupTreeItem ) { this . tree = await this . populateGateways ( element . gateways ) ; } if ( element instanceof ChannelTreeItem ) { this . tree = [ ] ; const channelElement = element ; if ( channelElement . chaincodes . length > 0 ) { this . tree = await this . createInstantiatedChaincodeTree ( element ) ; } } if ( element instanceof ConnectedTreeItem && element . label === 'Channels' ) { try { this . tree = await this . getChannelsTree ( ) ; } catch ( error ) { this . tree = await this . createConnectionTree ( ) ; outputAdapter . log ( LogType . ERROR , `Could not connect to gateway: ${ error . message }` ) ; } } if ( element instanceof InstantiatedUnknownTreeItem ) { await this . populateInstantiatedTreeItems ( element ) ; await this . refresh ( element . channels [ 0 ] ) ; return ; } if ( element instanceof InstantiatedContractTreeItem ) { this . tree = await this . createContractTree ( element ) ; } if ( element instanceof ContractTreeItem ) { this . tree = await this . createTransactionsChaincodeTree ( element ) ; } return this . tree ; } else { this . instantiatedChaincodeTreeItems = [ ] ; } if ( FabricGatewayConnectionManager . instance ( ) . getConnection ( ) ) { this . tree = await this . createConnectedTree ( ) ; } else { this . tree = await this . createConnectionTree ( ) ; } } catch ( error ) { outputAdapter . log ( LogType . ERROR , error . message ) ; } return this . tree ; } private async createConnectionTree ( ) { const tree = [ ] ; let allGateways = await this . fabricGatewayRegistry . getAll ( ) ; allGateways = await this . updateGatewayEnvironmentGroup ( allGateways ) ; const gatewayGroups = [ ] ; const otherGateways = [ ] ; for ( const gateway of allGateways ) { if ( gatewayGroups . length === 0 ) { if ( gateway . fromEnvironment || gateway . environmentGroup ) { gatewayGroups . push ( [ gateway ] ) ; } else { otherGateways . push ( gateway ) ; } continue ; } const groupIndex = gatewayGroups . findIndex ( ( group ) => { if ( gateway . fromEnvironment ) { return gateway . fromEnvironment === group [ 0 ] . fromEnvironment || gateway . fromEnvironment === group [ 0 ] . environmentGroup ; } else if ( gateway . environmentGroup ) { return gateway . environmentGroup === group [ 0 ] . fromEnvironment || gateway . environmentGroup === group [ 0 ] . environmentGroup ; } } ) ; if ( groupIndex !== - 1 ) { gatewayGroups [ groupIndex ] . push ( gateway ) ; } else { if ( gateway . fromEnvironment || gateway . environmentGroup ) { gatewayGroups . push ( [ gateway ] ) ; } else { otherGateways . push ( gateway ) ; } } } if ( otherGateways . length > 0 ) { gatewayGroups . push ( otherGateways ) ; } if ( gatewayGroups . length === 0 ) { tree . push ( new TextTreeItem ( this , 'No gateways found' ) ) ; } else { for ( const group of gatewayGroups ) { let groupName = 'Other gateways' ; if ( group [ 0 ] . fromEnvironment ) { groupName = group [ 0 ] . fromEnvironment ; } else if ( group [ 0 ] . environmentGroup ) { groupName = group [ 0 ] . environmentGroup ; } const groupTreeItem = new GatewayGroupTreeItem ( this , groupName , group , vscode . TreeItemCollapsibleState . Expanded ) ; if ( groupName !== 'Other gateways' ) { groupTreeItem . iconPath = await ExplorerUtil . getGroupIcon ( groupName ) ; } tree . push ( groupTreeItem ) ; } } return tree ; } private async createInstantiatedChaincodeTree ( channelTreeElement ) { const tree = [ ] ; for ( const chaincode of channelTreeElement . chaincodes ) { const index = this . instantiatedChaincodeTreeItems . findIndex ( ( treeItem ) => { return `${ chaincode . name }@${ chaincode . version }` === treeItem . label && treeItem . channels [ 0 ] . label === channelTreeElement . label ; } ) ; if ( index !== - 1 ) { tree . push ( this . instantiatedChaincodeTreeItems [ index ] ) ; } else { const gateway = await FabricGatewayConnectionManager . instance ( ) . getGatewayRegistryEntry ( ) ; if ( ! gateway . transactionDataDirectories || gateway . transactionDataDirectories . length === 0 ) { tree . push ( new InstantiatedUnknownTreeItem ( this , chaincode . name , [ channelTreeElement ] , chaincode . version , vscode . TreeItemCollapsibleState . Collapsed , undefined , true ) ) ; } else { if ( this . checkForTxdataAssociations ( gateway , chaincode . name , [ channelTreeElement ] ) ) { tree . push ( new InstantiatedAssociatedTreeItem ( this , chaincode . name , [ channelTreeElement ] , chaincode . version , vscode . TreeItemCollapsibleState . Collapsed , undefined , true ) ) ; } else { tree . push ( new InstantiatedUnknownTreeItem ( this , chaincode . name , [ channelTreeElement ] , chaincode . version , vscode . TreeItemCollapsibleState . Collapsed , undefined , true ) ) ; } } } } return tree ; } private async populateInstantiatedTreeItems ( unknownTreeItem ) { const connection = FabricGatewayConnectionManager . instance ( ) . getConnection ( ) ; const gateway = await FabricGatewayConnectionManager . instance ( ) . getGatewayRegistryEntry ( ) ; const contracts = await MetadataUtil . getContractNames ( connection , unknownTreeItem . name , unknownTreeItem . channels [ 0 ] . label ) ; let newElement ; if ( ! contracts ) { newElement = ( this . checkForTxdataAssociations ( gateway , unknownTreeItem . name , unknownTreeItem . channels ) ) ? new InstantiatedAssociatedChaincodeTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . None , undefined , true ) : new InstantiatedChaincodeTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . None , undefined , true ) ; } else if ( contracts . length === 0 ) { newElement = ( this . checkForTxdataAssociations ( gateway , unknownTreeItem . name , unknownTreeItem . channels ) ) ? new InstantiatedAssociatedContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . None , contracts , true ) : new InstantiatedContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . None , contracts , true ) ; } else if ( contracts . length === 1 ) { newElement = ( this . checkForTxdataAssociations ( gateway , unknownTreeItem . name , unknownTreeItem . channels ) ) ? new InstantiatedAssociatedContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . Collapsed , contracts , true ) : new InstantiatedContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . Collapsed , contracts , true ) ; } else { newElement = ( this . checkForTxdataAssociations ( gateway , unknownTreeItem . name , unknownTreeItem . channels ) ) ? new InstantiatedAssociatedMultiContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . Collapsed , contracts , true ) : new InstantiatedMultiContractTreeItem ( this , unknownTreeItem . name , unknownTreeItem . channels , unknownTreeItem . version , vscode . TreeItemCollapsibleState . Collapsed , contracts , true ) ; } this . instantiatedChaincodeTreeItems . push ( newElement ) ; } private async createContractTree ( chainCodeElement ) { const tree = [ ] ; for ( const contract of chainCodeElement . contracts ) { const connection = FabricGatewayConnectionManager . instance ( ) . getConnection ( ) ; const transactionNamesMap = await MetadataUtil . getTransactionNames ( connection , chainCodeElement . name , chainCodeElement . channels [ 0 ] . label ) ; const transactionNames = transactionNamesMap . get ( contract ) ; if ( contract === '' || chainCodeElement . contracts . length === 1 ) { for ( const transaction of transactionNames ) { const command = { command : ExtensionCommands . OPEN_TRANSACTION_PAGE , title : '' , arguments : [ chainCodeElement , transaction ] } ; tree . push ( new TransactionTreeItem ( this , transaction , chainCodeElement . name , chainCodeElement . channels [ 0 ] . label , contract , command ) ) ; } } else { tree . push ( new ContractTreeItem ( this , contract , vscode . TreeItemCollapsibleState . Collapsed , chainCodeElement , transactionNames , chainCodeElement . channels [ 0 ] . label ) ) ; } } return tree ; } private async createTransactionsChaincodeTree ( contractTreeElement ) { const tree = [ ] ; contractTreeElement . transactions . forEach ( ( transaction ) => { const command = { command : ExtensionCommands . OPEN_TRANSACTION_PAGE , title : '' , arguments : [ contractTreeElement , transaction ] } ; tree . push ( new TransactionTreeItem ( this , transaction , contractTreeElement . instantiatedChaincode . name , contractTreeElement . instantiatedChaincode . channels [ 0 ] . label , contractTreeElement . name , command ) ) ; } ) ; return tree ; } private async populateGateways ( gateways ) { const tree = [ ] ; for ( const gateway of gateways ) { const command = { command : ExtensionCommands . CONNECT_TO_GATEWAY , title : '' , arguments : [ gateway ] } ; const gatewayName = gateway . displayName ? gateway . displayName : gateway . name ; let environmentEntry ; let runtime ; if ( gateway . fromEnvironment ) { environmentEntry = await FabricEnvironmentRegistry . instance ( ) . get ( gateway . fromEnvironment ) ; } if ( environmentEntry && environmentEntry . managedRuntime ) { runtime = await LocalMicroEnvironmentManager . instance ( ) . ensureRuntime ( environmentEntry . name , undefined , environmentEntry . numberOfOrgs ) ; const treeItem = await LocalGatewayTreeItem . newLocalGatewayTreeItem ( this , gatewayName , gateway , vscode . TreeItemCollapsibleState . None , runtime , command ) ; tree . push ( treeItem ) ; } else if ( gateway . associatedWallet ) { tree . push ( new GatewayAssociatedTreeItem ( this , gatewayName , gateway , vscode . TreeItemCollapsibleState . None , command ) ) ; } else { tree . push ( new GatewayDissociatedTreeItem ( this , gatewayName , gateway , vscode . TreeItemCollapsibleState . None , command ) ) ; } } return tree ; } private async createConnectedTree ( ) { try { const tree = [ ] ; const connection = FabricGatewayConnectionManager . instance ( ) . getConnection ( ) ; const gateway = await FabricGatewayConnectionManager . instance ( ) . getGatewayRegistryEntry ( ) ; const gatewayName = gateway . name ; tree . push ( new ConnectedTreeItem ( this , `Connected via gateway: ${ gatewayName }` , gateway , 0 ) ) ; tree . push ( new ConnectedTreeItem ( this , `Using ID: ${ connection . identityName }` , gateway , 0 ) ) ; tree . push ( new ConnectedTreeItem ( this , `Channels` , gateway , vscode . TreeItemCollapsibleState . Expanded ) ) ; return tree ; } catch ( error ) { FabricGatewayConnectionManager . instance ( ) . disconnect ( ) ; throw error ; } } private async getChannelsTree ( ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; try { const connection = FabricGatewayConnectionManager . instance ( ) . getConnection ( ) ; const channelMap = await connection . createChannelMap ( ) ; const channels = Array . from ( channelMap . keys ( ) ) ; const tree = [ ] ; for ( const channel of channels ) { let chaincodes ; const peers = channelMap . get ( channel ) ; const capability = await connection . getChannelCapabilityFromPeer ( channel , peers [ 0 ] ) ; try { chaincodes = await connection . getInstantiatedChaincode ( channel ) ; if ( chaincodes . length > 0 ) { tree . push ( new ChannelTreeItem ( this , channel , peers , chaincodes , capability [ 0 ] , vscode . TreeItemCollapsibleState . Collapsed ) ) ; } else { tree . push ( new ChannelTreeItem ( this , channel , peers , chaincodes , capability [ 0 ] , vscode . TreeItemCollapsibleState . None ) ) ; } } catch ( error ) { tree . push ( new ChannelTreeItem ( this , channel , peers , [ ] , capability [ 0 ] , vscode . TreeItemCollapsibleState . None ) ) ; outputAdapter . log ( LogType . ERROR , `Error getting instantiated smart contracts for channel ${ channel } ${ error . message }` ) ; } } return tree ; } catch ( error ) { FabricGatewayConnectionManager . instance ( ) . disconnect ( ) ; throw error ; } } private checkForTxdataAssociations ( gateway , chaincodeName , channels ) { if ( ! gateway . transactionDataDirectories || gateway . transactionDataDirectories . length === 0 ) { return false ; } else { return gateway . transactionDataDirectories . some ( ( association ) => { for ( const channel of channels ) { return association . chaincodeName === chaincodeName && association . channelName === channel . label ; } } ) ; } } private async updateGatewayEnvironmentGroup ( gatewayRegistryEntries ) { for ( const gateway of gatewayRegistryEntries ) { if ( gateway . environmentGroup ) { const envExists = await FabricEnvironmentRegistry . instance ( ) . exists ( gateway . environmentGroup ) ; if ( ! envExists ) { delete gateway . environmentGroup ; await FabricGatewayRegistry . instance ( ) . update ( gateway ) ; } } } return gatewayRegistryEntries ; } }