Exemple #1
0
    async def _sign_submit(self, req_json: str) -> str:
        """
        Sign and submit (json) request to ledger; return (json) result.

        Raise ClosedPool if pool is not yet open, CorruptWallet if existing wallet's
        pool is no longer extant, or BadLedgerTxn on any other failure.

        :param req_json: json of request to sign and submit
        :param wait: whether to wait for the transaction to appear on the ledger before proceeding
        :return: json response
        """

        LOGGER.debug('_BaseAnchor._sign_submit >>> json: %s', req_json)

        if not self.pool.handle:
            LOGGER.debug('_BaseAnchor._submit <!< closed pool %s',
                         self.pool.name)
            raise ClosedPool('Cannot submit request to closed pool {}'.format(
                self.pool.name))

        try:
            rv_json = await ledger.sign_and_submit_request(
                self.pool.handle, self.wallet.handle, self.did, req_json)
            await asyncio.sleep(0)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError:
                LOGGER.debug(
                    '_BaseAnchor._sign_submit: <!< Corrupt wallet %s is not compatible with pool %s',
                    self.wallet.name, self.pool.name)
                raise CorruptWallet(
                    'Corrupt wallet {} is not compatible with pool {}'.format(
                        self.wallet.name, self.pool.name))
            else:
                LOGGER.debug(
                    '_BaseAnchor._sign_submit: <!<  cannot sign/submit request for ledger: indy error code %s',
                    self.wallet.name)
                raise BadLedgerTxn(
                    'Cannot sign/submit request for ledger: indy error code {}'
                    .format(x_indy.error_code))

        resp = json.loads(rv_json)
        if ('op' in resp) and (resp['op'] in ('REQNACK', 'REJECT')):
            LOGGER.debug(
                '_BaseAnchor._sign_submit: ledger rejected request: %s',
                resp['reason'])
            raise BadLedgerTxn(
                'Ledger rejected transaction request: {}'.format(
                    resp['reason']))

        seq_no = resp.get('result', {}).get('seqNo', None)
        if 'reason' in resp and seq_no is None:
            LOGGER.debug(
                '_BaseAnchor._sign_submit: <!< response indicates no transaction: %s',
                resp['reason'])
            raise BadLedgerTxn('Response indicates no transaction: {}'.format(
                resp['reason']))

        LOGGER.debug('_BaseAnchor._sign_submit <<< %s', rv_json)
        return rv_json
    async def _sign_submit(self, req_json: str) -> str:
        """
        Sign and submit (json) request to ledger; return (json) result.

        Raise:
        * AbsentPool for no pool
        * ClosedPool if pool is not yet open
        * CorruptWallet if existing wallet appears not to pertain to the anchor's pool
        * WalletState if wallet is closed
        * BadLedgerTxn on ledger rejection of transaction.

        :param req_json: json of request to sign and submit
        :return: json response
        """

        LOGGER.debug('BaseAnchor._sign_submit >>> req_json: %s', req_json)

        if not self.pool:
            LOGGER.debug('BaseAnchor._sign_submit <!< absent pool')
            raise AbsentPool('Cannot sign and submit request: absent pool')

        if not self.pool.handle:
            LOGGER.debug('BaseAnchor._submit <!< closed pool %s', self.pool.name)
            raise ClosedPool('Cannot sign and submit request to closed pool {}'.format(self.pool.name))

        if not self.wallet.handle:
            LOGGER.debug('BaseAnchor._sign_submit <!< Wallet %s is closed', self.name)
            raise WalletState('Wallet {} is closed'.format(self.name))

        try:
            rv_json = await ledger.sign_and_submit_request(self.pool.handle, self.wallet.handle, self.did, req_json)
            await asyncio.sleep(0)
        except IndyError as x_indy:
            if x_indy.error_code == ErrorCode.WalletIncompatiblePoolError:
                LOGGER.debug(
                    'BaseAnchor._sign_submit <!< Corrupt wallet %s is not compatible with pool %s',
                    self.name,
                    self.pool.name)
                raise CorruptWallet('Corrupt wallet {} is not compatible with pool {}'.format(
                    self.name,
                    self.pool.name))
            LOGGER.debug(
                'BaseAnchor._sign_submit <!< cannot sign/submit request for ledger: indy error code %s',
                x_indy.error_code)
            raise BadLedgerTxn('Cannot sign/submit request for ledger: indy error code {}'.format(
                x_indy.error_code))

        resp = json.loads(rv_json)
        if resp.get('op', '') in ('REQNACK', 'REJECT'):
            LOGGER.debug('BaseAnchor._sign_submit: ledger rejected request: %s', resp['reason'])
            raise BadLedgerTxn('Ledger rejected transaction request: {}'.format(resp['reason']))

        LOGGER.debug('BaseAnchor._sign_submit <<< %s', rv_json)
        return rv_json
Exemple #3
0
    async def _submit(self, req_json: str) -> str:
        """
        Submit (json) request to ledger; return (json) result.

        Raise AbsentPool for no pool, ClosedPool if pool is not yet open, or BadLedgerTxn on failure.

        :param req_json: json of request to sign and submit
        :return: json response
        """

        LOGGER.debug('BaseAnchor._submit >>> req_json: %s', req_json)

        if not self.pool:
            LOGGER.debug('BaseAnchor._submit <!< absent pool')
            raise AbsentPool('Cannot submit request: absent pool')

        if not self.pool.handle:
            LOGGER.debug('BaseAnchor._submit <!< closed pool %s',
                         self.pool.name)
            raise ClosedPool('Cannot submit request to closed pool {}'.format(
                self.pool.name))

        rv_json = await ledger.submit_request(self.pool.handle, req_json)
        await asyncio.sleep(0)

        resp = json.loads(rv_json)
        if resp.get('op', '') in ('REQNACK', 'REJECT'):
            LOGGER.debug('BaseAnchor._submit <!< ledger rejected request: %s',
                         resp['reason'])
            raise BadLedgerTxn(
                'Ledger rejected transaction request: {}'.format(
                    resp['reason']))

        LOGGER.debug('BaseAnchor._submit <<< %s', rv_json)
        return rv_json
Exemple #4
0
    async def send_schema(self, schema_data_json: str) -> str:
        """
        Send schema to ledger, then retrieve it as written to the ledger and return it.
        Raise BadLedgerTxn on failure.

        If schema already exists on ledger, log error and return schema.

        :param schema_data_json: schema data json with name, version, attribute names; e.g.,

        ::

            {
                'name': 'my-schema',
                'version': '1.234',
                'attr_names': ['favourite_drink', 'height', 'last_visit_date']
            }

        :return: schema json as written to ledger (or existed a priori)
        """

        LOGGER.debug('Origin.send_schema >>> schema_data_json: %s',
                     schema_data_json)

        schema_data = json.loads(schema_data_json)
        s_id = schema_id(self.did, schema_data['name'], schema_data['version'])
        s_key = schema_key(s_id)
        rv_json = None
        with SCHEMA_CACHE.lock:
            try:
                rv_json = await self.get_schema(s_key)
                LOGGER.error(
                    'Schema %s version %s already exists on ledger for origin-did %s: not sending',
                    schema_data['name'], schema_data['version'], self.did)
            except AbsentSchema:  # OK - about to create and send it
                (_, schema_json) = await anoncreds.issuer_create_schema(
                    self.did, schema_data['name'], schema_data['version'],
                    json.dumps(schema_data['attr_names']))
                req_json = await ledger.build_schema_request(
                    self.did, schema_json)
                await self._sign_submit(req_json)

                for _ in range(16):  # reasonable timeout
                    try:
                        rv_json = await self.get_schema(s_key)  # adds to cache
                        break
                    except AbsentSchema:
                        await sleep(1)
                        LOGGER.info(
                            'Sent schema %s to ledger, waiting 1s for its appearance',
                            s_id)

                if not rv_json:
                    LOGGER.debug(
                        'Origin.send_schema <!< timed out waiting on sent schema %s',
                        s_id)
                    raise BadLedgerTxn(
                        'Timed out waiting on sent schema {}'.format(s_id))

        LOGGER.debug('Origin.send_schema <<< %s', rv_json)
        return rv_json
