diff --git a/aries_cloudagent/indy/credx/issuer.py b/aries_cloudagent/indy/credx/issuer.py index e008010929..b0108fedbc 100644 --- a/aries_cloudagent/indy/credx/issuer.py +++ b/aries_cloudagent/indy/credx/issuer.py @@ -20,7 +20,6 @@ ) from ...askar.profile import AskarProfile -from ...core.profile import ProfileSession from ..issuer import ( IndyIssuer, @@ -384,7 +383,6 @@ async def revoke_credentials( revoc_reg_id: str, tails_file_path: str, cred_revoc_ids: Sequence[str], - transaction: ProfileSession = None, ) -> Tuple[str, Sequence[str]]: """ Revoke a set of credentials in a revocation registry. @@ -399,66 +397,84 @@ async def revoke_credentials( """ - txn = transaction if transaction else await self._profile.transaction() - try: - rev_reg_def = await txn.handle.fetch(CATEGORY_REV_REG_DEF, revoc_reg_id) - rev_reg = await txn.handle.fetch( - CATEGORY_REV_REG, revoc_reg_id, for_update=True - ) - rev_reg_info = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True - ) - if not rev_reg_def: - raise IndyIssuerError("Revocation registry definition not found") - if not rev_reg: - raise IndyIssuerError("Revocation registry not found") - if not rev_reg_info: - raise IndyIssuerError("Revocation registry metadata not found") - except AskarError as err: - raise IndyIssuerError("Error retrieving revocation registry") from err + delta = None + failed_crids = set() + max_attempt = 5 + attempt = 0 - try: - rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) - except CredxError as err: - raise IndyIssuerError( - "Error loading revocation registry definition" - ) from err - - rev_crids = [] - failed_crids = [] - max_cred_num = rev_reg_def.max_cred_num - rev_info = rev_reg_info.value_json - used_ids = set(rev_info.get("used_ids") or []) - - for rev_id in cred_revoc_ids: - rev_id = int(rev_id) - if rev_id < 1 or rev_id > max_cred_num: - LOGGER.error( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not in range", - revoc_reg_id, - rev_id, - ) - elif rev_id > rev_info["curr_id"]: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not yet issued", - revoc_reg_id, - rev_id, - ) - elif rev_id in used_ids: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s already revoked", - revoc_reg_id, - rev_id, - ) - else: - rev_crids.append(rev_id) + while True: + attempt += 1 + if attempt >= max_attempt: + raise IndyIssuerError("Repeated conflict attempting to update registry") + try: + async with self._profile.session() as session: + rev_reg_def = await session.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_reg = await session.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_reg_info = await session.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id + ) + if not rev_reg_def: + raise IndyIssuerError("Revocation registry definition not found") + if not rev_reg: + raise IndyIssuerError("Revocation registry not found") + if not rev_reg_info: + raise IndyIssuerError("Revocation registry metadata not found") + except AskarError as err: + raise IndyIssuerError("Error retrieving revocation registry") from err + + try: + rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) + except CredxError as err: + raise IndyIssuerError( + "Error loading revocation registry definition" + ) from err + + rev_crids = set() + failed_crids = set() + max_cred_num = rev_reg_def.max_cred_num + rev_info = rev_reg_info.value_json + used_ids = set(rev_info.get("used_ids") or []) + + for rev_id in cred_revoc_ids: + rev_id = int(rev_id) + if rev_id < 1 or rev_id > max_cred_num: + LOGGER.error( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not in range", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id > rev_info["curr_id"]: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not yet issued", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id in used_ids: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s already revoked", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + else: + rev_crids.add(rev_id) + + if not rev_crids: + break - if rev_crids: try: rev_reg = RevocationRegistry.load(rev_reg.raw_value) + except CredxError as err: + raise IndyIssuerError("Error loading revocation registry") from err + + try: delta = await asyncio.get_event_loop().run_in_executor( None, lambda: rev_reg.update( @@ -472,31 +488,41 @@ async def revoke_credentials( raise IndyIssuerError("Error updating revocation registry") from err try: - await txn.handle.replace( - CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() - ) - used_ids.update(rev_crids) - rev_info["used_ids"] = list(used_ids) - await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info - ) - for cred_rev_id in rev_crids: - issuer_cr_rec = await IssuerCredRevRecord.retrieve_by_ids( - txn, - revoc_reg_id, - str(cred_rev_id), + async with self._profile.transaction() as txn: + rev_reg_upd = await txn.handle.fetch( + CATEGORY_REV_REG, revoc_reg_id, for_update=True + ) + rev_info_upd = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True ) - await issuer_cr_rec.set_state( - txn, IssuerCredRevRecord.STATE_REVOKED + if not rev_reg_upd or not rev_reg_info: + LOGGER.warn( + "Revocation registry missing, skipping update: {}", + revoc_reg_id, + ) + delta = None + break + rev_info_upd = rev_info_upd.value_json + if rev_info_upd != rev_info: + # handle concurrent update to the registry by retrying + continue + await txn.handle.replace( + CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() + ) + used_ids.update(rev_crids) + rev_info_upd["used_ids"] = sorted(used_ids) + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd ) - if not transaction: await txn.commit() except AskarError as err: raise IndyIssuerError("Error saving revocation registry") from err - else: - delta = None + break - return (delta and delta.to_json(), failed_crids) + return ( + delta and delta.to_json(), + [str(rev_id) for rev_id in sorted(failed_crids)], + ) async def merge_revocation_registry_deltas( self, fro_delta: str, to_delta: str @@ -567,7 +593,7 @@ async def create_and_store_revocation_registry( rev_reg_def, rev_reg_def_private, rev_reg, - rev_reg_delta, + _rev_reg_delta, ) = await asyncio.get_event_loop().run_in_executor( None, lambda: RevocationRegistryDefinition.create( diff --git a/aries_cloudagent/indy/issuer.py b/aries_cloudagent/indy/issuer.py index 05c538cb43..3503626f72 100644 --- a/aries_cloudagent/indy/issuer.py +++ b/aries_cloudagent/indy/issuer.py @@ -4,7 +4,6 @@ from typing import Sequence, Tuple from ..core.error import BaseError -from ..core.profile import ProfileSession DEFAULT_CRED_DEF_TAG = "default" @@ -150,7 +149,6 @@ async def revoke_credentials( revoc_reg_id: str, tails_file_path: str, cred_rev_ids: Sequence[str], - transaction: ProfileSession = None, ) -> Tuple[str, Sequence[str]]: """ Revoke a set of credentials in a revocation registry. diff --git a/aries_cloudagent/indy/sdk/issuer.py b/aries_cloudagent/indy/sdk/issuer.py index 2298143e61..8fe7d23777 100644 --- a/aries_cloudagent/indy/sdk/issuer.py +++ b/aries_cloudagent/indy/sdk/issuer.py @@ -8,7 +8,6 @@ import indy.blob_storage from indy.error import AnoncredsRevocationRegistryFullError, IndyError, ErrorCode -from ...core.profile import ProfileSession from ...indy.sdk.profile import IndySdkProfile from ...messaging.util import encode from ...revocation.models.issuer_cred_rev_record import IssuerCredRevRecord @@ -267,7 +266,6 @@ async def revoke_credentials( rev_reg_id: str, tails_file_path: str, cred_rev_ids: Sequence[str], - transaction: ProfileSession = None, ) -> Tuple[str, Sequence[str]]: """ Revoke a set of credentials in a revocation registry. @@ -281,7 +279,7 @@ async def revoke_credentials( Tuple with the combined revocation delta, list of cred rev ids not revoked """ - failed_crids = [] + failed_crids = set() tails_reader_handle = await create_tails_reader(tails_file_path) result_json = None @@ -290,22 +288,12 @@ async def revoke_credentials( "Exception when revoking credential", IndyIssuerError ): try: - session = await self.profile.session() delta_json = await indy.anoncreds.issuer_revoke_credential( self.profile.wallet.handle, tails_reader_handle, rev_reg_id, cred_rev_id, ) - issuer_cr_rec = await IssuerCredRevRecord.retrieve_by_ids( - session, - rev_reg_id, - cred_rev_id, - ) - await issuer_cr_rec.set_state( - session, IssuerCredRevRecord.STATE_REVOKED - ) - except IndyError as err: if err.error_code == ErrorCode.AnoncredsInvalidUserRevocId: LOGGER.error( @@ -323,7 +311,7 @@ async def revoke_credentials( err, "Revocation error", IndyIssuerError ).roll_up ) - failed_crids.append(cred_rev_id) + failed_crids.add(int(cred_rev_id)) continue except StorageError as err: LOGGER.warning( @@ -344,7 +332,7 @@ async def revoke_credentials( else: result_json = delta_json - return (result_json, failed_crids) + return (result_json, [str(rev_id) for rev_id in sorted(failed_crids)]) async def merge_revocation_registry_deltas( self, fro_delta: str, to_delta: str diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 4ee333620b..85729ac9f2 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -113,7 +113,7 @@ async def revoke_credential( issuer_rr_rec = await revoc.get_issuer_rev_reg_record(rev_reg_id) if not issuer_rr_rec: raise RevocationManagerError( - f"No revocation registry record found for id {rev_reg_id}" + f"No revocation registry record found for id: {rev_reg_id}" ) if notify: @@ -137,19 +137,25 @@ async def revoke_credential( (delta_json, _) = await issuer.revoke_credentials( issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids ) - if delta_json: - issuer_rr_rec.revoc_reg_entry = json.loads(delta_json) - await issuer_rr_rec.send_entry(self._profile) - async with self._profile.session() as session: - await issuer_rr_rec.clear_pending(session) - await self.set_cred_revoked_state(rev_reg_id, [cred_rev_id]) - await notify_revocation_published_event( - self._profile, rev_reg_id, [cred_rev_id] + async with self._profile.transaction() as txn: + issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( + txn, issuer_rr_rec.record_id, for_update=True ) + if delta_json: + issuer_rr_upd.revoc_reg_entry = json.loads(delta_json) + await issuer_rr_upd.clear_pending(txn, crids) + await txn.commit() + if delta_json: + await issuer_rr_upd.send_entry(self._profile) + await self.set_cred_revoked_state(rev_reg_id, [cred_rev_id]) + await notify_revocation_published_event( + self._profile, rev_reg_id, [cred_rev_id] + ) else: - async with self._profile.session() as session: - await issuer_rr_rec.mark_pending(session, cred_rev_id) + async with self._profile.transaction() as txn: + await issuer_rr_rec.mark_pending(txn, cred_rev_id) + await txn.commit() async def publish_pending_revocations( self, @@ -181,37 +187,42 @@ async def publish_pending_revocations( result = {} issuer = self._profile.inject(IndyIssuer) - async with self._profile.transaction() as txn: - issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(txn) - for issuer_rr_rec in issuer_rr_recs: - rrid = issuer_rr_rec.revoc_reg_id - crids = [] - if not rrid2crid: - crids = issuer_rr_rec.pending_pub - elif rrid in rrid2crid: - crids = [ - crid - for crid in issuer_rr_rec.pending_pub - if crid in (rrid2crid[rrid] or []) or not rrid2crid[rrid] - ] - if crids: - # FIXME - must use the same transaction - (delta_json, failed_crids) = await issuer.revoke_credentials( - issuer_rr_rec.revoc_reg_id, - issuer_rr_rec.tails_local_path, - crids, - transaction=txn, + async with self._profile.session() as session: + issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(session) + + for issuer_rr_rec in issuer_rr_recs: + rrid = issuer_rr_rec.revoc_reg_id + if rrid2crid: + if rrid not in rrid2crid: + continue + limit_crids = rrid2crid[rrid] + else: + limit_crids = () + crids = set(issuer_rr_rec.pending_pub or ()) + if limit_crids: + crids = crids.intersection(limit_crids) + if crids: + (delta_json, failed_crids) = await issuer.revoke_credentials( + issuer_rr_rec.revoc_reg_id, + issuer_rr_rec.tails_local_path, + crids, + ) + async with self._profile.transaction() as txn: + issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( + txn, issuer_rr_rec.record_id, for_update=True ) - issuer_rr_rec.revoc_reg_entry = json.loads(delta_json) - await issuer_rr_rec.send_entry(self._profile) - published = [crid for crid in crids if crid not in failed_crids] - result[issuer_rr_rec.revoc_reg_id] = published - await issuer_rr_rec.clear_pending(txn, published) + if delta_json: + issuer_rr_upd.revoc_reg_entry = json.loads(delta_json) + await issuer_rr_upd.clear_pending(txn, crids) await txn.commit() - await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) - await notify_revocation_published_event( - self._profile, issuer_rr_rec.revoc_reg_id, crids - ) + if delta_json: + await issuer_rr_upd.send_entry(self._profile) + published = sorted(crid for crid in crids if crid not in failed_crids) + result[issuer_rr_rec.revoc_reg_id] = published + await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) + await notify_revocation_published_event( + self._profile, issuer_rr_rec.revoc_reg_id, crids + ) return result @@ -246,6 +257,7 @@ async def clear_pending_revocations( """ result = {} + notify = [] async with self._profile.transaction() as txn: issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(txn) @@ -254,9 +266,12 @@ async def clear_pending_revocations( await issuer_rr_rec.clear_pending(txn, (purge or {}).get(rrid)) if issuer_rr_rec.pending_pub: result[rrid] = issuer_rr_rec.pending_pub - await notify_pending_cleared_event(self._profile, rrid) + notify.append(rrid) await txn.commit() + for rrid in notify: + await notify_pending_cleared_event(self._profile, rrid) + return result async def set_cred_revoked_state( @@ -274,31 +289,31 @@ async def set_cred_revoked_state( """ for cred_rev_id in cred_rev_ids: - async with self._profile.session() as session: + async with self._profile.transaction() as txn: try: rev_rec = await IssuerCredRevRecord.retrieve_by_ids( - session, rev_reg_id, cred_rev_id + txn, rev_reg_id, cred_rev_id ) try: cred_ex_record = await V10CredentialExchange.retrieve_by_id( - session, rev_rec.cred_ex_id + txn, rev_rec.cred_ex_id, for_update=True ) cred_ex_record.state = ( V10CredentialExchange.STATE_CREDENTIAL_REVOKED ) - await cred_ex_record.save(session, reason="revoke credential") + await cred_ex_record.save(txn, reason="revoke credential") + await txn.commit() except StorageNotFoundError: try: cred_ex_record = await V20CredExRecord.retrieve_by_id( - session, rev_rec.cred_ex_id + txn, rev_rec.cred_ex_id, for_update=True ) cred_ex_record.state = ( V20CredExRecord.STATE_CREDENTIAL_REVOKED ) - await cred_ex_record.save( - session, reason="revoke credential" - ) + await cred_ex_record.save(txn, reason="revoke credential") + await txn.commit() except StorageNotFoundError: pass diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 10bd6cb336..f22ac1c0fd 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -1337,16 +1337,13 @@ async def on_revocation_registry_event(profile: Profile, event: Event): profile, f"{tails_base_url}/{registry_record.revoc_reg_id}", ) - async with profile.session() as session: - rev_reg_resp = await registry_record.send_def( - session.profile, - write_ledger=write_ledger, - endorser_did=endorser_did, - ) - except RevocationError as e: - raise RevocationError(e.message) from e - except RevocationNotSupportedError as e: - raise RevocationNotSupportedError(reason=e.message) from e + rev_reg_resp = await registry_record.send_def( + profile, + write_ledger=write_ledger, + endorser_did=endorser_did, + ) + except RevocationError: + raise if not create_transaction_for_endorser: meta_data = event.payload @@ -1384,19 +1381,18 @@ async def on_revocation_registry_event(profile: Profile, event: Event): except (StorageError, TransactionManagerError) as err: raise TransactionManagerError(reason=err.roll_up) from err - async with profile.session() as session: - responder = session.inject_or(BaseResponder) - if responder: - await responder.send( - revo_transaction_request, - connection_id=connection.connection_id, - ) - else: - LOGGER.warning( - "Configuration has no BaseResponder: cannot update " - "revocation on cred def %s", - cred_def_id, - ) + responder = profile.inject_or(BaseResponder) + if responder: + await responder.send( + revo_transaction_request, + connection_id=connection.connection_id, + ) + else: + LOGGER.warning( + "Configuration has no BaseResponder: cannot update " + "revocation on cred def %s", + cred_def_id, + ) async def on_revocation_entry_event(profile: Profile, event: Event): @@ -1429,10 +1425,8 @@ async def on_revocation_entry_event(profile: Profile, event: Event): write_ledger=write_ledger, endorser_did=endorser_did, ) - except RevocationError as e: - raise RevocationError(e.message) from e - except RevocationNotSupportedError as e: - raise RevocationError(e.message) from e + except RevocationError: + raise if not create_transaction_for_endorser: meta_data = event.payload @@ -1468,19 +1462,18 @@ async def on_revocation_entry_event(profile: Profile, event: Event): except (StorageError, TransactionManagerError) as err: raise RevocationError(err.roll_up) from err - async with profile.session() as session: - responder = session.inject_or(BaseResponder) - if responder: - await responder.send( - revo_transaction_request, - connection_id=connection.connection_id, - ) - else: - LOGGER.warning( - "Configuration has no BaseResponder: cannot update " - "revocation on cred def %s", - event.payload["endorser"]["cred_def_id"], - ) + responder = profile.inject_or(BaseResponder) + if responder: + await responder.send( + revo_transaction_request, + connection_id=connection.connection_id, + ) + else: + LOGGER.warning( + "Configuration has no BaseResponder: cannot update " + "revocation on cred def %s", + event.payload["endorser"]["cred_def_id"], + ) async def on_revocation_tails_file_event(profile: Profile, event: Event): diff --git a/aries_cloudagent/revocation/tests/test_manager.py b/aries_cloudagent/revocation/tests/test_manager.py index 72163e2830..470107b6ab 100644 --- a/aries_cloudagent/revocation/tests/test_manager.py +++ b/aries_cloudagent/revocation/tests/test_manager.py @@ -2,6 +2,7 @@ from asynctest import mock as async_mock from asynctest import TestCase as AsyncTestCase +from more_itertools import side_effect from ...core.in_memory import InMemoryProfile from ...indy.issuer import IndyIssuer @@ -32,22 +33,27 @@ async def setUp(self): async def test_revoke_credential_publish(self): CRED_EX_ID = "dummy-cxid" CRED_REV_ID = "1" + mock_issuer_rev_reg_record = async_mock.MagicMock( + revoc_reg_id=REV_REG_ID, + tails_local_path=TAILS_LOCAL, + send_entry=async_mock.CoroutineMock(), + clear_pending=async_mock.CoroutineMock(), + ) + with async_mock.patch.object( test_module.IssuerCredRevRecord, "retrieve_by_cred_ex_id", async_mock.CoroutineMock(), ) as mock_retrieve, async_mock.patch.object( test_module, "IndyRevocation", autospec=True - ) as revoc: + ) as revoc, async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_id", + async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_record), + ): mock_retrieve.return_value = async_mock.MagicMock( rev_reg_id="dummy-rr-id", cred_rev_id=CRED_REV_ID ) - mock_issuer_rev_reg_record = async_mock.MagicMock( - revoc_reg_id=REV_REG_ID, - tails_local_path=TAILS_LOCAL, - send_entry=async_mock.CoroutineMock(), - clear_pending=async_mock.CoroutineMock(), - ) mock_rev_reg = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock() ) @@ -80,7 +86,6 @@ async def test_revoke_credential_publish(self): async def test_revoke_cred_by_cxid_not_found(self): CRED_EX_ID = "dummy-cxid" - CRED_REV_ID = "1" with async_mock.patch.object( test_module.IssuerCredRevRecord, @@ -120,16 +125,25 @@ async def test_revoke_credential_no_rev_reg_rec(self): async def test_revoke_credential_pend(self): CRED_REV_ID = "1" + mock_issuer_rev_reg_record = async_mock.MagicMock( + mark_pending=async_mock.CoroutineMock() + ) + with async_mock.patch.object( test_module, "IndyRevocation", autospec=True ) as revoc, async_mock.patch.object( self.profile, "session", async_mock.MagicMock(return_value=self.profile.session()), - ) as session: - mock_issuer_rev_reg_record = async_mock.MagicMock( - mark_pending=async_mock.CoroutineMock() - ) + ) as session, async_mock.patch.object( + self.profile, + "transaction", + async_mock.MagicMock(return_value=session.return_value), + ) as session, async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_id", + async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_record), + ): revoc.return_value.get_issuer_rev_reg_record = async_mock.CoroutineMock( return_value=mock_issuer_rev_reg_record ) @@ -142,7 +156,7 @@ async def test_revoke_credential_pend(self): session.return_value, CRED_REV_ID ) - async def test_publish_pending_revocations(self): + async def test_publish_pending_revocations_basic(self): deltas = [ { "ver": "1.0", @@ -169,7 +183,11 @@ async def test_publish_pending_revocations(self): test_module.IssuerRevRegRecord, "query_by_pending", async_mock.CoroutineMock(return_value=[mock_issuer_rev_reg_record]), - ) as record_query: + ), async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_id", + async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_record), + ): issuer = async_mock.MagicMock(IndyIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas @@ -202,6 +220,7 @@ async def test_publish_pending_revocations_1_rev_reg_all(self): mock_issuer_rev_reg_records = [ async_mock.MagicMock( + record_id=0, revoc_reg_id=REV_REG_ID, tails_local_path=TAILS_LOCAL, pending_pub=["1", "2"], @@ -209,6 +228,7 @@ async def test_publish_pending_revocations_1_rev_reg_all(self): clear_pending=async_mock.CoroutineMock(), ), async_mock.MagicMock( + record_id=1, revoc_reg_id=f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:tag2", tails_local_path=TAILS_LOCAL, pending_pub=["9", "99"], @@ -220,7 +240,13 @@ async def test_publish_pending_revocations_1_rev_reg_all(self): test_module.IssuerRevRegRecord, "query_by_pending", async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_records), - ) as record: + ), async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_id", + async_mock.CoroutineMock( + side_effect=lambda _, id, **args: mock_issuer_rev_reg_records[id] + ), + ): issuer = async_mock.MagicMock(IndyIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas @@ -254,6 +280,7 @@ async def test_publish_pending_revocations_1_rev_reg_some(self): mock_issuer_rev_reg_records = [ async_mock.MagicMock( + record_id=0, revoc_reg_id=REV_REG_ID, tails_local_path=TAILS_LOCAL, pending_pub=["1", "2"], @@ -261,6 +288,7 @@ async def test_publish_pending_revocations_1_rev_reg_some(self): clear_pending=async_mock.CoroutineMock(), ), async_mock.MagicMock( + record_id=1, revoc_reg_id=f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:tag2", tails_local_path=TAILS_LOCAL, pending_pub=["9", "99"], @@ -272,7 +300,13 @@ async def test_publish_pending_revocations_1_rev_reg_some(self): test_module.IssuerRevRegRecord, "query_by_pending", async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_records), - ) as record: + ), async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_id", + async_mock.CoroutineMock( + side_effect=lambda _, id, **args: mock_issuer_rev_reg_records[id] + ), + ): issuer = async_mock.MagicMock(IndyIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas