diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 4d9e013664..485d9588c5 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -169,7 +169,7 @@ async def create_and_send_credential_definition( signature_type: str = None, tag: str = None, support_revocation: bool = False, - ) -> Tuple[str, dict]: + ) -> Tuple[str, dict, bool]: """ Send credential definition to ledger and store relevant key matter in wallet. @@ -180,6 +180,9 @@ async def create_and_send_credential_definition( tag: Optional tag to distinguish multiple credential definitions support_revocation: Optional flag to enable revocation for this cred def + Returns: + Tuple with cred def id, cred def structure, and whether it's novel + """ @abstractmethod diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index aceee9ba2e..12408a0f67 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -36,7 +36,6 @@ ) from .util import TAA_ACCEPTED_RECORD_TYPE, EndpointType - GENESIS_TRANSACTION_PATH = tempfile.gettempdir() GENESIS_TRANSACTION_PATH = path.join( GENESIS_TRANSACTION_PATH, "indy_genesis_transactions.txt" @@ -546,7 +545,7 @@ async def create_and_send_credential_definition( signature_type: str = None, tag: str = None, support_revocation: bool = False, - ) -> Tuple[str, dict]: + ) -> Tuple[str, dict, bool]: """ Send credential definition to ledger and store relevant key matter in wallet. @@ -557,6 +556,9 @@ async def create_and_send_credential_definition( tag: Optional tag to distinguish multiple credential definitions support_revocation: Optional flag to enable revocation for this cred def + Returns: + Tuple with cred def id, cred def structure, and whether it's novel + """ public_info = await self.wallet.get_public_did() if not public_info: @@ -568,6 +570,8 @@ async def create_and_send_credential_definition( if not schema: raise LedgerError(f"Ledger {self.pool_name} has no schema {schema_id}") + novel = False + # check if cred def is on ledger already for test_tag in [tag] if tag else ["tag", DEFAULT_CRED_DEF_TAG]: credential_definition_id = issuer.make_credential_definition_id( @@ -609,6 +613,7 @@ async def create_and_send_credential_definition( raise LedgerError(err.message) from err # Cred def is neither on ledger nor in wallet: create and send it + novel = True try: ( credential_definition_id, @@ -624,7 +629,6 @@ async def create_and_send_credential_definition( "Error cannot write cred def when ledger is in read only mode" ) - wallet_cred_def = json.loads(credential_definition_json) with IndyErrorHandler( "Exception when building cred def request", LedgerError ): @@ -632,19 +636,9 @@ async def create_and_send_credential_definition( public_info.did, credential_definition_json ) await self._submit(request_json, True, sign_did=public_info) - ledger_cred_def = await self.fetch_credential_definition( - credential_definition_id - ) - assert wallet_cred_def["value"] == ledger_cred_def["value"] - # Add non-secrets records if not yet present - storage = self.get_indy_storage() - found = await storage.search_records( - type_filter=CRED_DEF_SENT_RECORD_TYPE, - tag_query={"cred_def_id": credential_definition_id}, - ).fetch_all() - - if not found: + # Add non-secrets record + storage = self.get_indy_storage() schema_id_parts = schema_id.split(":") cred_def_tags = { "schema_id": schema_id, @@ -660,7 +654,7 @@ async def create_and_send_credential_definition( ) await storage.add_record(record) - return credential_definition_id, json.loads(credential_definition_json) + return (credential_definition_id, json.loads(credential_definition_json), novel) async def get_credential_definition(self, credential_definition_id: str) -> dict: """ diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index bb4ae44229..665c1c971e 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -608,7 +608,7 @@ async def test_send_schema_ledger_read_only( mock_wallet.get_public_did.return_value.did = "abc" fetch_schema_id = ( - f"{mock_wallet.get_public_did.return_value.did}:{2}:" + f"{mock_wallet.get_public_did.return_value.did}:2:" "schema_name:schema_version" ) mock_check_existing.side_effect = [None, fetch_schema_id] @@ -644,7 +644,7 @@ async def test_send_schema_issuer_error( mock_wallet.get_public_did.return_value.did = "abc" fetch_schema_id = ( - f"{mock_wallet.get_public_did.return_value.did}:{2}:" + f"{mock_wallet.get_public_did.return_value.did}:2:" "schema_name:schema_version" ) mock_check_existing.side_effect = [None, fetch_schema_id] @@ -686,7 +686,7 @@ async def test_send_schema_ledger_transaction_error( mock_wallet.get_public_did.return_value.did = "abc" fetch_schema_id = ( - f"{mock_wallet.get_public_did.return_value.did}:{2}:" + f"{mock_wallet.get_public_did.return_value.did}:2:" "schema_name:schema_version" ) mock_check_existing.side_effect = [None, fetch_schema_id] @@ -1025,16 +1025,108 @@ async def test_send_credential_definition( ) mock_did = mock_wallet.get_public_did.return_value - result_id, result_def = await ledger.create_and_send_credential_definition( + ( + result_id, + result_def, + novel, + ) = await ledger.create_and_send_credential_definition( issuer, schema_id, None, tag ) assert result_id == cred_def_id + assert novel mock_wallet.get_public_did.assert_called_once_with() mock_get_schema.assert_called_once_with(schema_id) mock_build_cred_def.assert_called_once_with(mock_did.did, cred_def_json) + @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.get_schema") + @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger._context_open") + @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger._context_close") + @async_mock.patch( + "aries_cloudagent.ledger.indy.IndyLedger.fetch_credential_definition" + ) + @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger._submit") + @async_mock.patch("aries_cloudagent.storage.indy.IndyStorage.search_records") + @async_mock.patch("aries_cloudagent.storage.indy.IndyStorage.add_record") + @async_mock.patch("indy.ledger.build_cred_def_request") + async def test_send_credential_definition_exists_in_ledger_and_wallet( + self, + mock_build_cred_def, + mock_add_record, + mock_search_records, + mock_submit, + mock_fetch_cred_def, + mock_close, + mock_open, + mock_get_schema, + ): + mock_wallet = async_mock.MagicMock() + mock_wallet.type = "indy" + + mock_search_records.return_value.fetch_all = async_mock.CoroutineMock( + return_value=[] + ) + + mock_get_schema.return_value = {"seqNo": 999} + cred_def_id = f"{self.test_did}:3:CL:999:default" + cred_def_value = { + "primary": {"n": "...", "s": "...", "r": "...", "revocation": None} + } + cred_def = { + "ver": "1.0", + "id": cred_def_id, + "schemaId": "999", + "type": "CL", + "tag": "default", + "value": cred_def_value, + } + cred_def_json = json.dumps(cred_def) + + mock_fetch_cred_def.return_value = {"mock": "cred-def"} + + issuer = async_mock.MagicMock(BaseIssuer) + issuer.make_credential_definition_id.return_value = cred_def_id + issuer.create_and_store_credential_definition.return_value = ( + cred_def_id, + cred_def_json, + ) + issuer.credential_definition_in_wallet.return_value = True + ledger = IndyLedger("name", mock_wallet) + + schema_id = "schema_issuer_did:name:1.0" + tag = "default" + + with async_mock.patch.object( + ledger, "get_indy_storage", async_mock.MagicMock() + ) as mock_get_storage: + mock_get_storage.return_value = async_mock.MagicMock( + add_record=async_mock.CoroutineMock() + ) + + async with ledger: + mock_wallet.get_public_did = async_mock.CoroutineMock() + mock_wallet.get_public_did.return_value = DIDInfo( + self.test_did, self.test_verkey, None + ) + mock_did = mock_wallet.get_public_did.return_value + + ( + result_id, + result_def, + novel, + ) = await ledger.create_and_send_credential_definition( + issuer, schema_id, None, tag + ) + assert result_id == cred_def_id + assert not novel + + mock_wallet.get_public_did.assert_called_once_with() + mock_get_schema.assert_called_once_with(schema_id) + + mock_build_cred_def.assert_not_called() + mock_get_storage.assert_not_called() + @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.get_schema") @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger._context_open") @async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger._context_close") @@ -1414,7 +1506,11 @@ async def test_send_credential_definition_on_ledger_in_wallet( ) mock_did = mock_wallet.get_public_did.return_value - result_id, result_def = await ledger.create_and_send_credential_definition( + ( + result_id, + result_def, + novel, + ) = await ledger.create_and_send_credential_definition( issuer, schema_id, None, tag ) assert result_id == cred_def_id @@ -1473,9 +1569,6 @@ async def test_send_credential_definition_create_cred_def_exception( issuer.create_and_store_credential_definition.side_effect = IssuerError( "invalid structure" ) - # issuer.credential_definition_in_wallet.side_effect = IndyError( - # error_code=ErrorCode.CommonInvalidStructure - # ) ledger = IndyLedger("name", mock_wallet) schema_id = "schema_issuer_did:name:1.0" diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index d0dab8491e..76691cd710 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -139,9 +139,9 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq raise web.HTTPForbidden(reason=reason) issuer: BaseIssuer = await context.inject(BaseIssuer) - try: + try: # even if in wallet, send it and raise if erroneously so async with ledger: - credential_definition_id, credential_definition = await shield( + (cred_def_id, cred_def, novel) = await shield( ledger.create_and_send_credential_definition( issuer, schema_id, @@ -153,19 +153,17 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq except LedgerError as e: raise web.HTTPBadRequest(reason=e.message) from e - # If revocation is requested, create revocation registry - if support_revocation: + # If revocation is requested and cred def is novel, create revocation registry + if support_revocation and novel: tails_base_url = context.settings.get("tails_server_base_url") if not tails_base_url: raise web.HTTPBadRequest(reason="tails_server_base_url not configured") try: # Create registry - issuer_did = credential_definition_id.split(":")[0] + issuer_did = cred_def_id.split(":")[0] revoc = IndyRevocation(context) registry_record = await revoc.init_issuer_registry( - credential_definition_id, - issuer_did, - max_cred_num=revocation_registry_size, + cred_def_id, issuer_did, max_cred_num=revocation_registry_size, ) except RevocationNotSupportedError as e: @@ -199,7 +197,7 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq except RevocationError as e: raise web.HTTPBadRequest(reason=e.message) from e - return web.json_response({"credential_definition_id": credential_definition_id}) + return web.json_response({"credential_definition_id": cred_def_id}) @docs( @@ -253,7 +251,7 @@ async def credential_definitions_get_credential_definition(request: web.BaseRequ """ context = request.app["request_context"] - credential_definition_id = request.match_info["cred_def_id"] + cred_def_id = request.match_info["cred_def_id"] ledger: BaseLedger = await context.inject(BaseLedger, required=False) if not ledger: @@ -263,11 +261,9 @@ async def credential_definitions_get_credential_definition(request: web.BaseRequ raise web.HTTPForbidden(reason=reason) async with ledger: - credential_definition = await ledger.get_credential_definition( - credential_definition_id - ) + cred_def = await ledger.get_credential_definition(cred_def_id) - return web.json_response({"credential_definition": credential_definition}) + return web.json_response({"credential_definition": cred_def}) async def register(app: web.Application): diff --git a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py index 4330b83ca8..6d2baba131 100644 --- a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py +++ b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py @@ -24,7 +24,7 @@ def setUp(self): self.ledger = async_mock.create_autospec(BaseLedger) self.ledger.__aenter__ = async_mock.CoroutineMock(return_value=self.ledger) self.ledger.create_and_send_credential_definition = async_mock.CoroutineMock( - return_value=(CRED_DEF_ID, {"cred": "def"}) + return_value=(CRED_DEF_ID, {"cred": "def"}, True) ) self.ledger.get_credential_definition = async_mock.CoroutineMock( return_value={"cred": "def"}