Exemple #5
0
    async def _submit(self, req_json: str) -> str:
        """
        Submit (json) request to ledger; return (json) result.

        Raise ClosedPool if pool is not yet open, or BadLedgerTxn on failure.

        :param req_json: json of request to sign and submit
        :param wait: whether to wait for the transaction to appear on the ledger before proceeding
        :return: json response
        """

        LOGGER.debug('_BaseAnchor._submit >>> json: %s', req_json)

        if not self.pool.handle:
            LOGGER.debug('_BaseAnchor._submit <!< closed pool %s',
                         self.pool.name)
            raise ClosedPool('Cannot submit request to closed pool {}'.format(
                self.pool.name))

        rv_json = await ledger.submit_request(self.pool.handle, req_json)
        await asyncio.sleep(0)

        resp = json.loads(rv_json)
        if ('op' in resp) and (resp['op'] in ('REQNACK', 'REJECT')):
            LOGGER.debug(
                '_BaseAnchor._submit: <!< ledger rejected request: %s',
                resp['reason'])
            raise BadLedgerTxn(
                'Ledger rejected transaction request: {}'.format(
                    resp['reason']))

        seq_no = resp.get('result', {}).get('seqNo', None)
        if 'reason' in resp and seq_no is None:
            LOGGER.debug(
                '_BaseAnchor._submit: <!< response indicates no transaction: %s',
                resp['reason'])
            raise BadLedgerTxn('Response indicates no transaction: {}'.format(
                resp['reason']))

        LOGGER.debug('_BaseAnchor._submit <<< %s', rv_json)
        return rv_json
Exemple #6
0
    async def send_endpoint(self, endpoint: str) -> None:
        """
        Send anchor's own endpoint attribute to ledger (and endpoint cache),
        if ledger does not yet have input value. Specify None to clear.

        Raise BadIdentifier on endpoint not formatted as '<ip-address>:<port>',
        BadLedgerTxn on failure, WalletState if wallet is closed.

        :param endpoint: value to set as endpoint attribute on ledger and cache:
            specify URL or None to clear.
        """

        LOGGER.debug('BaseAnchor.send_endpoint >>> endpoint: %s', endpoint)

        ledger_endpoint = await self.get_endpoint()
        if ledger_endpoint == endpoint:
            LOGGER.info('%s endpoint already set as %s', self.wallet.name,
                        endpoint)
            LOGGER.debug(
                'BaseAnchor.send_endpoint <<< (%s already set for %s )')
            return

        attr_json = json.dumps({
            'endpoint': {
                'endpoint':
                endpoint  # indy-sdk likes 'ha' here but won't map 'ha' to a URL, only ip:port
            }
        })
        req_json = await ledger.build_attrib_request(self.did, self.did, None,
                                                     attr_json, None)
        await self._sign_submit(req_json)

        for _ in range(16):  # reasonable timeout
            if await self.get_endpoint(None, False) == endpoint:
                break
            await asyncio.sleep(1)
            LOGGER.info(
                'Sent endpoint %s to ledger, waiting 1s for its confirmation',
                endpoint)
        else:
            LOGGER.debug(
                'BaseAnchor.send_endpoint <!< timed out waiting on send endpoint %s',
                endpoint)
            raise BadLedgerTxn(
                'Timed out waiting on sent endpoint {}'.format(endpoint))

        LOGGER.debug('BaseAnchor.send_endpoint <<<')
Exemple #7
0
    async def _____ensure_txn_applied(self, seq_no: int) -> None:
        """
        Wait, a second at a time, until transaction on given sequence number appears on the ledger.
        Time out after 16 seconds and raise BadLedgerTxn.

        :param seq_no: transaction sequence number
        """

        for _ in range(16):
            txn = json.loads(await self.get_txn(seq_no))

            if txn:
                LOGGER.debug('_BaseAnchor._ensure_txn_applied <<<')
                return
            await asyncio.sleep(1)

        raise BadLedgerTxn('Timed out waiting on txn #{}'.format(seq_no))
Exemple #8
0
    async def send_cred_def(self,
                            s_id: str,
                            revocation: bool = True,
                            rr_size: int = None) -> str:
        """
        Create a credential definition as Issuer, store it in its wallet, and send it to the ledger.

        Raise CorruptWallet for wallet not pertaining to current ledger, BadLedgerTxn on failure
        to send credential definition to ledger if need be, or IndyError for any other failure
        to create and store credential definition in wallet.

        :param s_id: schema identifier
        :param revocation: whether to support revocation for cred def
        :param rr_size: size of initial revocation registry (default as per _create_rev_reg()), if revocation supported
        :return: json credential definition as it appears on ledger
        """

        LOGGER.debug(
            'Issuer.send_cred_def >>> s_id: %s, revocation: %s, rr_size: %s',
            s_id, revocation, rr_size)

        if not ok_schema_id(s_id):
            LOGGER.debug('Issuer.send_cred_def <!< Bad schema id %s', s_id)
            raise BadIdentifier('Bad schema id {}'.format(s_id))

        rv_json = json.dumps({})
        schema_json = await self.get_schema(schema_key(s_id))
        schema = json.loads(schema_json)

        cd_id = cred_def_id(self.did, schema['seqNo'], self.pool.protocol)
        private_key_ok = True
        with CRED_DEF_CACHE.lock:
            try:
                rv_json = await self.get_cred_def(cd_id)
                LOGGER.info(
                    'Cred def on schema %s version %s already exists on ledger; Issuer %s not sending another',
                    schema['name'], schema['version'], self.wallet.name)
            except AbsentCredDef:
                pass  # OK - about to create, store, and send it

            try:
                (_, cred_def_json
                 ) = await anoncreds.issuer_create_and_store_credential_def(
                     self.wallet.handle,
                     self.did,  # issuer DID
                     schema_json,
                     self.pool.protocol.cd_id_tag(
                         False
                     ),  # expect only one cred def per schema and issuer
                     'CL',
                     json.dumps({'support_revocation': revocation}))
                if json.loads(rv_json):
                    private_key_ok = False
                    LOGGER.warning(
                        'New cred def on %s in wallet shadows existing one on ledger: private key not usable',
                        cd_id)
                    # carry on though, this anchor may have other roles so public key may be good enough
            except IndyError as x_indy:
                if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError:
                    if json.loads(rv_json):
                        LOGGER.info(
                            'Issuer wallet %s reusing existing cred def on schema %s version %s',
                            self.wallet.name, schema['name'],
                            schema['version'])
                    else:
                        LOGGER.debug(
                            'Issuer.send_cred_def: <!< corrupt wallet %s',
                            self.wallet.name)
                        raise CorruptWallet(
                            'Corrupt Issuer wallet {} has cred def on schema {} version {} not on ledger'
                            .format(self.wallet.name, schema['name'],
                                    schema['version']))
                else:
                    LOGGER.debug(
                        'Issuer.send_cred_def: <!< cannot store cred def in wallet %s: indy error code %s',
                        self.wallet.name, x_indy.error_code)
                    raise

            if not json.loads(
                    rv_json
            ):  # checking the ledger returned no cred def: send it
                req_json = await ledger.build_cred_def_request(
                    self.did, cred_def_json)
                await self._sign_submit(req_json)

                for _ in range(16):  # reasonable timeout
                    try:
                        rv_json = await self.get_cred_def(cd_id
                                                          )  # adds to cache
                        break
                    except AbsentCredDef:
                        await sleep(1)
                        LOGGER.info(
                            'Sent cred def %s to ledger, waiting 1s for its appearance',
                            cd_id)

                if not rv_json:
                    LOGGER.debug(
                        'Issuer.send_cred_def <!< timed out waiting on sent cred_def %s',
                        cd_id)
                    raise BadLedgerTxn(
                        'Timed out waiting on sent cred_def {}'.format(cd_id))

                if revocation:
                    await self._sync_revoc(
                        rev_reg_id(cd_id, 0),
                        rr_size)  # create new rev reg, tails file for tag 0

        if revocation and private_key_ok:
            for tag in [
                    str(t) for t in range(
                        int(Tails.next_tag(self._dir_tails, cd_id)[0]))
            ]:  # '0' to str(next-1)
                await self._sync_revoc(rev_reg_id(cd_id, tag),
                                       rr_size if tag == 0 else None)

        makedirs(join(self._dir_tails, cd_id), exist_ok=True
                 )  # make sure dir exists for box id collection, revo or not

        LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json)
        return rv_json
