def _get_stream(self): """ get the events of the channel. Return: the events in success or None in fail. """ _logger.info("create peer delivery stream") if self._signed_event is not None: return self._peer.delivery(self._signed_event, filtered=self._filtered) seek_info = self._create_seek_info(self._start, self._stop) kwargs = {} if self._peer._client_cert_path: with open(self._peer._client_cert_path, 'rb') as f: b64der = pem_to_der(f.read()) kwargs['tls_cert_hash'] = sha256(b64der).digest() tx_context = TXContext(self._requestor, self._requestor.cryptoSuite, TXProposalRequest()) seek_info_header = build_channel_header( common_pb2.HeaderType.Value('DELIVER_SEEK_INFO'), tx_context.tx_id, self._channel_name, current_timestamp(), tx_context.epoch, **kwargs) seek_header = build_header(tx_context.identity, seek_info_header, tx_context.nonce) seek_payload_bytes = create_seek_payload(seek_header, seek_info) sig = tx_context.sign(seek_payload_bytes) envelope = create_envelope(sig, seek_payload_bytes) # this is a stream response return self._peer.delivery(envelope, filtered=self._filtered)
class Client(object): """ Main interaction handler with end user. Client can maintain several channels. """ def __init__(self, net_profile=None): """ Construct client""" self._crypto_suite = None self._tx_context = None self.kv_store_path = None # TODO: fix t.his as private later self._state_store = None self._is_dev_mode = False self.network_info = dict() self._organizations = dict() self._users = dict() self._channels = dict() self._peers = dict() self._orderers = dict() self._CAs = dict() if net_profile: _logger.debug("Init client with profile={}".format(net_profile)) self.init_with_net_profile(net_profile) def init_with_net_profile(self, profile_path='network.json'): """ Load the connection profile from external file to network_info. Init the handlers for orgs, peers, orderers, ca nodes :param profile_path: The connection profile file path :return: """ with open(profile_path, 'r') as profile: d = json.load(profile) self.network_info = d # read kv store path self.kv_store_path = self.get_net_info('client', 'credentialStore', 'path') if self.kv_store_path: self._state_store = FileKeyValueStore(self.kv_store_path) else: _logger.warning( 'No kv store path exists in profile {}'.format(profile_path)) # Init organizations orgs = self.get_net_info('organizations') for name in orgs: _logger.debug("create org with name={}".format(name)) org = create_org(name, orgs[name], self.state_store) self._organizations[name] = org # Init CAs # TODO # Init orderer nodes orderers = self.get_net_info('orderers') _logger.debug("Import orderers = {}".format(orderers.keys())) for name in orderers: orderer = Orderer(name=name, endpoint=orderers[name]['url']) orderer.init_with_bundle(orderers[name]) self.orderers[name] = orderer # Init peer nodes peers = self.get_net_info('peers') _logger.debug("Import peers = {}".format(peers.keys())) for name in peers: peer = Peer(name=name) peer.init_with_bundle(peers[name]) self._peers[name] = peer def get_user(self, org_name, name): """ Get a user instance. :param org_name: Name of org belongs to :param name: Name of the user :return: user instance or None """ if org_name in self.organizations: org = self.organizations[org_name] return org.get_user(name) return None def get_orderer(self, name): """ Get an orderer instance with the name. :param name: Name of the orderer node. :return: The orderer instance or None. """ if name in self.orderers: return self.orderers[name] else: _logger.warning("Cannot find orderer with name {}".format(name)) return None def get_peer(self, name): """ Get a peer instance with the name. :param name: Name of the peer node. :return: The peer instance or None. """ if name in self._peers: return self._peers[name] else: _logger.warning("Cannot find peer with name {}".format(name)) return None def export_net_profile(self, export_file='network_exported.json'): """ Export the current network profile into external file :param export_file: External file to save the result into :return: """ with open(export_file, 'w') as f: json.dump(self.network_info, f, indent=4) def get_net_info(self, *key_path): """ Get the info from self.network_info :param key_path: path of the key, e.g., a.b.c means info['a']['b']['c'] :return: The value, or None """ result = self.network_info if result: for k in key_path: try: result = result[k] except KeyError: _logger.warning( 'No key path {} exists in net info'.format(key_path)) return None return result @property def organizations(self): """ Get the organizations in the network. :return: organizations as dict """ return self._organizations @property def orderers(self): """ Get the orderers in the network. :return: orderers as dict """ return self._orderers @property def peers(self): """ Get the peers instance in the network. :return: peers as dict """ return self._peers @property def CAs(self): """ Get the CAs in the network. :return: CAs as dict """ return self._CAs def new_channel(self, name): """Create a channel handler instance with given name. Args: name (str): The name of the channel. Returns: channel: The inited channel. """ _logger.debug("New channel with name = {}".format(name)) if name not in self._channels: self._channels[name] = Channel(name, self) return self._channels[name] def get_channel(self, name): """Get a channel handler instance. Args: name (str): The name of the channel. Returns: Get the channel instance with the name or None """ return self._channels.get(name, None) def channel_create(self, orderer_name, channel_name, requestor, config_yaml, channel_profile): """ Create a channel, send request to orderer, and check the response :param orderer_name: Name of orderer to send request to :param channel_name: Name of channel to create :param requestor: Name of creator :param config_yaml: Directory path of config yaml to be set for FABRIC_ CFG_PATH variable :param channel_profile: Name of the channel profile defined inside config yaml file :return: True (creation succeeds) or False (creation failed) """ if self.get_channel(channel_name): _logger.warning("channel {} already existed when creating".format( channel_name)) return True orderer = self.get_orderer(orderer_name) if not orderer: _logger.error("No orderer_name instance found with name {}".format( orderer_name)) return False tx = self.generate_channel_tx(channel_name, config_yaml, channel_profile) if tx is None: _logger.error('Configtx is empty') return False _logger.info("Configtx file sucessfully created in current directory") with open(tx, 'rb') as f: envelope = f.read() config = utils.extract_channel_config(envelope) # convert envelope to config self.tx_context = TXContext(requestor, Ecies(), {}) tx_id = self.tx_context.tx_id nonce = self.tx_context.nonce signatures = [] org1_admin_signature = self.sign_channel_config(config) # append org1_admin_signature to signatures signatures.append(org1_admin_signature) request = { 'tx_id': tx_id, 'nonce': nonce, 'signatures': signatures, 'config': config, 'orderer': orderer, 'channel_name': channel_name } q = Queue(1) response = self._create_channel(request) response.subscribe(on_next=lambda x: q.put(x), on_error=lambda x: q.put(x)) status, _ = q.get(timeout=5) _logger.debug(status) if status.status == 200: self.new_channel(channel_name) return True else: return False def channel_join(self, requestor, channel_name, peer_names, orderer_name): """ Join a channel. Get genesis block from orderer, then send request to peer :param requestor: User to send the request :param channel_name: Name of channel to create :param peer_names: List of peers to join to the channel :param orderer_name: Name of orderer to get genesis block from :return: True (creation succeeds) or False (creation failed) """ channel = self.get_channel(channel_name) if not channel: _logger.warning( "channel {} not existed when join".format(channel_name)) return False orderer = self.get_orderer(orderer_name) if not orderer: _logger.warning("orderer {} not existed when channel join".format( orderer_name)) return False tx_prop_req = TXProposalRequest() # get the genesis block orderer_admin = self.get_user('orderer.example.com', 'Admin') tx_context = TXContext(orderer_admin, ecies(), tx_prop_req) genesis_block = orderer.get_genesis_block( tx_context, channel.name).SerializeToString() # create the peer tx_context = TXContext(requestor, ecies(), tx_prop_req) peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) """ # connect the peer eh = EventHub() event = peer_config['grpc_event_endpoint'] tx_id = client.tx_context.tx_id eh.set_peer_addr(event) eh.connect() eh.register_block_event(block_event_callback) all_ehs.append(eh) """ request = { "targets": peers, "block": genesis_block, "tx_context": tx_context, "transient_map": {} } return channel.join_channel(request) def chaincode_install(self, requestor, peer_names, cc_path, cc_name, cc_version, timeout=5): """ Install chaincode to given peers by requestor role :param requestor: User role who issue the request :param peer_names: Names of the peers to install :param cc_path: chaincode path :param cc_name: chaincode name :param cc_version: chaincode version :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(CC_INSTALL, cc_path, CC_TYPE_GOLANG, cc_name, cc_version) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) sleep(5) response = self.send_install_proposal(tx_context, peers) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) res = queue.get(timeout=timeout) proposal_response, _ = res[0][0] return proposal_response.response.status == 200 def _create_channel(self, request): """Calls the orderer to start building the new channel. Args: request (dct): The create channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False _logger.debug(request) if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def update_channel(self, request): """Calls the orderer to update an existing channel. Args: request (dct): The update channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def _validate_request(self, request): """ Validate a request :param request: request to validate :return: """ # TODO: implement this to validate the request pass def _create_or_update_channel_request(self, request, have_envelope): """Inits the create of update channel process. Args: request (dct): A create_update channel request. have_envelope (bool): Signals if the requests contains a finished protobuf envelope. Returns: rx.Observable: An observable for the orderer_response or an error. """ _logger.debug('_create_or_update_channel - start') error_msg = None if 'config' not in request and not have_envelope: error_msg = 'Missing config request parameter containing ' \ 'the configuration of the channel' if 'signatures' not in request and not have_envelope: error_msg = 'Missing signatures request parameter for the ' \ 'new channel' elif 'signatures' in request and \ not isinstance(request['signatures'], list) \ and not have_envelope: error_msg = 'Signatures request parameter must be an array ' \ 'of signatures' if 'tx_id' not in request and not have_envelope: error_msg = 'Missing tx_id request parameter' if 'nonce' not in request and not have_envelope: error_msg = 'Missing nonce request parameter' if 'orderer' not in request: error_msg = 'Missing orderer request parameter' if 'channel_name' not in request: error_msg = 'Missing channel_name request parameter' if error_msg: _logger.error( '_create_or_update_channel error: {}'.format(error_msg)) raise ValueError(error_msg) if have_envelope: _logger.debug('_create_or_update_channel - have envelope') envelope = common_pb2.Envelope() envelope.ParseFromString(request['envelope']) signature = envelope.signature payload = envelope.payload else: _logger.debug('_create_or_update_channel - have config_update') proto_config_update_envelope = configtx_pb2.ConfigUpdateEnvelope() proto_config_update_envelope.config_update = request['config'] # convert signatures to protobuf signature signatures = request['signatures'] proto_signatures = utils.string_to_signature(signatures) proto_config_update_envelope.signatures.extend(proto_signatures) proto_channel_header = utils.build_channel_header( common_pb2.HeaderType.Value('CONFIG_UPDATE'), request['tx_id'], request['channel_name'], utils.current_timestamp()) proto_header = utils.build_header(self.tx_context.identity, proto_channel_header, request['nonce']) proto_payload = common_pb2.Payload() proto_payload.header.CopyFrom(proto_header) proto_payload.data = proto_config_update_envelope \ .SerializeToString() payload_bytes = proto_payload.SerializeToString() signature_bytes = self.tx_context.sign(payload_bytes) signature = signature_bytes payload = payload_bytes # assemble the final envelope out_envelope = common_pb2.Envelope() out_envelope.signature = signature out_envelope.payload = payload orderer = request['orderer'] return orderer.broadcast(out_envelope) def sign_channel_config(self, config, to_string=True): """This method uses the client instance's current signing identity to sign over the configuration bytes passed in. Args: config: The configuration update in bytes form. to_string: Whether to convert the result to string Returns: config_signature (common_pb2.ConfigSignature): The signature of the current user of the config bytes. """ sign_channel_context = self.tx_context proto_signature_header = common_pb2.SignatureHeader() proto_signature_header.creator = sign_channel_context.identity proto_signature_header.nonce = sign_channel_context.nonce proto_signature_header_bytes = \ proto_signature_header.SerializeToString() signing_bytes = proto_signature_header_bytes + config signature_bytes = sign_channel_context.sign(signing_bytes) proto_config_signature = configtx_pb2.ConfigSignature() proto_config_signature.signature_header = proto_signature_header_bytes proto_config_signature.signature = signature_bytes if to_string: return proto_config_signature.SerializeToString() else: return proto_config_signature @property def crypto_suite(self): """Get the crypto suite. Returns: The crypto_suite instance or None """ return self._crypto_suite @crypto_suite.setter def crypto_suite(self, crypto_suite): """Set the crypto suite to given one. Args: crypto_suite: The crypto_suite to use. Returns: None """ self._crypto_suite = crypto_suite @property def tx_context(self): """ Get the current tx_context for the client. Returns: The tx_context object or None """ return self._tx_context @tx_context.setter def tx_context(self, tx_context): """Set the tx_context to the given one. Args: tx_context: The tx_context to be used. Return: None """ self._tx_context = tx_context @property def state_store(self): """ Get the KeyValue store. Return the keyValue store instance or None """ return self._state_store @state_store.setter def state_store(self, state_store): """ Set the KeyValue store. Args: state_store: the KeyValue store to use. No return Value """ self._state_store = state_store def send_install_proposal(self, tx_context, peers, scheduler=None): """ Send install proposal Args: tx_context: transaction context peers: peers scheduler: rx scheduler Returns: A set of proposal_response """ return utils.send_install_proposal(tx_context, peers, scheduler) def send_instantiate_proposal(self, tx_context, peers, channel_name='businesschannel'): """ Send instantiate proposal Args: tx_context: transaction context peers: peers channel_name: the name of channel Returns: A set of proposal_response """ app_channel = self.get_channel(channel_name) _logger.debug("context {}".format(tx_context)) return app_channel.send_instantiate_proposal(tx_context, peers) def generate_channel_tx(self, channel_name, config_yaml, channel_profile): """ Creates channel configuration transaction Args: :param channel_name: Name of the channel :param config_yaml: Directory path of config yaml to be set for FABRIC_CFG_PATH variable :param channel_profile: Name of the channel profile defined inside config yaml file Returns: path to tx file if success else None """ # check if configtxgen is in PATH def cmd_exists(cmd): return any( os.access(os.path.join(path, cmd), os.X_OK) for path in os.environ["PATH"].split(os.pathsep)) if not cmd_exists('configtxgen'): _logger.error("configtxgen not in PATH.") return False # Generate channel.tx with configtxgen tx_path = "/tmp/channel.tx" config_yaml = config_yaml if os.path.isabs(config_yaml) else \ os.getcwd() + "/" + config_yaml _logger.info("FABRIC_CFG_PATH set to {}".format(config_yaml)) new_env = dict(os.environ, FABRIC_CFG_PATH=config_yaml) output = subprocess.Popen([ 'configtxgen', '-profile', channel_profile, '-outputCreateChannelTx', tx_path, '-channelID', channel_name ], stdout=open(os.devnull, "w"), stderr=subprocess.PIPE, env=new_env) err = output.communicate()[1] if output.returncode: _logger.error('Failed to generate transaction file', err) return None return tx_path def chaincode_instantiate(self, requestor, channel_name, peer_names, args, cc_name, cc_version, timeout=5): """ Instantiate installed chaincode to particular peer in particular channel :param requestor: User role who issue the request :param channel_name: the name of the channel to send tx proposal :param peer_names: Names of the peers to install :param args (list): arguments (keys and values) for initialization :param cc_name: chaincode name :param cc_version: chaincode version :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req_dep = create_tx_prop_req(prop_type=CC_INSTANTIATE, cc_type=CC_TYPE_GOLANG, cc_name=cc_name, cc_version=cc_version, fcn='init', args=args) tx_context_dep = create_tx_context(requestor, ecies(), tran_prop_req_dep) res = self.send_instantiate_proposal(tx_context_dep, peers, channel_name) sleep(5) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tran_req = utils.build_tx_req(res) response = utils.send_transaction(self.orderers, tran_req, tx_context) sleep(5) self.txid_for_test = tx_context_dep.tx_id # used only for query test queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) res, _ = queue.get(timeout=timeout) _logger.debug(res) return res.status == 200 def chaincode_invoke(self, requestor, channel_name, peer_names, args, cc_name, cc_version, timeout=5): """ Invoke chaincode for ledger update :param requestor: User role who issue the request :param channel_name: the name of the channel to send tx proposal :param peer_names: Names of the peers to install :param args (list): arguments (keys and values) for initialization :param cc_name: chaincode name :param cc_version: chaincode version :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(prop_type=CC_INVOKE, cc_type=CC_TYPE_GOLANG, cc_name=cc_name, cc_version=cc_version, fcn='invoke', args=args) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) res = self.get_channel(channel_name).send_tx_proposal( tx_context, peers) tran_req = utils.build_tx_req(res) tx_context_tx = create_tx_context(requestor, ecies(), tran_req) utils.send_transaction(self.orderers, tran_req, tx_context_tx) sleep(5) queue = Queue(1) res.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) res = queue.get(timeout=timeout) _logger.debug(res) return res[0][0][0].response.status == 200 def query_installed_chaincodes(self, requestor, peer_names, timeout=5): """ Queries installed chaincode, returns all chaincodes installed on a peer :param requestor: User role who issue the request :param peer_names: Names of the peers to query :return: A `ChaincodeQueryResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) request = create_tx_prop_req(prop_type=CC_QUERY, fcn='getinstalledchaincodes', cc_name='lscc', cc_type=CC_TYPE_GOLANG, args=[]) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tx_context.tx_prop_req = request response = Channel('businesschannel', self).send_tx_proposal(tx_context, peers) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: query_trans = query_pb2.ChaincodeQueryResponse() query_trans.ParseFromString(res[0][0][0].response.payload) for cc in query_trans.chaincodes: _logger.debug('cc name {}, version {}, path {}'.format( cc.name, cc.version, cc.path)) return query_trans return response except Exception: _logger.error("Failed to query installed chaincodes: {}", sys.exc_info()[0]) raise def query_channels(self, requestor, peer_names, timeout=5): """ Queries channel name joined by a peer :param requestor: User role who issue the request :param peer_names: Names of the peers to install :return: A `ChannelQueryResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) request = create_tx_prop_req(prop_type=CC_QUERY, fcn='GetChannels', cc_name='cscc', cc_type=CC_TYPE_GOLANG, args=[]) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tx_context.tx_prop_req = request response = Channel('businesschannel', self).send_tx_proposal(tx_context, peers) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: query_trans = query_pb2.ChannelQueryResponse() query_trans.ParseFromString(res[0][0][0].response.payload) for ch in query_trans.channels: _logger.debug('channel id {}'.format(ch.channel_id)) return query_trans return response except Exception: _logger.error("Failed to query channel: {}", sys.exc_info()[0]) raise def query_info(self, requestor, channel_name, peer_names, timeout=5): """ Queries information of a channel :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :return: A `BlockchainInfo` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_info(tx_context, peers) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: chain_info = ledger_pb2.BlockchainInfo() chain_info.ParseFromString(response.response.payload) _logger.debug('response status {}'.format( response.response.status)) return chain_info return response except Exception: _logger.error("Failed to query info: {}", sys.exc_info()[0]) raise def query_block_by_txid(self, requestor, channel_name, peer_names, tx_id, timeout=5): """ Queries block by tx id :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :param tx_id: Transaction ID :return: A `BlockDecoder` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_block_by_txid(tx_context, peers, tx_id) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: _logger.debug('response status {}'.format( response.response.status)) block = BlockDecoder().decode(response.response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return response except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_block_by_hash(self, requestor, channel_name, peer_names, block_hash, timeout=5): """ Queries block by hash :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :param block_hash: Hash of a block :return: A `BlockDecoder` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_block_by_hash(tx_context, peers, block_hash) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: _logger.debug('response status {}'.format( response.response.status)) block = BlockDecoder().decode(response.response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return response except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_block(self, requestor, channel_name, peer_names, block_number, timeout=5): """ Queries block by number :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to install :param block_number: Number of a block :return: A `BlockDecoder` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_block(tx_context, peers, block_number) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: _logger.debug('response status {}'.format( response.response.status)) block = BlockDecoder().decode(response.response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return response except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_transaction(self, requestor, channel_name, peer_names, tx_id, timeout=5): """ Queries block by number :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to install :param tx_id: The id of the transaction :return: A `BlockDecoder` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_transaction(tx_context, peers, tx_id) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) response = res[0][0][0] if response.response: _logger.debug('response status {}'.format( response.response.status)) process_trans = BlockDecoder().decode_transaction( response.response.payload) return process_trans return response except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_instantiated_chaincodes(self, requestor, channel_name, peer_names, timeout=5): """ Queries instantiated chaincode :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to query :return: A `ChaincodeQueryResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) response = channel.query_instantiated_chaincodes(tx_context, peers) queue = Queue(1) response.subscribe(on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x)) try: res = queue.get(timeout=timeout) _logger.debug(res) response = res[0][0][0] if response.response: query_trans = query_pb2.ChaincodeQueryResponse() query_trans.ParseFromString(res[0][0][0].response.payload) for cc in query_trans.chaincodes: _logger.debug('cc name {}, version {}, path {}'.format( cc.name, cc.version, cc.path)) return query_trans return response except Exception: _logger.error("Failed to query instantiated chaincodes: {}", sys.exc_info()[0]) raise
class Client(object): """ Main interaction handler with end user. Client can maintain several channels. """ def __init__(self, net_profile=None): """ Construct client""" self._channels = dict() self._crypto_suite = None self._tx_context = None self.kv_store_path = None # TODO: fix this as private later self._state_store = None self._is_dev_mode = False self.network_info = dict() self._organizations = dict() self._users = dict() self._channels = dict() self._peers = dict() self._orderers = dict() self._CAs = dict() if net_profile: logging.debug("Init client with profile={}".format(net_profile)) self.init_with_net_profile(net_profile) def init_with_net_profile(self, profile_path='network.json'): """ Load the connection profile from external file to network_info. :param profile_path: The connection profile file path :return: """ with open(profile_path, 'r') as profile: d = json.load(profile) self.network_info = d # read kv store path self.kv_store_path = self.get_net_info('client', 'credentialStore', 'path') if self.kv_store_path: self._state_store = FileKeyValueStore(self.kv_store_path) else: logging.warning( 'No kv store path exists in profile {}'.format(profile_path)) # Init organizations orgs = self.get_net_info('organizations') for name in orgs: org = create_org(name, orgs[name], self.state_store) self._organizations[name] = org # Init CAs # TODO # Init orderer nodes orderers = self.get_net_info('orderers') for name in orderers: orderer = Orderer(name=name) orderer.init_with_bundle(orderers[name]) self.orderers[name] = orderer # Init peer nodes peers = self.get_net_info('peers') for name in peers: peer = Peer(name=name) peer.init_with_bundle(peers[name]) self._peers[name] = peer def get_user(self, org_name, name): """ Get a user instance. :param org_name: Name of org belongs to :param name: Name of the user :return: user instance or None """ if org_name in self.organizations: org = self.organizations[org_name] return org.get_user(name) return None def get_orderer(self, name): """ Get an orderer instance with the name. :param name: Name of the orderer node. :return: The orderer instance or None. """ if name in self.orderers: return self.orderers[name] else: return None def get_peer(self, name): """ Get a peer instance with the name. :param name: Name of the peer node. :return: The peer instance or None. """ if name in self._peers: return self._peers['name'] else: return None def export_net_profile(self, export_file='network_exported.json'): """ Export the current network profile into external file :param export_file: External file to save the result into :return: """ with open(export_file, 'w') as f: json.dump(self.network_info, f, indent=4) def get_net_info(self, *key_path): """ Get the info from self.network_info :param key_path: path of the key, e.g., a.b.c means info['a']['b']['c'] :return: The value, or None """ result = self.network_info if result: for k in key_path: try: result = result[k] except KeyError: logging.warning( 'No key path {} exists in net info'.format(key_path)) return None return result @property def organizations(self): """ Get the organizations in the network. :return: organizations as dict """ return self._organizations @property def orderers(self): """ Get the orderers in the network. :return: orderers as dict """ return self._orderers @property def peers(self): """ Get the peers instance in the network. :return: peers as dict """ return self._peers @property def CAs(self): """ Get the CAs in the network. :return: CAs as dict """ return self._CAs def new_channel(self, name): """Init a channel instance with given name. Args: name (str): The name of the channel. Returns: channel: The inited channel. """ _logger.debug("New channel with name = {}".format(name)) if name not in self._channels: self._channels[name] = Channel(name, self) return self._channels[name] def get_channel(self, name): """Get a channel instance. Args: name (str): The name of the channel. Returns: Get the channel instance with the name or None """ return self._channels.get(name, None) def create_channel(self, orderer_name, channel, creator, tx): """ Create a channel :param orderer_name: Name of orderer to send request to :param channel: Name of channel to create :param creator: Name of creator :param tx: Path to the new channel tx file :return: """ orderer = self.get_orderer(orderer_name) if not orderer: logging.error("No orderer_name instance found with name {}".format( orderer_name)) return None with open(tx, 'rb') as f: envelope = f.read() config = extract_channel_config(envelope) # convert envelope to config self.tx_context = TXContext(creator, Ecies(), {}) tx_id = self.tx_context.tx_id nonce = self.tx_context.nonce signatures = [] org1_admin_signature = self.sign_channel_config(config) # append org1_admin_signature to signatures signatures.append(org1_admin_signature) request = { 'tx_id': tx_id, 'nonce': nonce, 'signatures': signatures, 'config': config, 'orderer': orderer, 'channel_name': channel } return self._create_channel(request) def _create_channel(self, request): """Calls the orderer to start building the new channel. Args: request (dct): The create channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False logging.debug(request) if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def update_channel(self, request): """Calls the orderer to update an existing channel. Args: request (dct): The update channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def _validate_request(self, request): """ Validate a request :param request: request to validate :return: """ # TODO: implement this to validate the request pass def _create_or_update_channel_request(self, request, have_envelope): """Inits the create of update channel process. Args: request (dct): A create_update channel request. have_envelope (bool): Signals if the requests contains a finished protobuf envelope. Returns: rx.Observable: An observable for the orderer_response or an error. """ _logger.debug('_create_or_update_channel - start') error_msg = None if 'config' not in request and not have_envelope: error_msg = 'Missing config request parameter containing ' \ 'the configuration of the channel' if 'signatures' not in request and not have_envelope: error_msg = 'Missing signatures request parameter for the ' \ 'new channel' elif 'signatures' in request and \ not isinstance(request['signatures'], list) \ and not have_envelope: error_msg = 'Signatures request parameter must be an array ' \ 'of signatures' if 'tx_id' not in request and not have_envelope: error_msg = 'Missing tx_id request parameter' if 'nonce' not in request and not have_envelope: error_msg = 'Missing nonce request parameter' if 'orderer' not in request: error_msg = 'Missing orderer request parameter' if 'channel_name' not in request: error_msg = 'Missing channel_name request parameter' if error_msg: _logger.error( '_create_or_update_channel error: {}'.format(error_msg)) raise ValueError(error_msg) if have_envelope: _logger.debug('_create_or_update_channel - have envelope') envelope = common_pb2.Envelope() envelope.ParseFromString(request['envelope']) signature = envelope.signature payload = envelope.payload else: _logger.debug('_create_or_update_channel - have config_update') proto_config_update_envelope = configtx_pb2.ConfigUpdateEnvelope() proto_config_update_envelope.config_update = request['config'] # convert signatures to protobuf signature signatures = request['signatures'] proto_signatures = utils.string_to_signature(signatures) proto_config_update_envelope.signatures.extend(proto_signatures) proto_channel_header = utils.build_channel_header( common_pb2.HeaderType.Value('CONFIG_UPDATE'), request['tx_id'], request['channel_name'], utils.current_timestamp()) proto_header = utils.build_header(self.tx_context.identity, proto_channel_header, request['nonce']) proto_payload = common_pb2.Payload() proto_payload.header.CopyFrom(proto_header) proto_payload.data = proto_config_update_envelope \ .SerializeToString() payload_bytes = proto_payload.SerializeToString() signature_bytes = self.tx_context.sign(payload_bytes) signature = signature_bytes payload = payload_bytes # assemble the final envelope out_envelope = common_pb2.Envelope() out_envelope.signature = signature out_envelope.payload = payload orderer = request['orderer'] return orderer.broadcast(out_envelope) def sign_channel_config(self, config, to_string=True): """This method uses the client instance's current signing identity to sign over the configuration bytes passed in. Args: config: The configuration update in bytes form. to_string: Whether to convert the result to string Returns: config_signature (common_pb2.ConfigSignature): The signature of the current user of the config bytes. """ sign_channel_context = self.tx_context proto_signature_header = common_pb2.SignatureHeader() proto_signature_header.creator = sign_channel_context.identity proto_signature_header.nonce = sign_channel_context.nonce proto_signature_header_bytes = \ proto_signature_header.SerializeToString() signing_bytes = proto_signature_header_bytes + config signature_bytes = sign_channel_context.sign(signing_bytes) proto_config_signature = configtx_pb2.ConfigSignature() proto_config_signature.signature_header = proto_signature_header_bytes proto_config_signature.signature = signature_bytes if to_string: return proto_config_signature.SerializeToString() else: return proto_config_signature @property def crypto_suite(self): """Get the crypto suite. Returns: The crypto_suite instance or None """ return self._crypto_suite @crypto_suite.setter def crypto_suite(self, crypto_suite): """Set the crypto suite to given one. Args: crypto_suite: The crypto_suite to use. Returns: None """ self._crypto_suite = crypto_suite @property def tx_context(self): """ Get the current tx_context for the client. Returns: The tx_context object or None """ return self._tx_context @tx_context.setter def tx_context(self, tx_context): """Set the tx_context to the given one. Args: tx_context: The tx_context to be used. Return: None """ self._tx_context = tx_context @property def state_store(self): """ Get the KeyValue store. Return the keyValue store instance or None """ return self._state_store @state_store.setter def state_store(self, state_store): """ Set the KeyValue store. Args: state_store: the KeyValue store to use. No return Value """ self._state_store = state_store def send_install_proposal(self, tx_context, peers, scheduler=None): """ Send install proposal Args: scheduler: rx scheduler tx_context: transaction context peers: peers Returns: A set of proposal_response """ app_channel = create_app_channel(self, name="businesschannel") _logger.debug("context {}".format(tx_context)) return app_channel.send_install_proposal(tx_context, peers, scheduler)
class Client(object): """ Main interaction handler with end user. Client can maintain several channels. """ def __init__(self, net_profile=None): """ Construct client""" self._crypto_suite = None self._tx_context = None self.kv_store_path = None # TODO: fix t.his as private later self._state_store = None self._is_dev_mode = False self.network_info = dict() self._organizations = dict() self._users = dict() self._channels = dict() self._peers = dict() self._orderers = dict() self._CAs = dict() if net_profile: logging.debug("Init client with profile={}".format(net_profile)) self.init_with_net_profile(net_profile) def init_with_net_profile(self, profile_path='network.json'): """ Load the connection profile from external file to network_info. Init the handlers for orgs, peers, orderers, ca nodes :param profile_path: The connection profile file path :return: """ with open(profile_path, 'r') as profile: d = json.load(profile) self.network_info = d # read kv store path self.kv_store_path = self.get_net_info('client', 'credentialStore', 'path') if self.kv_store_path: self._state_store = FileKeyValueStore(self.kv_store_path) else: logging.warning('No kv store path exists in profile {}'.format( profile_path)) # Init organizations orgs = self.get_net_info('organizations') for name in orgs: logging.debug("create org with name={}".format(name)) org = create_org(name, orgs[name], self.state_store) self._organizations[name] = org # Init CAs # TODO # Init orderer nodes orderers = self.get_net_info('orderers') logging.debug("Import orderers = {}".format(orderers.keys())) for name in orderers: orderer = Orderer(name=name, endpoint=orderers[name]['url']) orderer.init_with_bundle(orderers[name]) self.orderers[name] = orderer # Init peer nodes peers = self.get_net_info('peers') logging.debug("Import peers = {}".format(peers.keys())) for name in peers: peer = Peer(name=name) peer.init_with_bundle(peers[name]) self._peers[name] = peer def get_user(self, org_name, name): """ Get a user instance. :param org_name: Name of org belongs to :param name: Name of the user :return: user instance or None """ if org_name in self.organizations: org = self.organizations[org_name] return org.get_user(name) return None def get_orderer(self, name): """ Get an orderer instance with the name. :param name: Name of the orderer node. :return: The orderer instance or None. """ if name in self.orderers: return self.orderers[name] else: logging.warning("Cannot find orderer with name {}".format(name)) return None def get_peer(self, name): """ Get a peer instance with the name. :param name: Name of the peer node. :return: The peer instance or None. """ if name in self._peers: return self._peers[name] else: logging.warning("Cannot find peer with name {}".format(name)) return None def export_net_profile(self, export_file='network_exported.json'): """ Export the current network profile into external file :param export_file: External file to save the result into :return: """ with open(export_file, 'w') as f: json.dump(self.network_info, f, indent=4) def get_net_info(self, *key_path): """ Get the info from self.network_info :param key_path: path of the key, e.g., a.b.c means info['a']['b']['c'] :return: The value, or None """ result = self.network_info if result: for k in key_path: try: result = result[k] except KeyError: logging.warning('No key path {} exists in net info'.format( key_path)) return None return result @property def organizations(self): """ Get the organizations in the network. :return: organizations as dict """ return self._organizations @property def orderers(self): """ Get the orderers in the network. :return: orderers as dict """ return self._orderers @property def peers(self): """ Get the peers instance in the network. :return: peers as dict """ return self._peers @property def CAs(self): """ Get the CAs in the network. :return: CAs as dict """ return self._CAs def new_channel(self, name): """Create a channel handler instance with given name. Args: name (str): The name of the channel. Returns: channel: The inited channel. """ _logger.debug("New channel with name = {}".format(name)) if name not in self._channels: self._channels[name] = Channel(name, self) return self._channels[name] def get_channel(self, name): """Get a channel handler instance. Args: name (str): The name of the channel. Returns: Get the channel instance with the name or None """ return self._channels.get(name, None) def channel_create(self, orderer_name, channel_name, requester, tx): """ Create a channel, send request to orderer, and check the response :param orderer_name: Name of orderer to send request to :param channel_name: Name of channel to create :param requester: Name of creator :param tx: Path to the new channel tx file :return: True (creation succeeds) or False (creation failed) """ if self.get_channel(channel_name): logging.warning("channel {} already existed when creating".format( channel_name)) return True orderer = self.get_orderer(orderer_name) if not orderer: logging.error("No orderer_name instance found with name {}".format( orderer_name)) return False with open(tx, 'rb') as f: envelope = f.read() config = extract_channel_config(envelope) # convert envelope to config self.tx_context = TXContext(requester, Ecies(), {}) tx_id = self.tx_context.tx_id nonce = self.tx_context.nonce signatures = [] org1_admin_signature = self.sign_channel_config(config) # append org1_admin_signature to signatures signatures.append(org1_admin_signature) request = { 'tx_id': tx_id, 'nonce': nonce, 'signatures': signatures, 'config': config, 'orderer': orderer, 'channel_name': channel_name } q = Queue(1) response = self._create_channel(request) response.subscribe(on_next=lambda x: q.put(x), on_error=lambda x: q.put(x)) status, _ = q.get(timeout=5) if status.status == 200: self.new_channel(channel_name) return True else: return False def channel_join(self, requester, channel_name, peer_names, orderer_name): """ Join a channel. Get genesis block from orderer, then send request to peer :param requester: User to send the request :param channel_name: Name of channel to create :param peer_names: List of peers to join to the channel :param orderer_name: Name of orderer to get genesis block from :return: True (creation succeeds) or False (creation failed) """ channel = self.get_channel(channel_name) if not channel: logging.warning("channel {} not existed when join".format( channel_name)) return False orderer = self.get_orderer(orderer_name) if not orderer: logging.warning("orderer {} not existed when channel join".format( orderer_name)) return False tx_prop_req = TXProposalRequest() # get the genesis block orderer_admin = self.get_user('orderer.example.com', 'Admin') tx_context = TXContext(orderer_admin, ecies(), tx_prop_req) genesis_block = orderer.get_genesis_block( tx_context, channel.name).SerializeToString() # create the peer tx_context = TXContext(requester, ecies(), tx_prop_req) peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) """ # connect the peer eh = EventHub() event = peer_config['grpc_event_endpoint'] tx_id = client.tx_context.tx_id eh.set_peer_addr(event) eh.connect() eh.register_block_event(block_event_callback) all_ehs.append(eh) """ request = { "targets": peers, "block": genesis_block, "tx_context": tx_context, "transient_map": {} } return channel.join_channel(request) def chaincode_install(self, requestor, peer_names, cc_path, cc_name, cc_version, timeout=5): """ Install chaincode to given peers by requestor role :param requestor: User role who issue the request :param peer_names: Names of the peers to install :param cc_path: chaincode path :param cc_name: chaincode name :param cc_version: chaincode version :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(CC_INSTALL, cc_path, CC_TYPE_GOLANG, cc_name, cc_version) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) # sleep(5) response = self.send_install_proposal(tx_context, peers) queue = Queue(1) response.subscribe( on_next=lambda x: queue.put(x), on_error=lambda x: queue.put(x) ) res = queue.get(timeout=timeout) proposal_response, _ = res[0][0] return proposal_response.response.status == 200 def _create_channel(self, request): """Calls the orderer to start building the new channel. Args: request (dct): The create channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False logging.debug(request) if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def update_channel(self, request): """Calls the orderer to update an existing channel. Args: request (dct): The update channel request. Returns: rx.Observable: An observable for the orderer_response or an error. """ have_envelope = False if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def _validate_request(self, request): """ Validate a request :param request: request to validate :return: """ # TODO: implement this to validate the request pass def _create_or_update_channel_request(self, request, have_envelope): """Inits the create of update channel process. Args: request (dct): A create_update channel request. have_envelope (bool): Signals if the requests contains a finished protobuf envelope. Returns: rx.Observable: An observable for the orderer_response or an error. """ _logger.debug('_create_or_update_channel - start') error_msg = None if 'config' not in request and not have_envelope: error_msg = 'Missing config request parameter containing ' \ 'the configuration of the channel' if 'signatures' not in request and not have_envelope: error_msg = 'Missing signatures request parameter for the ' \ 'new channel' elif 'signatures' in request and \ not isinstance(request['signatures'], list) \ and not have_envelope: error_msg = 'Signatures request parameter must be an array ' \ 'of signatures' if 'tx_id' not in request and not have_envelope: error_msg = 'Missing tx_id request parameter' if 'nonce' not in request and not have_envelope: error_msg = 'Missing nonce request parameter' if 'orderer' not in request: error_msg = 'Missing orderer request parameter' if 'channel_name' not in request: error_msg = 'Missing channel_name request parameter' if error_msg: _logger.error('_create_or_update_channel error: {}' .format(error_msg)) raise ValueError(error_msg) if have_envelope: _logger.debug('_create_or_update_channel - have envelope') envelope = common_pb2.Envelope() envelope.ParseFromString(request['envelope']) signature = envelope.signature payload = envelope.payload else: _logger.debug('_create_or_update_channel - have config_update') proto_config_update_envelope = configtx_pb2.ConfigUpdateEnvelope() proto_config_update_envelope.config_update = request['config'] # convert signatures to protobuf signature signatures = request['signatures'] proto_signatures = utils.string_to_signature(signatures) proto_config_update_envelope.signatures.extend(proto_signatures) proto_channel_header = utils.build_channel_header( common_pb2.HeaderType.Value('CONFIG_UPDATE'), request['tx_id'], request['channel_name'], utils.current_timestamp() ) proto_header = utils.build_header(self.tx_context.identity, proto_channel_header, request['nonce']) proto_payload = common_pb2.Payload() proto_payload.header.CopyFrom(proto_header) proto_payload.data = proto_config_update_envelope \ .SerializeToString() payload_bytes = proto_payload.SerializeToString() signature_bytes = self.tx_context.sign(payload_bytes) signature = signature_bytes payload = payload_bytes # assemble the final envelope out_envelope = common_pb2.Envelope() out_envelope.signature = signature out_envelope.payload = payload orderer = request['orderer'] return orderer.broadcast(out_envelope) def sign_channel_config(self, config, to_string=True): """This method uses the client instance's current signing identity to sign over the configuration bytes passed in. Args: config: The configuration update in bytes form. to_string: Whether to convert the result to string Returns: config_signature (common_pb2.ConfigSignature): The signature of the current user of the config bytes. """ sign_channel_context = self.tx_context proto_signature_header = common_pb2.SignatureHeader() proto_signature_header.creator = sign_channel_context.identity proto_signature_header.nonce = sign_channel_context.nonce proto_signature_header_bytes = \ proto_signature_header.SerializeToString() signing_bytes = proto_signature_header_bytes + config signature_bytes = sign_channel_context.sign(signing_bytes) proto_config_signature = configtx_pb2.ConfigSignature() proto_config_signature.signature_header = proto_signature_header_bytes proto_config_signature.signature = signature_bytes if to_string: return proto_config_signature.SerializeToString() else: return proto_config_signature @property def crypto_suite(self): """Get the crypto suite. Returns: The crypto_suite instance or None """ return self._crypto_suite @crypto_suite.setter def crypto_suite(self, crypto_suite): """Set the crypto suite to given one. Args: crypto_suite: The crypto_suite to use. Returns: None """ self._crypto_suite = crypto_suite @property def tx_context(self): """ Get the current tx_context for the client. Returns: The tx_context object or None """ return self._tx_context @tx_context.setter def tx_context(self, tx_context): """Set the tx_context to the given one. Args: tx_context: The tx_context to be used. Return: None """ self._tx_context = tx_context @property def state_store(self): """ Get the KeyValue store. Return the keyValue store instance or None """ return self._state_store @state_store.setter def state_store(self, state_store): """ Set the KeyValue store. Args: state_store: the KeyValue store to use. No return Value """ self._state_store = state_store def send_install_proposal(self, tx_context, peers, scheduler=None): """ Send install proposal Args: scheduler: rx scheduler tx_context: transaction context peers: peers Returns: A set of proposal_response """ app_channel = create_app_channel(self, name="businesschannel") _logger.debug("context {}".format(tx_context)) return app_channel.send_install_proposal(tx_context, peers, scheduler)
class Client(object): """ Main interaction handler with end user. Client can maintain several channels. """ def __init__(self, net_profile=None): """ Construct client""" self._crypto_suite = None self._tx_context = None self.kv_store_path = None # TODO: fix t.his as private later self._state_store = None self._is_dev_mode = False self.network_info = dict() self._organizations = dict() self._users = dict() self._channels = dict() self._peers = dict() self._orderers = dict() self._CAs = dict() if net_profile: _logger.debug("Init client with profile={}".format(net_profile)) self.init_with_net_profile(net_profile) def init_with_net_profile(self, profile_path='network.json'): """ Load the connection profile from external file to network_info. Init the handlers for orgs, peers, orderers, ca nodes :param profile_path: The connection profile file path :return: """ with open(profile_path, 'r') as profile: d = json.load(profile) self.network_info = d # read kv store path self.kv_store_path = self.get_net_info('client', 'credentialStore', 'path') if self.kv_store_path: self._state_store = FileKeyValueStore(self.kv_store_path) else: _logger.warning( 'No kv store path exists in profile {}'.format(profile_path)) # Init organizations orgs = self.get_net_info('organizations') for name in orgs: _logger.debug("create org with name={}".format(name)) org = create_org(name, orgs[name], self.state_store) self._organizations[name] = org # Init CAs # TODO # Init orderer nodes orderers = self.get_net_info('orderers') _logger.debug("Import orderers = {}".format(orderers.keys())) for name in orderers: orderer = Orderer(name=name, endpoint=orderers[name]['url']) orderer.init_with_bundle(orderers[name]) self.orderers[name] = orderer # Init peer nodes peers = self.get_net_info('peers') _logger.debug("Import peers = {}".format(peers.keys())) for name in peers: peer = Peer(name=name) peer.init_with_bundle(peers[name]) self._peers[name] = peer def get_user(self, org_name, name): """ Get a user instance. :param org_name: Name of org belongs to :param name: Name of the user :return: user instance or None """ if org_name in self.organizations: org = self.organizations[org_name] return org.get_user(name) return None def get_orderer(self, name): """ Get an orderer instance with the name. :param name: Name of the orderer node. :return: The orderer instance or None. """ if name in self.orderers: return self.orderers[name] else: _logger.warning("Cannot find orderer with name {}".format(name)) return None def get_peer(self, name): """ Get a peer instance with the name. :param name: Name of the peer node. :return: The peer instance or None. """ if name in self._peers: return self._peers[name] else: _logger.warning("Cannot find peer with name {}".format(name)) return None def export_net_profile(self, export_file='network_exported.json'): """ Export the current network profile into external file :param export_file: External file to save the result into :return: """ with open(export_file, 'w') as f: json.dump(self.network_info, f, indent=4) def get_net_info(self, *key_path): """ Get the info from self.network_info :param key_path: path of the key, e.g., a.b.c means info['a']['b']['c'] :return: The value, or None """ result = self.network_info if result: for k in key_path: try: result = result[k] except KeyError: _logger.warning( 'No key path {} exists in net info'.format(key_path)) return None return result @property def organizations(self): """ Get the organizations in the network. :return: organizations as dict """ return self._organizations @property def orderers(self): """ Get the orderers in the network. :return: orderers as dict """ return self._orderers @property def peers(self): """ Get the peers instance in the network. :return: peers as dict """ return self._peers @property def CAs(self): """ Get the CAs in the network. :return: CAs as dict """ return self._CAs def new_channel(self, name): """Create a channel handler instance with given name. Args: name (str): The name of the channel. Returns: channel: The inited channel. """ _logger.debug("New channel with name = {}".format(name)) if name not in self._channels: self._channels[name] = Channel(name, self) return self._channels[name] def get_channel(self, name): """Get a channel handler instance. Args: name (str): The name of the channel. Returns: Get the channel instance with the name or None """ return self._channels.get(name, None) def channel_create(self, orderer_name, channel_name, requestor, config_yaml, channel_profile): """ Create a channel, send request to orderer, and check the response :param orderer_name: Name of orderer to send request to :param channel_name: Name of channel to create :param requestor: Name of creator :param config_yaml: Directory path of config yaml to be set for FABRIC_ CFG_PATH variable :param channel_profile: Name of the channel profile defined inside config yaml file :return: True (creation succeeds) or False (creation failed) """ if self.get_channel(channel_name): _logger.warning("channel {} already existed when creating".format( channel_name)) return False orderer = self.get_orderer(orderer_name) if not orderer: _logger.error("No orderer_name instance found with name {}".format( orderer_name)) return False tx = self.generate_channel_tx(channel_name, config_yaml, channel_profile) if tx is None: _logger.error('Configtx is empty') return False _logger.info("Configtx file successfully created in current directory") with open(tx, 'rb') as f: envelope = f.read() config = utils.extract_channel_config(envelope) # convert envelope to config self.tx_context = TXContext(requestor, Ecies(), {}) tx_id = self.tx_context.tx_id nonce = self.tx_context.nonce signatures = [] org1_admin_signature = self.sign_channel_config(config) # append org1_admin_signature to signatures signatures.append(org1_admin_signature) request = { 'tx_id': tx_id, 'nonce': nonce, 'signatures': signatures, 'config': config, 'orderer': orderer, 'channel_name': channel_name } response = self._create_channel(request) if response[0].status == 200: self.new_channel(channel_name) return True else: return False def channel_join(self, requestor, channel_name, peer_names, orderer_name): """ Join a channel. Get genesis block from orderer, then send request to peer :param requestor: User to send the request :param channel_name: Name of channel to create :param peer_names: List of peers to join to the channel :param orderer_name: Name of orderer to get genesis block from :return: True (creation succeeds) or False (creation failed) """ channel = self.get_channel(channel_name) if not channel: _logger.warning( "channel {} not existed when join".format(channel_name)) return False orderer = self.get_orderer(orderer_name) if not orderer: _logger.warning("orderer {} not existed when channel join".format( orderer_name)) return False tx_prop_req = TXProposalRequest() # get the genesis block orderer_admin = self.get_user(orderer_name, 'Admin') tx_context = TXContext(orderer_admin, ecies(), tx_prop_req) genesis_block = orderer.get_genesis_block( tx_context, channel.name).SerializeToString() # create the peer tx_context = TXContext(requestor, ecies(), tx_prop_req) peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) """ # connect the peer eh = EventHub() event = peer_config['grpc_event_endpoint'] tx_id = client.tx_context.tx_id eh.set_peer_addr(event) eh.connect() eh.register_block_event(block_event_callback) all_ehs.append(eh) """ request = { "targets": peers, "block": genesis_block, "tx_context": tx_context, "transient_map": {} } return channel.join_channel(request) def chaincode_install(self, requestor, peer_names, cc_path, cc_name, cc_version): """ Install chaincode to given peers by requestor role :param requestor: User role who issue the request :param peer_names: Names of the peers to install :param cc_path: chaincode path :param cc_name: chaincode name :param cc_version: chaincode version :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(CC_INSTALL, cc_path, CC_TYPE_GOLANG, cc_name, cc_version) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) responses = self.send_install_proposal(tx_context, peers) return responses def _create_channel(self, request): """Calls the orderer to start building the new channel. Args: request (dct): The create channel request. Returns: OrdererResponse or an error. """ have_envelope = False _logger.debug(request) if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def update_channel(self, request): """Calls the orderer to update an existing channel. Args: request (dct): The update channel request. Returns: OrdererResponse or an error. """ have_envelope = False if request and 'envelope' in request: _logger.debug('_create_channel - have envelope') have_envelope = True return self._create_or_update_channel_request(request, have_envelope) def _validate_request(self, request): """ Validate a request :param request: request to validate :return: """ # TODO: implement this to validate the request pass def _create_or_update_channel_request(self, request, have_envelope): """Inits the create of update channel process. Args: request (dct): A create_update channel request. have_envelope (bool): Signals if the requests contains a finished protobuf envelope. Returns: BroadcastResponse which includes status and info """ _logger.debug('_create_or_update_channel - start') error_msg = None if 'config' not in request and not have_envelope: error_msg = 'Missing config request parameter containing ' \ 'the configuration of the channel' if 'signatures' not in request and not have_envelope: error_msg = 'Missing signatures request parameter for the ' \ 'new channel' elif 'signatures' in request and \ not isinstance(request['signatures'], list) \ and not have_envelope: error_msg = 'Signatures request parameter must be an array ' \ 'of signatures' if 'tx_id' not in request and not have_envelope: error_msg = 'Missing tx_id request parameter' if 'nonce' not in request and not have_envelope: error_msg = 'Missing nonce request parameter' if 'orderer' not in request: error_msg = 'Missing orderer request parameter' if 'channel_name' not in request: error_msg = 'Missing channel_name request parameter' if error_msg: _logger.error( '_create_or_update_channel error: {}'.format(error_msg)) raise ValueError(error_msg) if have_envelope: _logger.debug('_create_or_update_channel - have envelope') envelope = common_pb2.Envelope() envelope.ParseFromString(request['envelope']) signature = envelope.signature payload = envelope.payload else: _logger.debug('_create_or_update_channel - have config_update') proto_config_update_envelope = configtx_pb2.ConfigUpdateEnvelope() proto_config_update_envelope.config_update = request['config'] # convert signatures to protobuf signature signatures = request['signatures'] proto_signatures = utils.string_to_signature(signatures) proto_config_update_envelope.signatures.extend(proto_signatures) proto_channel_header = utils.build_channel_header( common_pb2.HeaderType.Value('CONFIG_UPDATE'), request['tx_id'], request['channel_name'], utils.current_timestamp()) proto_header = utils.build_header(self.tx_context.identity, proto_channel_header, request['nonce']) proto_payload = common_pb2.Payload() proto_payload.header.CopyFrom(proto_header) proto_payload.data = proto_config_update_envelope \ .SerializeToString() payload_bytes = proto_payload.SerializeToString() signature_bytes = self.tx_context.sign(payload_bytes) signature = signature_bytes payload = payload_bytes # assemble the final envelope out_envelope = common_pb2.Envelope() out_envelope.signature = signature out_envelope.payload = payload orderer = request['orderer'] return orderer.broadcast(out_envelope) def sign_channel_config(self, config, to_string=True): """This method uses the client instance's current signing identity to sign over the configuration bytes passed in. Args: config: The configuration update in bytes form. to_string: Whether to convert the result to string Returns: config_signature (common_pb2.ConfigSignature): The signature of the current user of the config bytes. """ sign_channel_context = self.tx_context proto_signature_header = common_pb2.SignatureHeader() proto_signature_header.creator = sign_channel_context.identity proto_signature_header.nonce = sign_channel_context.nonce proto_signature_header_bytes = \ proto_signature_header.SerializeToString() signing_bytes = proto_signature_header_bytes + config signature_bytes = sign_channel_context.sign(signing_bytes) proto_config_signature = configtx_pb2.ConfigSignature() proto_config_signature.signature_header = proto_signature_header_bytes proto_config_signature.signature = signature_bytes if to_string: return proto_config_signature.SerializeToString() else: return proto_config_signature @property def crypto_suite(self): """Get the crypto suite. Returns: The crypto_suite instance or None """ return self._crypto_suite @crypto_suite.setter def crypto_suite(self, crypto_suite): """Set the crypto suite to given one. Args: crypto_suite: The crypto_suite to use. Returns: None """ self._crypto_suite = crypto_suite @property def tx_context(self): """ Get the current tx_context for the client. Returns: The tx_context object or None """ return self._tx_context @tx_context.setter def tx_context(self, tx_context): """Set the tx_context to the given one. Args: tx_context: The tx_context to be used. Return: None """ self._tx_context = tx_context @property def state_store(self): """ Get the KeyValue store. Return the keyValue store instance or None """ return self._state_store @state_store.setter def state_store(self, state_store): """ Set the KeyValue store. Args: state_store: the KeyValue store to use. No return Value """ self._state_store = state_store def send_install_proposal(self, tx_context, peers): """ Send install proposal Args: tx_context: transaction context peers: peers scheduler: rx scheduler Returns: A set of proposal_response """ return utils.send_install_proposal(tx_context, peers) def send_instantiate_proposal(self, tx_context, peers, channel_name): """ Send instantiate proposal Args: tx_context: transaction context peers: peers channel_name: the name of channel Returns: A set of proposal_response """ app_channel = self.get_channel(channel_name) _logger.debug("context {}".format(tx_context)) return app_channel.send_instantiate_proposal(tx_context, peers) def generate_channel_tx(self, channel_name, cfg_path, channel_profile): """ Creates channel configuration transaction Args: :param channel_name: Name of the channel :param cfg_path: Directory path of config yaml to be set for FABRIC_CFG_PATH variable :param channel_profile: Name of the channel profile defined inside config yaml file Returns: path to tx file if success else None """ if 'fabric-bin/bin' not in os.environ['PATH']: executable_path = os.path.join( os.path.dirname(__file__).rsplit('/', 2)[0], 'fabric-bin/bin') os.environ['PATH'] += os.pathsep + executable_path # check if configtxgen is in PATH if shutil.which('configtxgen') is None: _logger.error("configtxgen not in PATH.") return None # Generate channel.tx with configtxgen tx_path = "/tmp/channel.tx" cfg_path = cfg_path if os.path.isabs(cfg_path) else \ os.getcwd() + "/" + cfg_path _logger.info("FABRIC_CFG_PATH set to {}".format(cfg_path)) new_env = dict(os.environ, FABRIC_CFG_PATH=cfg_path) output = subprocess.Popen([ 'configtxgen', '-configPath', cfg_path, '-profile', channel_profile, '-channelID', channel_name, '-outputCreateChannelTx', tx_path ], stdout=open(os.devnull, "w"), stderr=subprocess.PIPE, env=new_env) err = output.communicate()[1] if output.returncode: _logger.error('Failed to generate transaction file', err) return None return tx_path def chaincode_instantiate(self, requestor, channel_name, peer_names, args, cc_name, cc_version, timeout=10): """ Instantiate installed chaincode to particular peer in particular channel :param requestor: User role who issue the request :param channel_name: the name of the channel to send tx proposal :param peer_names: Names of the peers to install :param args (list): arguments (keys and values) for initialization :param cc_name: chaincode name :param cc_version: chaincode version :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req_dep = create_tx_prop_req(prop_type=CC_INSTANTIATE, cc_type=CC_TYPE_GOLANG, cc_name=cc_name, cc_version=cc_version, fcn='init', args=args) tx_context_dep = create_tx_context(requestor, ecies(), tran_prop_req_dep) res = self.send_instantiate_proposal(tx_context_dep, peers, channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tran_req = utils.build_tx_req(res) responses = utils.send_transaction(self.orderers, tran_req, tx_context) if not (tran_req.responses[0].response.status == 200 and responses[0].status == 200): return False # Wait until chaincode is really instantiated # Note : we will remove this part when we have channel event hub starttime = int(time.time()) while int(time.time()) - starttime < timeout: try: response = self.query_transaction(requestor=requestor, channel_name=channel_name, peer_names=peer_names, tx_id=tx_context_dep.tx_id, decode=False) if response.response.status == 200: return True time.sleep(1) except Exception: time.sleep(1) return False def chaincode_invoke(self, requestor, channel_name, peer_names, args, cc_name, cc_version, cc_type=CC_TYPE_GOLANG, fcn='invoke', timeout=10): """ Invoke chaincode for ledger update :param requestor: User role who issue the request :param channel_name: the name of the channel to send tx proposal :param peer_names: Names of the peers to install :param args (list): arguments (keys and values) for initialization :param cc_name: chaincode name :param cc_version: chaincode version :param cc_type: chaincode type language :param fcn: chaincode function :param timeout: Timeout to wait :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(prop_type=CC_INVOKE, cc_name=cc_name, cc_version=cc_version, cc_type=cc_type, fcn=fcn, args=args) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) res = self.get_channel(channel_name).send_tx_proposal( tx_context, peers) tran_req = utils.build_tx_req(res) tx_context_tx = create_tx_context(requestor, ecies(), tran_req) responses = utils.send_transaction(self.orderers, tran_req, tx_context_tx) res = tran_req.responses[0].response if not (res.status == 200 and responses[0].status == 200): return res.message # Wait until chaincode invoke is really effective # Note : we will remove this part when we have channel event hub starttime = int(time.time()) payload = None while int(time.time()) - starttime < timeout: try: response = self.query_transaction(requestor=requestor, channel_name=channel_name, peer_names=peer_names, tx_id=tx_context.tx_id, decode=False) if response.response.status == 200: payload = tran_req.responses[0].response.payload return payload.decode('utf-8') time.sleep(1) except Exception: time.sleep(1) msg = 'Failed to invoke chaincode. Query check returned: %s' return msg % payload.message def chaincode_query(self, requestor, channel_name, peer_names, args, cc_name, cc_version, cc_type=CC_TYPE_GOLANG, fcn='query'): """ Query chaincode :param requestor: User role who issue the request :param channel_name: the name of the channel to send tx proposal :param peer_names: Names of the peers to install :param args (list): arguments (keys and values) for initialization :param cc_name: chaincode name :param cc_version: chaincode version :param cc_type: chaincode type language :param fcn: chaincode function :return: True or False """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) tran_prop_req = create_tx_prop_req(prop_type=CC_QUERY, cc_name=cc_name, cc_version=cc_version, cc_type=cc_type, fcn=fcn, args=args) tx_context = create_tx_context(requestor, ecies(), tran_prop_req) res = self.get_channel(channel_name).send_tx_proposal( tx_context, peers) tran_req = utils.build_tx_req(res) res = tran_req.responses[0].response if res.status == 200: return res.payload.decode('utf-8') return res.message def query_installed_chaincodes(self, requestor, peer_names, decode=True): """ Queries installed chaincode, returns all chaincodes installed on a peer :param requestor: User role who issue the request :param peer_names: Names of the peers to query :param deocode: Decode the response payload :return: A `ChaincodeQueryResponse` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) request = create_tx_prop_req(prop_type=CC_QUERY, fcn='getinstalledchaincodes', cc_name='lscc', cc_type=CC_TYPE_GOLANG, args=[]) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tx_context.tx_prop_req = request responses = Channel._send_tx_proposal('', tx_context, peers) try: if responses[0][0].response and decode: query_trans = query_pb2.ChaincodeQueryResponse() query_trans.ParseFromString(responses[0][0].response.payload) for cc in query_trans.chaincodes: _logger.debug('cc name {}, version {}, path {}'.format( cc.name, cc.version, cc.path)) return query_trans return responses[0][0] except Exception: _logger.error("Failed to query installed chaincodes: {}", sys.exc_info()[0]) raise def query_channels(self, requestor, peer_names, decode=True): """ Queries channel name joined by a peer :param requestor: User role who issue the request :param peer_names: Names of the peers to install :param deocode: Decode the response payload :return: A `ChannelQueryResponse` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) request = create_tx_prop_req(prop_type=CC_QUERY, fcn='GetChannels', cc_name='cscc', cc_type=CC_TYPE_GOLANG, args=[]) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) tx_context.tx_prop_req = request responses = Channel._send_tx_proposal('', tx_context, peers) try: if responses[0][0].response and decode: query_trans = query_pb2.ChannelQueryResponse() query_trans.ParseFromString(responses[0][0].response.payload) for ch in query_trans.channels: _logger.debug('channel id {}'.format(ch.channel_id)) return query_trans return responses[0][0] except Exception: _logger.error("Failed to query channel: {}", sys.exc_info()[0]) raise def query_info(self, requestor, channel_name, peer_names, decode=True): """ Queries information of a channel :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :param deocode: Decode the response payload :return: A `BlockchainInfo` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_info(tx_context, peers) try: if responses[0][0].response and decode: chain_info = ledger_pb2.BlockchainInfo() chain_info.ParseFromString(responses[0][0].response.payload) _logger.debug('response status {}'.format( responses[0][0].response.status)) return chain_info return responses[0][0] except Exception: _logger.error("Failed to query info: {}", sys.exc_info()[0]) raise def query_block_by_txid(self, requestor, channel_name, peer_names, tx_id, decode=True): """ Queries block by tx id :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :param tx_id: Transaction ID :param deocode: Decode the response payload :return: A `BlockDecoder` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_block_by_txid(tx_context, peers, tx_id) try: if responses[0][0].response and decode: _logger.debug('response status {}'.format( responses[0][0].response.status)) block = BlockDecoder().decode(responses[0][0].response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return responses[0][0] except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_block_by_hash(self, requestor, channel_name, peer_names, block_hash, decode=True): """ Queries block by hash :param requestor: User role who issue the request :param channel_name: Name of channel to query :param peer_names: Names of the peers to install :param block_hash: Hash of a block :param deocode: Decode the response payload :return: A `BlockDecoder` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_block_by_hash(tx_context, peers, block_hash) try: if responses[0][0].response and decode: _logger.debug('response status {}'.format( responses[0][0].response.status)) block = BlockDecoder().decode(responses[0][0].response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return responses[0][0] except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_block(self, requestor, channel_name, peer_names, block_number, decode=True): """ Queries block by number :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to install :param block_number: Number of a block :param deocode: Decode the response payload :return: A `BlockDecoder` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_block(tx_context, peers, block_number) try: if responses[0][0].response and decode: _logger.debug('response status {}'.format( responses[0][0].response.status)) block = BlockDecoder().decode(responses[0][0].response.payload) _logger.debug('looking at block {}'.format( block['header']['number'])) return block return responses[0][0] except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_transaction(self, requestor, channel_name, peer_names, tx_id, decode=True): """ Queries block by number :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to install :param tx_id: The id of the transaction :param deocode: Decode the response payload :return: A `BlockDecoder` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_transaction(tx_context, peers, tx_id) try: if responses[0][0].response and decode: _logger.debug('response status {}'.format( responses[0][0].response.status)) process_trans = BlockDecoder().decode_transaction( responses[0][0].response.payload) return process_trans return responses[0][0] except Exception: _logger.error("Failed to query block: {}", sys.exc_info()[0]) raise def query_instantiated_chaincodes(self, requestor, channel_name, peer_names, decode=True): """ Queries instantiated chaincode :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to query :param deocode: Decode the response payload :return: A `ChaincodeQueryResponse` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.query_instantiated_chaincodes(tx_context, peers) try: if responses[0][0].response and decode: query_trans = query_pb2.ChaincodeQueryResponse() query_trans.ParseFromString(responses[0][0].response.payload) for cc in query_trans.chaincodes: _logger.debug('cc name {}, version {}, path {}'.format( cc.name, cc.version, cc.path)) return query_trans return responses[0][0] except Exception: _logger.error("Failed to query instantiated chaincodes: {}", sys.exc_info()[0]) raise def get_channel_config(self, requestor, channel_name, peer_names, decode=True): """ Get configuration block for the channel :param requestor: User role who issue the request :param channel_name: name of channel to query :param peer_names: Names of the peers to query :param deocode: Decode the response payload :return: A `ChaincodeQueryResponse` or `ProposalResponse` """ peers = [] for peer_name in peer_names: peer = self.get_peer(peer_name) peers.append(peer) channel = self.get_channel(channel_name) tx_context = create_tx_context(requestor, ecies(), TXProposalRequest()) responses = channel.get_channel_config(tx_context, peers) try: if responses[0][0].response and decode: _logger.debug('response status {}'.format( responses[0][0].response.status)) block = common_pb2.Block() block.ParseFromString(responses[0][0].response.payload) envelope = common_pb2.Envelope() envelope.ParseFromString(block.data.data[0]) payload = common_pb2.Payload() payload.ParseFromString(envelope.payload) config_envelope = configtx_pb2.ConfigEnvelope() config_envelope.ParseFromString(payload.data) return config_envelope return responses[0][0] except Exception: _logger.error("Failed to get channel config block: {}", sys.exc_info()[0]) raise def extract_channel_config(config_envelope): """Extracts the protobuf 'ConfigUpdate' out of the 'ConfigEnvelope' that is produced by the configtxgen tool The returned object may then be signed using sign_channel_config() method. Once all the signatures have been collected, the 'ConfigUpdate' object and the signatures may be used on create_channel() or update_channel() calls Args: config_envelope (bytes): encoded bytes of the ConfigEnvelope protobuf Returns: config_update (bytes): encoded bytes of ConfigUpdate protobuf, ready to be signed """ _logger.debug('extract_channel_config start') envelope = common_pb2.Envelope() envelope.ParseFromString(config_envelope) payload = common_pb2.Payload() payload.ParseFromString(envelope.payload) configtx = configtx_pb2.ConfigUpdateEnvelope() configtx.ParseFromString(payload.data) config_update = configtx.ConfigUpdate return config_update.SerializeToString() def query_peers(self, requestor, target_peer, crypto=ecies(), decode=True): """Queries peers with discovery api :param requestor: User role who issue the request :param target_peer: Name of the peers to send request :param crypto: crypto method to sign the request :param deocode: Decode the response payload :return result: a nested dict of query result """ dummy_channel = self.new_channel('discover-local') response = dummy_channel._discovery(requestor, target_peer, crypto, local=True) try: results = {} if response and decode: for index in range(len(response.results)): result = response.results[index] if not result: raise Exception("Discovery results are missing") if hasattr(result, 'error'): _logger.error( "Channel {} received discovery error: {}".format( dummy_channel.name, result.error.content)) if hasattr(result, 'members'): results['local_peers'] = \ self._process_discovery_membership_result( result.members) return results except Exception: _logger.error("Failed to query instantiated chaincodes: {}", sys.exc_info()[0]) raise def _process_discovery_membership_result(self, q_members): peers_by_org = {} if hasattr(q_members, 'peers_by_org'): for mspid in q_members.peers_by_org: peers_by_org[mspid] = {} peers_by_org[mspid]['peers'] = self._process_peers( q_members.peers_by_org[mspid].peers) return peers_by_org def _process_peers(self, q_peers): peers = [] for q_peer in q_peers: peer = {} # IDENTITY q_identity = identities_pb2.SerializedIdentity() q_identity.ParseFromString(q_peer.identity) peer['mspid'] = q_identity.mspid # MEMBERSHIP q_membership_msg = message_pb2.GossipMessage() q_membership_msg.ParseFromString(q_peer.membership_info.payload) peer['endpoint'] = q_membership_msg.alive_msg.membership.endpoint # STATE if hasattr(q_peer, 'state_info'): message_s = message_pb2.GossipMessage() message_s.ParseFromString(q_peer.state_info.payload) try: peer['ledger_height'] = int( message_s.state_info.properties.ledger_height) except AttributeError as e: peer['ledger_height'] = 0 _logger.debug('missing ledger_height: {}'.format(e)) peer['chaincodes'] = [] for index in message_s.state_info.properties.chaincodes: q_cc = message_s.info.properties.chaincodes[index] cc = {} cc['name'] = q_cc.name cc['version'] = q_cc.version peer['chaincodes'].append(cc) peers.append(peer) return sorted(peers, key=lambda k: k['endpoint'])