Create credential as Issuer out of credential request and dict of key:value (raw, unencoded)
        entries for attributes.

        Return credential json, and if cred def supports revocation, credential revocation identifier
        and revocation registry delta ledger timestamp (epoch seconds).

        If the credential definition supports revocation, and the current revocation registry is full,
        the processing creates a new revocation registry en passant. Depending on the revocation
        registry size (by default starting at 256 and doubling iteratively through 4096), this
        operation may delay credential creation by several seconds.

        :param cred_offer_json: credential offer json as created by Issuer
        :param cred_req_json: credential request json as created by HolderProver
        :param cred_attrs: dict mapping each attribute to its raw value (the operation encodes it); e.g.,

        ::

            {
                'favourite_drink': 'martini',
                'height': 180,
                'last_visit_date': '2017-12-31',
                'weaknesses': None
            }

        :param rr_size: size of new revocation registry (default as per _create_rev_reg()) if necessary
        :return: newly issued credential json; credential revocation identifier (if cred def supports
            revocation, None otherwise), and ledger timestamp (if cred def supports revocation, None otherwise)

async def create_cred(
            self,
            cred_offer_json,
            cred_req_json: str,
            cred_attrs: dict,
            rr_size: int = None) -> (str, str, int):
        """
        Create credential as Issuer out of credential request and dict of key:value (raw, unencoded)
        entries for attributes.

        Return credential json, and if cred def supports revocation, credential revocation identifier
        and revocation registry delta ledger timestamp (epoch seconds).

        If the credential definition supports revocation, and the current revocation registry is full,
        the processing creates a new revocation registry en passant. Depending on the revocation
        registry size (by default starting at 256 and doubling iteratively through 4096), this
        operation may delay credential creation by several seconds.

        :param cred_offer_json: credential offer json as created by Issuer
        :param cred_req_json: credential request json as created by HolderProver
        :param cred_attrs: dict mapping each attribute to its raw value (the operation encodes it); e.g.,

        ::

            {
                'favourite_drink': 'martini',
                'height': 180,
                'last_visit_date': '2017-12-31',
                'weaknesses': None
            }

        :param rr_size: size of new revocation registry (default as per _create_rev_reg()) if necessary
        :return: newly issued credential json; credential revocation identifier (if cred def supports
            revocation, None otherwise), and ledger timestamp (if cred def supports revocation, None otherwise)
        """

        LOGGER.debug(
            'Issuer.create_cred >>> cred_offer_json: %s, cred_req_json: %s, cred_attrs: %s, rr_size: %s',
            cred_offer_json,
            cred_req_json,
            cred_attrs,
            rr_size)

        cd_id = json.loads(cred_offer_json)['cred_def_id']
        cred_def = json.loads(await self.get_cred_def(cd_id))  # ensure cred def is in cache

        if 'revocation' in cred_def['value']:
            with REVO_CACHE.lock:
                rr_id = Tails.current_rev_reg_id(self._dir_tails, cd_id)
                tails = REVO_CACHE[rr_id].tails
                assert tails  # at (re)start, at cred def, Issuer sync_revoc() sets this index in revocation cache

                try:
                    (cred_json, cred_revoc_id, rr_delta_json) = await anoncreds.issuer_create_credential(
                        self.wallet.handle,
                        cred_offer_json,
                        cred_req_json,
                        json.dumps({k: cred_attr_value(cred_attrs[k]) for k in cred_attrs}),
                        tails.rr_id,
                        tails.reader_handle)
                    # do not create rr delta frame and append to cached delta frames list: timestamp could lag or skew
                    rre_req_json = await ledger.build_revoc_reg_entry_request(
                        self.did,
                        tails.rr_id,
                        'CL_ACCUM',
                        rr_delta_json)
                    await self._sign_submit(rre_req_json)
                    resp_json = await self._sign_submit(rre_req_json)
                    resp = json.loads(resp_json)
                    rv = (cred_json, cred_revoc_id, resp['result']['txnMetadata']['txnTime'])

                except IndyError as x_indy:
                    if x_indy.error_code == ErrorCode.AnoncredsRevocationRegistryFullError:
                        (tag, rr_size_suggested) = Tails.next_tag(self._dir_tails, cd_id)
                        rr_id = rev_reg_id(cd_id, tag)
                        await self._create_rev_reg(rr_id, rr_size or rr_size_suggested)
                        REVO_CACHE[rr_id].tails = await Tails(self._dir_tails, cd_id).open()
                        return await self.create_cred(cred_offer_json, cred_req_json, cred_attrs)  # should be ok now
                    else:
                        LOGGER.debug(
                            'Issuer.create_cred: <!<  cannot create cred, indy error code %s',
                            x_indy.error_code)
                        raise
        else:
            try:
                (cred_json, _, _) = await anoncreds.issuer_create_credential(
                    self.wallet.handle,
                    cred_offer_json,
                    cred_req_json,
                    json.dumps({k: cred_attr_value(cred_attrs[k]) for k in cred_attrs}),
                    None,
                    None)
                rv = (cred_json, _, _)
            except IndyError as x_indy:
                LOGGER.debug('Issuer.create_cred: <!<  cannot create cred, indy error code %s', x_indy.error_code)
                raise

        LOGGER.debug('Issuer.create_cred <<< %s', rv)
        return rv