Exemple #9
0
    async def send_cred_def(self,
                            s_id: str,
                            revo: bool = True,
                            rr_size: int = None) -> str:
        """
        Create a credential definition as Issuer, store it in its wallet, and send it to the ledger.

        Raise CorruptWallet for wallet not pertaining to current ledger, BadLedgerTxn on failure
        to send credential definition to ledger if need be, WalletState for closed wallet,
        or IndyError for any other failure to create and store credential definition in wallet.

        :param s_id: schema identifier
        :param revo: whether to support revocation for cred def
        :param rr_size: size of initial revocation registry (default as per RevRegBuilder.create_rev_reg()),
            if revocation supported
        :return: json credential definition as it appears on ledger
        """

        LOGGER.debug(
            'Issuer.send_cred_def >>> s_id: %s, revo: %s, rr_size: %s', s_id,
            revo, rr_size)

        if not ok_schema_id(s_id):
            LOGGER.debug('Issuer.send_cred_def <!< Bad schema id %s', s_id)
            raise BadIdentifier('Bad schema id {}'.format(s_id))

        if not self.wallet.handle:
            LOGGER.debug('Issuer.send_cred_def <!< Wallet %s is closed',
                         self.name)
            raise WalletState('Wallet {} is closed'.format(self.name))

        if not self.pool:
            LOGGER.debug('Issuer.send_cred_def <!< issuer %s has no pool',
                         self.name)
            raise AbsentPool(
                'Issuer {} has no pool: cannot send cred def'.format(
                    self.name))

        rv_json = json.dumps({})
        schema_json = await self.get_schema(schema_key(s_id))
        schema = json.loads(schema_json)

        cd_id = cred_def_id(self.did, schema['seqNo'], self.pool.protocol)
        private_key_ok = True
        with CRED_DEF_CACHE.lock:
            try:
                rv_json = await self.get_cred_def(cd_id)
                LOGGER.info(
                    'Cred def on schema %s version %s already exists on ledger; Issuer %s not sending another',
                    schema['name'], schema['version'], self.name)
            except AbsentCredDef:
                pass  # OK - about to create, store, and send it

            (cred_def_json, private_key_ok) = await self._create_cred_def(
                schema, json.loads(rv_json), revo)

            if not json.loads(
                    rv_json
            ):  # checking the ledger returned no cred def: send it
                req_json = await ledger.build_cred_def_request(
                    self.did, cred_def_json)
                await self._sign_submit(req_json)

                for _ in range(16):  # reasonable timeout
                    try:
                        rv_json = await self.get_cred_def(cd_id
                                                          )  # adds to cache
                        break
                    except AbsentCredDef:
                        await asyncio.sleep(1)
                        LOGGER.info(
                            'Sent cred def %s to ledger, waiting 1s for its appearance',
                            cd_id)

                if not rv_json:
                    LOGGER.debug(
                        'Issuer.send_cred_def <!< timed out waiting on sent cred_def %s',
                        cd_id)
                    raise BadLedgerTxn(
                        'Timed out waiting on sent cred_def {}'.format(cd_id))

                if revo:  # create new rev reg for tag '0'
                    if self.rrbx:
                        (_, rr_size_suggested) = Tails.next_tag(
                            self.dir_tails, cd_id)
                        self.rrb.mark_in_progress(rev_reg_id(cd_id, '0'),
                                                  rr_size or rr_size_suggested)

                    await self._sync_revoc_for_issue(rev_reg_id(
                        cd_id, '0'), rr_size)  # sync rev reg on tag '0'

        if revo and private_key_ok:
            for tag in [
                    str(t) for t in range(
                        1, int(Tails.next_tag(self.dir_tails, cd_id)[0]))
            ]:  # '1' to next-1
                await self._sync_revoc_for_issue(
                    rev_reg_id(cd_id, tag), rr_size if tag == '0' else None)

        makedirs(
            join(self.dir_tails, cd_id),
            exist_ok=True)  # dir required for box id collection, revo or not

        LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json)
        return rv_json