From 5d3a5eaf5eff99368f5db67f2c23de1869dc1a5f Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Fri, 20 Oct 2023 00:44:18 +0530 Subject: [PATCH] update send_entry on calling publish-revocations Signed-off-by: Shaanjot Gill --- aries_cloudagent/revocation/manager.py | 28 +++- aries_cloudagent/revocation/routes.py | 10 +- .../revocation/tests/test_manager.py | 139 ++++++++++++++++++ .../revocation/tests/test_routes.py | 38 +++++ 4 files changed, 211 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 54f436f33c..b57ae4c699 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -161,7 +161,8 @@ async def revoke_credential( ) except StorageNotFoundError: raise RevocationManagerError( - f"No connection record found for id: {connection_id}" + "No endorser connection record found " + f"for id: {connection_id}" ) endorser_info = await connection_record.metadata_get( session, "endorser_info" @@ -206,6 +207,7 @@ async def update_rev_reg_revoked_state( async def publish_pending_revocations( self, rrid2crid: Mapping[Text, Sequence[Text]] = None, + connection_id: str = None, ) -> Mapping[Text, Sequence[Text]]: """Publish pending revocations to the ledger. @@ -226,6 +228,7 @@ async def publish_pending_revocations( - all pending revocations from all revocation registry tagged 0 - pending ["1", "2"] from revocation registry tagged 1 - no pending revocations from any other revocation registries. + connection_id: connection identifier for endorser connection to use Returns: mapping from each revocation registry id to its cred rev ids published. """ @@ -263,7 +266,28 @@ async def publish_pending_revocations( await txn.commit() await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) if delta_json: - await issuer_rr_upd.send_entry(self._profile) + if connection_id: + async with self._profile.session() as session: + try: + connection_record = await ConnRecord.retrieve_by_id( + session, connection_id + ) + except StorageNotFoundError: + raise RevocationManagerError( + "No endorser connection record found " + f"for id: {connection_id}" + ) + endorser_info = await connection_record.metadata_get( + session, "endorser_info" + ) + endorser_did = endorser_info["endorser_did"] + await issuer_rr_upd.send_entry( + self._profile, + write_ledger=False, + endorser_did=endorser_did, + ) + else: + 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 notify_revocation_published_event( diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 5c4056db43..ca433d8cbf 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -594,10 +594,16 @@ async def publish_revocations(request: web.BaseRequest): rrid2crid = body.get("rrid2crid") rev_manager = RevocationManager(context.profile) - + profile = context.profile + connection_id = None + if is_author_role(profile): + connection_id = await get_endorser_connection_id(profile) + if not connection_id: + raise web.HTTPBadRequest(reason="No endorser connection found") try: rev_reg_resp = await rev_manager.publish_pending_revocations( - rrid2crid, + rrid2crid=rrid2crid, + connection_id=connection_id, ) except (RevocationError, StorageError, IndyIssuerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err diff --git a/aries_cloudagent/revocation/tests/test_manager.py b/aries_cloudagent/revocation/tests/test_manager.py index 86edf22fbc..79f9270db6 100644 --- a/aries_cloudagent/revocation/tests/test_manager.py +++ b/aries_cloudagent/revocation/tests/test_manager.py @@ -313,6 +313,145 @@ async def test_revoke_credential_pend(self): issuer.revoke_credentials.assert_not_awaited() + async def test_publish_pending_revocations_endorser(self): + deltas = [ + { + "ver": "1.0", + "value": {"prevAccum": "1 ...", "accum": "21 ...", "issued": [1, 2, 3]}, + }, + { + "ver": "1.0", + "value": { + "prevAccum": "21 ...", + "accum": "36 ...", + "issued": [1, 2, 3], + }, + }, + ] + + 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"], + send_entry=async_mock.CoroutineMock(), + 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"], + send_entry=async_mock.CoroutineMock(), + clear_pending=async_mock.CoroutineMock(), + ), + ] + conn_record = ConnRecord( + their_label="Hello", + their_role=ConnRecord.Role.RESPONDER.rfc160, + alias="Bob", + ) + session = await self.profile.session() + await conn_record.save(session) + await conn_record.metadata_set( + session, + key="endorser_info", + value={ + "endorser_did": "test_endorser_did", + "endorser_name": "test_endorser_name", + }, + ) + conn_id = conn_record.connection_id + assert conn_id is not None + with async_mock.patch.object( + test_module.IssuerRevRegRecord, + "query_by_pending", + async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_records), + ), 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 + ) + + issuer.revoke_credentials = async_mock.CoroutineMock( + side_effect=[(json.dumps(delta), []) for delta in deltas] + ) + self.profile.context.injector.bind_instance(IndyIssuer, issuer) + manager = RevocationManager(self.profile) + result = await manager.publish_pending_revocations( + rrid2crid={REV_REG_ID: "2"}, connection_id=conn_id + ) + assert result == {REV_REG_ID: ["2"]} + mock_issuer_rev_reg_records[0].clear_pending.assert_called_once() + mock_issuer_rev_reg_records[1].clear_pending.assert_not_called() + + async def test_publish_pending_revocations_endorser_x(self): + deltas = [ + { + "ver": "1.0", + "value": {"prevAccum": "1 ...", "accum": "21 ...", "issued": [1, 2, 3]}, + }, + { + "ver": "1.0", + "value": { + "prevAccum": "21 ...", + "accum": "36 ...", + "issued": [1, 2, 3], + }, + }, + ] + + 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"], + send_entry=async_mock.CoroutineMock(), + 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"], + send_entry=async_mock.CoroutineMock(), + clear_pending=async_mock.CoroutineMock(), + ), + ] + with async_mock.patch.object( + test_module.IssuerRevRegRecord, + "query_by_pending", + async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_records), + ), 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 + ) + + issuer.revoke_credentials = async_mock.CoroutineMock( + side_effect=[(json.dumps(delta), []) for delta in deltas] + ) + self.profile.context.injector.bind_instance(IndyIssuer, issuer) + manager = RevocationManager(self.profile) + with self.assertRaises(RevocationManagerError): + result = await manager.publish_pending_revocations( + rrid2crid={REV_REG_ID: "2"}, connection_id="invalid_conn_id" + ) + async def test_publish_pending_revocations_basic(self): deltas = [ { diff --git a/aries_cloudagent/revocation/tests/test_routes.py b/aries_cloudagent/revocation/tests/test_routes.py index 8c73ccffe8..c51ea29e3b 100644 --- a/aries_cloudagent/revocation/tests/test_routes.py +++ b/aries_cloudagent/revocation/tests/test_routes.py @@ -290,6 +290,44 @@ async def test_publish_revocations_x(self): with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.publish_revocations(self.request) + async def test_publish_revocations_endorser(self): + self.author_request.json = async_mock.CoroutineMock() + + with async_mock.patch.object( + test_module, "RevocationManager", autospec=True + ) as mock_mgr, async_mock.patch.object( + test_module, + "get_endorser_connection_id", + async_mock.CoroutineMock(return_value="dummy-conn-id"), + ), async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + pub_pending = async_mock.CoroutineMock() + mock_mgr.return_value.publish_pending_revocations = pub_pending + + await test_module.publish_revocations(self.author_request) + + mock_response.assert_called_once_with( + {"rrid2crid": pub_pending.return_value} + ) + + async def test_publish_revocations_endorser_x(self): + self.author_request.json = async_mock.CoroutineMock() + + with async_mock.patch.object( + test_module, "RevocationManager", autospec=True + ) as mock_mgr, async_mock.patch.object( + test_module, + "get_endorser_connection_id", + async_mock.CoroutineMock(return_value=None), + ), async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + pub_pending = async_mock.CoroutineMock() + mock_mgr.return_value.publish_pending_revocations = pub_pending + with self.assertRaises(test_module.web.HTTPBadRequest): + await test_module.publish_revocations(self.author_request) + async def test_clear_pending_revocations(self): self.request.json = async_mock.CoroutineMock()