From 15e65c6ed129a2c0a70eb30002e0063064133d8e Mon Sep 17 00:00:00 2001 From: jamshale Date: Tue, 16 Jan 2024 18:37:33 +0000 Subject: [PATCH 1/3] Anoncreds - schema endorsement Signed-off-by: jamshale --- .../anoncreds/default/did_indy/registry.py | 6 +- .../anoncreds/default/legacy_indy/author.py | 54 ++++ .../anoncreds/default/legacy_indy/registry.py | 133 ++++++++-- .../legacy_indy/tests/test_registry.py | 240 ++++++++++++++++-- aries_cloudagent/anoncreds/events.py | 42 ++- aries_cloudagent/anoncreds/issuer.py | 13 +- .../anoncreds/models/anoncreds_schema.py | 6 +- aries_cloudagent/anoncreds/registry.py | 19 +- aries_cloudagent/anoncreds/routes.py | 53 +++- .../anoncreds/tests/test_issuer.py | 38 ++- .../anoncreds/tests/test_routes.py | 56 +++- aries_cloudagent/ledger/base.py | 21 +- .../endorse_transaction/v1_0/manager.py | 55 ++-- .../v1_0/tests/test_manager.py | 61 ++++- demo/features/0586-sign-transaction.feature | 7 + demo/features/steps/0586-sign-transaction.py | 54 ++-- 16 files changed, 723 insertions(+), 135 deletions(-) create mode 100644 aries_cloudagent/anoncreds/default/legacy_indy/author.py diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index e67bd17da0..99179462e0 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -6,6 +6,7 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile +from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver from ...models.anoncreds_cred_def import ( CredDef, CredDefResult, @@ -14,13 +15,12 @@ from ...models.anoncreds_revocation import ( GetRevListResult, GetRevRegDefResult, - RevRegDef, - RevRegDefResult, RevList, RevListResult, + RevRegDef, + RevRegDefResult, ) from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult -from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/author.py b/aries_cloudagent/anoncreds/default/legacy_indy/author.py new file mode 100644 index 0000000000..f4c9691192 --- /dev/null +++ b/aries_cloudagent/anoncreds/default/legacy_indy/author.py @@ -0,0 +1,54 @@ +"""Author specific for indy legacy.""" + +from typing import Optional + +from aiohttp import web + +from aries_cloudagent.connections.models.conn_record import ConnRecord +from aries_cloudagent.messaging.models.base import BaseModelError +from aries_cloudagent.protocols.endorse_transaction.v1_0.util import ( + get_endorser_connection_id, +) +from aries_cloudagent.storage.error import StorageNotFoundError + + +async def get_endorser_info(profile, options: Optional[dict] = {}): + """Gets the endorser did for the current transaction.""" + endorser_connection_id = options.get("endorser_connection_id", None) + if not endorser_connection_id: + endorser_connection_id = await get_endorser_connection_id(profile) + + if not endorser_connection_id: + raise web.HTTPForbidden(reason="No endorser connection found") + + try: + async with profile.session() as session: + connection_record = await ConnRecord.retrieve_by_id( + session, endorser_connection_id + ) + endorser_info = await connection_record.metadata_get( + session, "endorser_info" + ) + except StorageNotFoundError as err: + raise web.HTTPNotFound( + reason=f"Connection for endorser with id {endorser_connection_id} not found" + ) from err + except BaseModelError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + if not endorser_info: + raise web.HTTPForbidden( + reason=( + "Endorser Info is not set up in " + "connection metadata for this connection record" + ) + ) + if "endorser_did" not in endorser_info.keys(): + raise web.HTTPForbidden( + reason=( + ' "endorser_did" is not set in "endorser_info"' + " in connection metadata for this connection record" + ) + ) + + return endorser_info["endorser_did"], endorser_connection_id diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 227a090999..9c9bfaa170 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -3,11 +3,13 @@ import json import logging import re +import uuid from asyncio import shield from typing import List, Optional, Pattern, Sequence, Tuple from base58 import alphabet +from ....anoncreds.default.legacy_indy.author import get_endorser_info from ....cache.base import BaseCache from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -22,9 +24,20 @@ GET_CRED_DEF, IndyLedgerRequestsExecutor, ) +from ....messaging.responder import BaseResponder from ....multitenant.base import BaseMultitenantManager -from ....revocation_anoncreds.models.issuer_cred_rev_record import IssuerCredRevRecord +from ....protocols.endorse_transaction.v1_0.manager import ( + TransactionManager, + TransactionManagerError, +) +from ....protocols.endorse_transaction.v1_0.util import is_author_role +from ....revocation_anoncreds.models.issuer_cred_rev_record import ( + IssuerCredRevRecord, +) from ....revocation_anoncreds.recover import generate_ledger_rrrecovery_txn +from ....storage.error import StorageError +from ....utils import sentinel +from ....wallet.did_info import DIDInfo from ...base import ( AnonCredsObjectAlreadyExists, AnonCredsObjectNotFound, @@ -194,11 +207,7 @@ async def register_schema( # Assume endorser role on the network, no option for 3rd-party endorser ledger = profile.inject_or(BaseLedger) if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - # TODO is this warning necessary? - reason += ": missing wallet-type?" - raise AnonCredsRegistrationError(reason) + raise AnonCredsRegistrationError("No ledger available") # Translate schema into format expected by Indy LOGGER.debug("Registering schema: %s", schema_id) @@ -212,32 +221,90 @@ async def register_schema( } LOGGER.debug("schema value: %s", indy_schema) + endorser_did = None + create_transaction = options.get("create_transaction_for_endorser", False) + + if is_author_role(profile) or create_transaction: + endorser_did, endorser_connection_id = await get_endorser_info( + profile, options + ) + + write_ledger = ( + True if endorser_did is None and not create_transaction else False + ) + + # Get either the transaction or the seq_no or the created schema async with ledger: try: - seq_no = await shield( - ledger.send_schema_anoncreds(schema_id, indy_schema) + result = await shield( + ledger.send_schema_anoncreds( + schema_id, + indy_schema, + write_ledger=write_ledger, + endorser_did=endorser_did, + ) ) except LedgerObjectAlreadyExistsError as err: - indy_schema = err.obj - schema = AnonCredsSchema( - name=indy_schema["name"], - version=indy_schema["version"], - attr_names=indy_schema["attrNames"], - issuer_id=indy_schema["id"].split(":")[0], - ) raise AnonCredsSchemaAlreadyExists(err.message, err.obj_id, schema) except (AnonCredsIssuerError, LedgerError) as err: raise AnonCredsRegistrationError("Failed to register schema") from err + # Didn't need endorsement, so return schema result + if write_ledger: + return SchemaResult( + job_id=None, + schema_state=SchemaState( + state=SchemaState.STATE_FINISHED, + schema_id=schema_id, + schema=schema, + ), + registration_metadata={}, + schema_metadata={"seqNo": result}, + ) + + # Need endorsement, so execute transaction flow + (schema_id, schema_def) = result + + job_id = uuid.uuid4().hex + meta_data = {"context": {"job_id": job_id, "schema_id": schema_id}} + + transaction_manager = TransactionManager(profile) + try: + transaction = await transaction_manager.create_record( + messages_attach=schema_def["signed_txn"], + connection_id=endorser_connection_id, + meta_data=meta_data, + ) + except StorageError: + raise AnonCredsRegistrationError("Failed to store transaction record") + + if profile.settings.get("endorser.auto_request"): + try: + ( + transaction, + transaction_request, + ) = await transaction_manager.create_request(transaction=transaction) + except (StorageError, TransactionManagerError) as err: + raise AnonCredsRegistrationError( + "Transaction manager failed to create request: " + err.roll_up + ) from err + + responder = profile.inject(BaseResponder) + await responder.send( + message=transaction_request, + connection_id=endorser_connection_id, + ) + return SchemaResult( - job_id=None, + job_id=job_id, schema_state=SchemaState( - state=SchemaState.STATE_FINISHED, + state=SchemaState.STATE_TRANSACTION_REQUESTED, schema_id=schema_id, schema=schema, ), - registration_metadata={}, - schema_metadata={"seqNo": seq_no}, + registration_metadata={ + "txn": transaction.serialize(), + }, ) async def get_credential_definition( @@ -771,3 +838,31 @@ async def fix_ledger_entry( applied_txn = ledger_response["result"] return (rev_reg_delta, recovery_txn, applied_txn) + + async def txn_submit( + self, + profile: Profile, + ledger_transaction: str, + sign: bool = None, + taa_accept: bool = None, + sign_did: DIDInfo = sentinel, + write_ledger: bool = True, + ) -> str: + """Submit a transaction to the ledger.""" + ledger = profile.inject(BaseLedger) + + if not ledger: + raise LedgerError("No ledger available") + + try: + return await shield( + ledger.txn_submit( + ledger_transaction, + sign=sign, + taa_accept=taa_accept, + sign_did=sign_did, + write_ledger=write_ledger, + ) + ) + except LedgerError as err: + raise AnonCredsRegistrationError(err.roll_up) from err diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py index b29cbbe291..59583646d6 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py @@ -1,10 +1,32 @@ """Test LegacyIndyRegistry.""" -import pytest +import json import re -from ..registry import LegacyIndyRegistry + +import pytest +from asynctest import TestCase from base58 import alphabet +from aries_cloudagent.anoncreds.base import ( + AnonCredsRegistrationError, + AnonCredsSchemaAlreadyExists, +) +from aries_cloudagent.anoncreds.models.anoncreds_schema import ( + AnonCredsSchema, + SchemaResult, +) +from aries_cloudagent.askar.profile_anon import AskarAnoncredsProfile +from aries_cloudagent.connections.models.conn_record import ConnRecord +from aries_cloudagent.core.in_memory.profile import InMemoryProfile +from aries_cloudagent.ledger.error import LedgerError, LedgerObjectAlreadyExistsError +from aries_cloudagent.messaging.responder import BaseResponder +from aries_cloudagent.protocols.endorse_transaction.v1_0.manager import ( + TransactionManager, +) +from aries_cloudagent.tests import mock + +from .. import registry as test_module + B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$" INDY_SCHEMA_ID = rf"^[{B58}]{{21,22}}:2:.+:[0-9.]+$" @@ -33,23 +55,211 @@ "WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:20:tag:CL_ACCUM:0" ) +mock_schema = AnonCredsSchema( + issuer_id="did:indy:sovrin:SGrjRL82Y9ZZbzhUDXokvQ", + attr_names=["name", "age", "vmax"], + name="test_schema", + version="1.0", +) -@pytest.fixture -def registry(): - """Registry fixture""" - yield LegacyIndyRegistry() +@pytest.mark.anoncreds +class TestLegacyIndyRegistry(TestCase): + def setUp(self): + self.profile = InMemoryProfile.test_profile( + settings={"wallet-type": "askar-anoncreds"}, + profile_class=AskarAnoncredsProfile, + ) + self.registry = test_module.LegacyIndyRegistry() -@pytest.mark.indy -class TestLegacyIndyRegistry: - @pytest.mark.asyncio - async def test_supported_did_regex(self, registry: LegacyIndyRegistry): + async def test_supported_did_regex(self): """Test the supported_did_regex.""" - assert registry.supported_identifiers_regex == SUPPORTED_ID_REGEX - assert bool(registry.supported_identifiers_regex.match(TEST_INDY_DID)) - assert bool(registry.supported_identifiers_regex.match(TEST_INDY_DID_1)) - assert bool(registry.supported_identifiers_regex.match(TEST_INDY_SCHEMA_ID)) + assert self.registry.supported_identifiers_regex == SUPPORTED_ID_REGEX + assert bool(self.registry.supported_identifiers_regex.match(TEST_INDY_DID)) + assert bool(self.registry.supported_identifiers_regex.match(TEST_INDY_DID_1)) + assert bool( + self.registry.supported_identifiers_regex.match(TEST_INDY_SCHEMA_ID) + ) assert bool( - registry.supported_identifiers_regex.match(TEST_INDY_REV_REG_DEF_ID) + self.registry.supported_identifiers_regex.match(TEST_INDY_REV_REG_DEF_ID) + ) + + async def test_register_schema_no_endorsement(self): + self.profile.inject_or = mock.MagicMock( + return_value=mock.CoroutineMock( + send_schema_anoncreds=mock.CoroutineMock(return_value=1) + ) + ) + + result = await self.registry.register_schema(self.profile, mock_schema, {}) + + assert isinstance(result, SchemaResult) + + @mock.patch.object( + ConnRecord, + "retrieve_by_id", + return_value=mock.CoroutineMock( + metadata_get=mock.CoroutineMock(return_value={"endorser_did": "test_did"}) + ), + ) + async def test_register_schema_with_author_role(self, mock_endorser_conn_record): + self.profile.inject_or = mock.MagicMock( + return_value=mock.CoroutineMock( + send_schema_anoncreds=mock.CoroutineMock( + return_value=( + "test_schema_id", + { + "signed_txn": "test_signed_txn", + }, + ) + ) + ) + ) + self.profile.settings.set_value("endorser.author", True) + + result = await self.registry.register_schema( + self.profile, mock_schema, {"endorser_connection_id": "test_connection_id"} + ) + + assert result.registration_metadata["txn"] is not None + assert mock_endorser_conn_record.called + + @mock.patch.object( + ConnRecord, + "retrieve_by_id", + return_value=mock.CoroutineMock( + metadata_get=mock.CoroutineMock(return_value={"endorser_did": "test_did"}) + ), + ) + async def test_register_schema_already_exists(self, mock_endorser_conn_record): + self.profile.inject_or = mock.MagicMock( + return_value=mock.CoroutineMock( + send_schema_anoncreds=mock.CoroutineMock( + side_effect=LedgerObjectAlreadyExistsError( + "test", "test", mock_schema + ) + ) + ) + ) + self.profile.settings.set_value("endorser.author", True) + + with self.assertRaises(AnonCredsSchemaAlreadyExists): + await self.registry.register_schema( + self.profile, + mock_schema, + {"endorser_connection_id": "test_connection_id"}, + ) + + @mock.patch.object( + ConnRecord, + "retrieve_by_id", + return_value=mock.CoroutineMock( + metadata_get=mock.CoroutineMock(return_value={"endorser_did": "test_did"}) + ), + ) + async def test_register_schema_with_create_trasaction_param( + self, mock_endorser_conn_record + ): + self.profile.inject_or = mock.MagicMock( + return_value=mock.CoroutineMock( + send_schema_anoncreds=mock.CoroutineMock( + return_value=( + "test_schema_id", + { + "signed_txn": "test_signed_txn", + }, + ) + ) + ) + ) + + result = await self.registry.register_schema( + self.profile, + mock_schema, + { + "endorser_connection_id": "test_connection_id", + "create_transaction_for_endorser": True, + }, + ) + + assert result.registration_metadata["txn"] is not None + assert mock_endorser_conn_record.called + + @mock.patch.object( + ConnRecord, + "retrieve_by_id", + return_value=mock.CoroutineMock( + metadata_get=mock.CoroutineMock(return_value={"endorser_did": "test_did"}) + ), + ) + @mock.patch.object( + TransactionManager, + "create_request", + ) + async def test_register_schema_with_author_role_and_create_request( + self, mock_create_request, mock_endorser_conn_record + ): + class MockTransaction: + def __init__(self, txn): + self.txn = txn + + def serialize(self): + return json.dumps(self.txn) + + self.profile.inject_or = mock.MagicMock( + return_value=mock.CoroutineMock( + send_schema_anoncreds=mock.CoroutineMock( + return_value=( + "test_schema_id", + { + "signed_txn": "test_signed_txn", + }, + ) + ) + ) ) + + mock_create_request.return_value = ( + MockTransaction({"test": "test"}), + "transaction_request", + ) + + self.profile.settings.set_value("endorser.author", True) + self.profile.settings.set_value("endorser.auto_request", True) + self.profile.context.injector.bind_instance( + BaseResponder, mock.MagicMock(send=mock.CoroutineMock(return_value=None)) + ) + + result = await self.registry.register_schema( + self.profile, mock_schema, {"endorser_connection_id": "test_connection_id"} + ) + + assert result.registration_metadata["txn"] is not None + assert mock_create_request.called + assert self.profile.context.injector.get_provider( + BaseResponder + )._instance.send.called + + async def test_txn_submit(self): + self.profile.inject = mock.MagicMock( + side_effect=[ + None, + mock.CoroutineMock( + txn_submit=mock.CoroutineMock(side_effect=LedgerError("test error")) + ), + mock.CoroutineMock( + txn_submit=mock.CoroutineMock(return_value="transaction response") + ), + ] + ) + + # No ledger + with self.assertRaises(LedgerError): + await self.registry.txn_submit(self.profile, "test_txn") + # Write error + with self.assertRaises(AnonCredsRegistrationError): + await self.registry.txn_submit(self.profile, "test_txn") + + result = await self.registry.txn_submit(self.profile, "test_txn") + assert result == "transaction response" diff --git a/aries_cloudagent/anoncreds/events.py b/aries_cloudagent/anoncreds/events.py index 252307dae9..75d3c4e5b0 100644 --- a/aries_cloudagent/anoncreds/events.py +++ b/aries_cloudagent/anoncreds/events.py @@ -6,16 +6,15 @@ from ..core.event_bus import Event from .models.anoncreds_revocation import RevRegDef - CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished" REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished" REV_LIST_FINISHED_EVENT = "anoncreds::revocation-list::finished" +SCHEMA_REGISTRATION_FINISHED_EVENT = "anoncreds::schema::registration::finished" -CRED_DEF_FINISHED_PATTERN = re.compile("anoncreds::credential-definition::finished") -REV_REG_DEF_FINISHED_PATTERN = re.compile( - "anoncreds::revocation-registry-definition::finished" -) -REV_LIST_FINISHED_PATTERN = re.compile("anoncreds::revocation-list::finished") +CRED_DEF_FINISHED_PATTERN = re.compile(CRED_DEF_FINISHED_EVENT) +REV_REG_DEF_FINISHED_PATTERN = re.compile(REV_REG_DEF_FINISHED_EVENT) +REV_LIST_FINISHED_PATTERN = re.compile(REV_LIST_FINISHED_EVENT) +SCHEMA_REGISTRATION_FINISHED_PATTERN = re.compile(SCHEMA_REGISTRATION_FINISHED_EVENT) class CredDefFinishedPayload(NamedTuple): @@ -128,3 +127,34 @@ def __init__(self, payload: RevListFinishedPayload): def payload(self) -> RevListFinishedPayload: """Return payload.""" return self._payload + + +class SchemaRegistrationFinishedPayload(NamedTuple): + """Payload of schema transaction event.""" + + meta_data: dict + + +class SchemaRegistrationFinishedEvent(Event): + """Event for schema post-process.""" + + def __init__(self, payload: SchemaRegistrationFinishedPayload): + """Initialize an instance. + + Args: + schema_id: schema id + meta_data: meta data + """ + self._topic = SCHEMA_REGISTRATION_FINISHED_EVENT + self._payload = payload + + @classmethod + def with_payload(cls, meta_data: dict): + """With payload.""" + payload = SchemaRegistrationFinishedPayload(meta_data) + return cls(payload) + + @property + def payload(self) -> SchemaRegistrationFinishedPayload: + """Return payload.""" + return self._payload diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index d7c07c3a18..ad49e11ce9 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -130,20 +130,20 @@ async def _finish_registration( await txn.handle.remove(category, job_id) return entry - async def _store_schema( + async def store_schema( self, result: SchemaResult, ): """Store schema after reaching finished state.""" - ident = result.schema_state.schema_id or result.job_id - if not ident: + identifier = result.job_id or result.schema_state.schema_id + if not identifier: raise ValueError("Schema id or job id must be set") try: async with self.profile.session() as session: await session.handle.insert( CATEGORY_SCHEMA, - ident, + identifier, result.schema_state.schema.to_json(), { "name": result.schema_state.schema.name, @@ -203,15 +203,14 @@ async def create_and_register_schema( options, ) - await self._store_schema(schema_result) - + await self.store_schema(schema_result) return schema_result except AnonCredsSchemaAlreadyExists as err: # If we find that we've previously written a schema that looks like # this one before but that schema is not in our wallet, add it to # the wallet so we can return from our get schema calls - await self._store_schema( + await self.store_schema( SchemaResult( job_id=None, schema_state=SchemaState( diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 9528eb5ab7..b193e75460 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -6,13 +6,12 @@ from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf -from aries_cloudagent.messaging.valid import ( +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( INDY_OR_KEY_DID_EXAMPLE, INDY_SCHEMA_ID_EXAMPLE, ) -from ...messaging.models.base import BaseModel, BaseModelSchema - class AnonCredsSchema(BaseModel): """An AnonCreds Schema object.""" @@ -144,6 +143,7 @@ class SchemaState(BaseModel): STATE_FAILED = "failed" STATE_ACTION = "action" STATE_WAIT = "wait" + STATE_TRANSACTION_REQUESTED = "transaction_requested" class Meta: """SchemaState metadata.""" diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 920b465741..38535e1bfe 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -3,8 +3,14 @@ import logging from typing import List, Optional, Sequence - from ..core.profile import Profile +from .base import ( + AnonCredsRegistrationError, + AnonCredsResolutionError, + BaseAnonCredsHandler, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) from .models.anoncreds_cred_def import ( CredDef, CredDefResult, @@ -13,19 +19,12 @@ from .models.anoncreds_revocation import ( GetRevListResult, GetRevRegDefResult, - RevRegDef, - RevRegDefResult, RevList, RevListResult, + RevRegDef, + RevRegDefResult, ) from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult -from .base import ( - AnonCredsRegistrationError, - AnonCredsResolutionError, - BaseAnonCredsHandler, - BaseAnonCredsRegistrar, - BaseAnonCredsResolver, -) LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 23f82eb458..bc4ca28ced 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -13,11 +13,11 @@ ) from marshmallow import fields -from aries_cloudagent.ledger.error import LedgerError - from ..admin.request_context import AdminRequestContext from ..askar.profile import AskarProfile -from ..core.event_bus import EventBus +from ..core.event_bus import Event, EventBus +from ..core.profile import Profile +from ..ledger.error import LedgerError from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import ( INDY_CRED_DEF_ID_EXAMPLE, @@ -41,6 +41,7 @@ AnonCredsRegistrationError, AnonCredsResolutionError, ) +from .events import SCHEMA_REGISTRATION_FINISHED_PATTERN from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema @@ -126,11 +127,24 @@ class SchemaPostOptionSchema(OpenAPISchema): """Parameters and validators for schema options.""" endorser_connection_id = fields.Str( - description="Connection identifier (optional) (this is an example)", + description=""" + Connection identifier (optional) (this is an example) + You can set this is you know the endorsers connection id you want to use. + If not specified then the agent will attempt to find an endorser connection. + """, required=False, example=UUIDFour.EXAMPLE, ) + create_transaction_for_endorser = fields.Bool( + description=""" + Create transaction for endorser (optional, default false). + Use this for agents who don't specify an author role but want to + create a transaction for an endorser to sign.""", + required=False, + example=False, + ) + class SchemaPostRequestSchema(OpenAPISchema): """Parameters and validators for query string in create schema.""" @@ -180,7 +194,7 @@ async def schemas_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] body = await request.json() - options = body.get("options") + options = body.get("options", {}) schema_data = body.get("schema") if schema_data is None: @@ -194,7 +208,11 @@ async def schemas_post(request: web.BaseRequest): issuer = AnonCredsIssuer(context.profile) try: result = await issuer.create_and_register_schema( - issuer_id, name, version, attr_names, options=options + issuer_id, + name, + version, + attr_names, + options, ) return web.json_response(result.serialize()) except (AnonCredsIssuerError, AnonCredsRegistrationError) as e: @@ -644,6 +662,22 @@ async def publish_revocations(request: web.BaseRequest): raise web.HTTPBadRequest(reason=err.roll_up) from err +def register_events(event_bus: EventBus): + """Register events.""" + # TODO Make this pluggable? + setup_manager = DefaultRevocationSetup() + setup_manager.register_events(event_bus) + event_bus.subscribe(SCHEMA_REGISTRATION_FINISHED_PATTERN, on_schema_event) + + +async def on_schema_event(profile: Profile, event: Event): + """Schema post processing.""" + await AnonCredsIssuer(profile).finish_schema( + event.payload.meta_data["context"]["job_id"], + event.payload.meta_data["context"]["schema_id"], + ) + + async def register(app: web.Application): """Register routes.""" @@ -673,13 +707,6 @@ async def register(app: web.Application): ) -def register_events(event_bus: EventBus): - """Register events.""" - # TODO Make this pluggable? - setup_manager = DefaultRevocationSetup() - setup_manager.register_events(event_bus) - - def post_process_routes(app: web.Application): """Amend swagger API.""" diff --git a/aries_cloudagent/anoncreds/tests/test_issuer.py b/aries_cloudagent/anoncreds/tests/test_issuer.py index 78b2656fb8..5af4f02c61 100644 --- a/aries_cloudagent/anoncreds/tests/test_issuer.py +++ b/aries_cloudagent/anoncreds/tests/test_issuer.py @@ -297,7 +297,7 @@ async def test_create_and_register_schema_fail_insert(self, mock_session_handle) mock_session_handle.insert.assert_called_once() @mock.patch.object(InMemoryProfileSession, "handle") - async def test_create_and_register_already_exists_but_not_in_wallet( + async def test_create_and_register_schema_already_exists_but_not_in_wallet( self, mock_session_handle ): mock_session_handle.fetch_all = mock.CoroutineMock(return_value=[]) @@ -363,6 +363,42 @@ async def test_create_and_register_schema_without_job_id_or_schema_id_raises_x( attr_names=["attr1", "attr2"], ) + @mock.patch.object(InMemoryProfileSession, "handle") + @mock.patch.object(test_module.AnonCredsIssuer, "store_schema") + async def test_create_and_register_schema_with_endorsed_transaction_response_does_not_store_schema( + self, mock_store_schema, mock_session_handle + ): + mock_session_handle.fetch_all = mock.CoroutineMock(return_value=[]) + mock_session_handle.insert = mock.CoroutineMock(return_value=None) + self.profile.inject = mock.Mock( + return_value=mock.MagicMock( + register_schema=mock.CoroutineMock( + return_value=SchemaResult( + job_id="job-id", + schema_state=SchemaState( + state="finished", + schema_id="schema-id", + schema=AnonCredsSchema( + issuer_id="issuer-id", + name="schema-name", + version="1.0", + attr_names=["attr1", "attr2"], + ), + ), + ) + ) + ) + ) + result = await self.issuer.create_and_register_schema( + issuer_id="did:sov:3avoBCqDMFHFaKUHug9s8W", + name="example name", + version="1.0", + attr_names=["attr1", "attr2"], + ) + + assert isinstance(result, SchemaResult) + assert mock_store_schema.called + async def test_finish_schema(self): self.profile.transaction = mock.Mock( return_value=mock.MagicMock( diff --git a/aries_cloudagent/anoncreds/tests/test_routes.py b/aries_cloudagent/anoncreds/tests/test_routes.py index e539a93e73..abbd9000d7 100644 --- a/aries_cloudagent/anoncreds/tests/test_routes.py +++ b/aries_cloudagent/anoncreds/tests/test_routes.py @@ -6,11 +6,21 @@ from aries_cloudagent.admin.request_context import AdminRequestContext from aries_cloudagent.anoncreds.base import AnonCredsObjectNotFound +from aries_cloudagent.anoncreds.events import SchemaRegistrationFinishedEvent from aries_cloudagent.anoncreds.issuer import AnonCredsIssuer +from aries_cloudagent.anoncreds.models.anoncreds_schema import ( + AnonCredsSchema, + SchemaResult, + SchemaState, +) from aries_cloudagent.anoncreds.revocation import AnonCredsRevocation from aries_cloudagent.anoncreds.revocation_setup import DefaultRevocationSetup from aries_cloudagent.askar.profile_anon import AskarAnoncredsProfile -from aries_cloudagent.core.in_memory.profile import InMemoryProfile +from aries_cloudagent.core.event_bus import MockEventBus +from aries_cloudagent.core.in_memory.profile import ( + InMemoryProfile, + InMemoryProfileSession, +) from aries_cloudagent.revocation_anoncreds.manager import RevocationManager from aries_cloudagent.tests import mock @@ -64,7 +74,19 @@ async def setUp(self) -> None: @mock.patch.object( AnonCredsIssuer, "create_and_register_schema", - return_value=MockSchema("schemaId"), + return_value=SchemaResult( + job_id=None, + schema_state=SchemaState( + state="finished", + schema_id=None, + schema=AnonCredsSchema( + issuer_id="issuer-id", + name="name", + version="1.0", + attr_names=["attr1", "attr2"], + ), + ), + ), ) async def test_schemas_post(self, mock_create_and_register_schema): self.request.json = mock.CoroutineMock( @@ -82,7 +104,7 @@ async def test_schemas_post(self, mock_create_and_register_schema): ] ) result = await test_module.schemas_post(self.request) - assert json.loads(result.body)["schema_id"] == "schemaId" + assert result is not None assert mock_create_and_register_schema.call_count == 1 @@ -358,9 +380,12 @@ async def test_publish_revocations(self, mock_publish): assert mock_publish.call_count == 1 @mock.patch.object(DefaultRevocationSetup, "register_events") - async def test_register_events(self, mock_manager): - test_module.register_events("event_bus") - mock_manager.assert_called_once_with("event_bus") + async def test_register_events(self, mock_revocation_setup_listeners): + mock_event_bus = MockEventBus() + mock_event_bus.subscribe = mock.MagicMock() + test_module.register_events(mock_event_bus) + assert mock_revocation_setup_listeners.call_count == 1 + assert mock_event_bus.subscribe.call_count == 1 async def test_register(self): mock_app = mock.MagicMock() @@ -373,3 +398,22 @@ async def test_post_process_routes(self): mock_app = mock.MagicMock(_state={"swagger_dict": {}}) test_module.post_process_routes(mock_app) assert "tags" in mock_app._state["swagger_dict"] + + @mock.patch.object( + InMemoryProfileSession, + "handle", + ) + @mock.patch.object(AnonCredsIssuer, "finish_schema") + async def test_on_schema_event_valid_payload(self, mock_finish, mock_handle): + mock_handle.insert = mock.CoroutineMock(return_value=None) + test_event = SchemaRegistrationFinishedEvent.with_payload( + { + "context": { + "job_id": "test_job_id", + "schema_id": "test_schema_id", + } + } + ) + + await test_module.on_schema_event(self.profile, test_event) + assert mock_finish.call_count == 1 diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index e2ab4c3d4a..2943f03bf2 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -3,8 +3,7 @@ import json import logging import re - -from abc import ABC, abstractmethod, ABCMeta +from abc import ABC, ABCMeta, abstractmethod from enum import Enum from hashlib import sha256 from typing import List, Sequence, Tuple, Union @@ -13,16 +12,14 @@ from ..messaging.valid import IndyDID from ..utils import sentinel from ..wallet.did_info import DIDInfo - +from .endpoint_type import EndpointType from .error import ( BadLedgerRequestError, LedgerError, - LedgerTransactionError, LedgerObjectAlreadyExistsError, + LedgerTransactionError, ) -from .endpoint_type import EndpointType - LOGGER = logging.getLogger(__name__) @@ -594,6 +591,9 @@ async def send_schema_anoncreds( attribute_names: A list of schema attributes """ + from aries_cloudagent.anoncreds.default.legacy_indy.registry import ( + LegacyIndyRegistry, + ) public_info = await self.get_wallet_public_did() if not public_info: @@ -631,16 +631,17 @@ async def send_schema_anoncreds( ) try: - resp = await self.txn_submit( + legacy_indy_registry = LegacyIndyRegistry() + resp = await legacy_indy_registry.txn_submit( + self.profile, schema_req, sign=True, sign_did=public_info, write_ledger=write_ledger, ) - # TODO Clean this up - # if not write_ledger: - # return schema_id, {"signed_txn": resp} + if not write_ledger: + return schema_id, {"signed_txn": resp} try: # parse sequence number out of response diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index 18f5664010..bf526ec77b 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -3,11 +3,12 @@ import json import logging import uuid - from asyncio import shield +from ....anoncreds.events import SchemaRegistrationFinishedEvent from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError +from ....core.event_bus import EventBus from ....core.profile import Profile from ....indy.issuer import IndyIssuerError from ....ledger.base import BaseLedger @@ -15,17 +16,16 @@ from ....messaging.credential_definitions.util import notify_cred_def_event from ....messaging.schemas.util import notify_schema_event from ....revocation.util import ( - notify_revocation_reg_endorsed_event, notify_revocation_entry_endorsed_event, + notify_revocation_reg_endorsed_event, ) from ....storage.error import StorageError, StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet from ....wallet.util import ( - notify_endorse_did_event, notify_endorse_did_attrib_event, + notify_endorse_did_event, ) - from .messages.cancel_transaction import CancelTransaction from .messages.endorsed_transaction_response import EndorsedTransactionResponse from .messages.refused_transaction_response import RefusedTransactionResponse @@ -415,22 +415,32 @@ async def complete_transaction( if (not endorser) and ( txn_goal_code != TransactionRecord.WRITE_DID_TRANSACTION ): - ledger = self._profile.inject(BaseLedger) - if not ledger: - reason = "No ledger available" - if not self._profile.context.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" - raise TransactionManagerError(reason) + if ( + self._profile.context.settings.get_value("wallet.type") + == "askar-anoncreds" + ): + from aries_cloudagent.anoncreds.default.legacy_indy.registry import ( + LegacyIndyRegistry, + ) - async with ledger: - try: - ledger_response_json = await shield( - ledger.txn_submit( - ledger_transaction, sign=False, taa_accept=False + legacy_indy_registry = LegacyIndyRegistry() + ledger_response_json = await legacy_indy_registry.txn_submit( + self._profile, ledger_transaction, sign=False, taa_accept=False + ) + else: + ledger = self._profile.inject(BaseLedger) + if not ledger: + raise TransactionManagerError("No ledger available") + + async with ledger: + try: + ledger_response_json = await shield( + ledger.txn_submit( + ledger_transaction, sign=False, taa_accept=False + ) ) - ) - except (IndyIssuerError, LedgerError) as err: - raise TransactionManagerError(err.roll_up) from err + except (IndyIssuerError, LedgerError) as err: + raise TransactionManagerError(err.roll_up) from err ledger_response = json.loads(ledger_response_json) @@ -797,7 +807,14 @@ async def endorsed_txn_post_processing( meta_data["context"]["public_did"] = public_did # Notify schema ledger write event - await notify_schema_event(self._profile, schema_id, meta_data) + if self._profile.settings.get("wallet.type") == "askar-anoncreds": + event_bus = self.profile.inject(EventBus) + await event_bus.notify( + self._profile, + SchemaRegistrationFinishedEvent.with_payload(meta_data), + ) + else: + await notify_schema_event(self._profile, schema_id, meta_data) elif ledger_response["result"]["txn"]["type"] == "102": # cred def transaction diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py index 82902dfe93..0b326dd79b 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py @@ -1,16 +1,17 @@ import asyncio import json import uuid - from unittest import IsolatedAsyncioTestCase -from aries_cloudagent.tests import mock from .....admin.request_context import AdminRequestContext +from .....anoncreds.default.legacy_indy.registry import LegacyIndyRegistry from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache from .....connections.models.conn_record import ConnRecord +from .....core.event_bus import EventBus from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError +from .....tests import mock from .....wallet.base import BaseWallet from .....wallet.did_method import SOV, DIDMethods from .....wallet.key_type import ED25519 @@ -497,6 +498,62 @@ async def test_complete_transaction(self): assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ACKED + @mock.patch.object( + LegacyIndyRegistry, + "txn_submit", + return_value=json.dumps( + { + "result": { + "txn": {"type": "101", "metadata": {"from": TEST_DID}}, + "txnMetadata": {"txnId": SCHEMA_ID}, + } + } + ), + ) + async def test_complete_transaction_anoncreds(self, mock_txn_submit): + self.profile.settings.set_value("wallet.type", "askar-anoncreds") + + transaction_record = await self.manager.create_record( + messages_attach=self.test_messages_attach, + connection_id=self.test_connection_id, + ) + future = asyncio.Future() + future.set_result( + mock.MagicMock(return_value=mock.MagicMock(add_record=mock.CoroutineMock())) + ) + self.ledger.get_indy_storage = future + self.profile.context.injector.bind_instance( + EventBus, mock.MagicMock(notify=mock.CoroutineMock()) + ) + + with mock.patch.object( + TransactionRecord, "save", autospec=True + ) as save_record, mock.patch.object( + ConnRecord, "retrieve_by_id" + ) as mock_conn_rec_retrieve: + mock_conn_rec_retrieve.return_value = mock.MagicMock( + metadata_get=mock.CoroutineMock( + return_value={ + "transaction_their_job": ( + TransactionJob.TRANSACTION_ENDORSER.name + ), + "transaction_my_job": (TransactionJob.TRANSACTION_AUTHOR.name), + } + ) + ) + + ( + transaction_record, + transaction_acknowledgement_message, + ) = await self.manager.complete_transaction(transaction_record, False) + save_record.assert_called_once() + + assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ACKED + assert mock_txn_submit.called + assert self.profile.context.injector.get_provider( + EventBus + )._instance.notify.called + async def test_create_refuse_response_bad_state(self): transaction_record = await self.manager.create_record( messages_attach=self.test_messages_attach, diff --git a/demo/features/0586-sign-transaction.feature b/demo/features/0586-sign-transaction.feature index 6b50f24867..2c4675aa74 100644 --- a/demo/features/0586-sign-transaction.feature +++ b/demo/features/0586-sign-transaction.feature @@ -28,6 +28,13 @@ Feature: RFC 0586 Aries sign (endorse) transactions functions | --multitenant --multi-ledger | --multitenant --multi-ledger | driverslicense | | --multitenant --multi-ledger --revocation | --multitenant --multi-ledger --revocation | driverslicense | + @WalletType_Askar_AnonCreds + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | + | --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | anoncreds-testing | + | --wallet-type askar-anoncreds | | driverslicense | + | | --wallet-type askar-anoncreds | anoncreds-testing | + @T001.1-RFC0586 @GHA Scenario Outline: endorse a transaction and write to the ledger diff --git a/demo/features/steps/0586-sign-transaction.py b/demo/features/steps/0586-sign-transaction.py index 204eca0cfa..9020d71e11 100644 --- a/demo/features/steps/0586-sign-transaction.py +++ b/demo/features/steps/0586-sign-transaction.py @@ -1,5 +1,4 @@ import time -from time import sleep from bdd_support.agent_backchannel_client import ( agent_container_GET, @@ -7,15 +6,14 @@ agent_container_POST, agent_container_PUT, agent_container_register_did, + aries_container_fetch_cred_def, + aries_container_fetch_cred_defs, + aries_container_fetch_schemas, async_sleep, read_json_data, read_schema_data, - aries_container_fetch_schemas, - aries_container_fetch_cred_defs, - aries_container_fetch_cred_def, ) from behave import given, then, when -from runners.agent_container import AgentContainer # This step is defined in another feature file # Given "Acme" and "Bob" have an existing connection @@ -50,7 +48,7 @@ def step_impl(context, agent_name, did_role): # assume everything works! async_sleep(3.0) - if not "public_dids" in context: + if "public_dids" not in context: context.public_dids = {} context.public_dids[did_role] = created_did["result"]["did"] @@ -101,12 +99,25 @@ def step_impl(context, agent_name, schema_name): schema_info = read_schema_data(schema_name) connection_id = agent["agent"].agent.connection_id - created_txn = agent_container_POST( - agent["agent"], - "/schemas", - data=schema_info["schema"], - params={"conn_id": connection_id, "create_transaction_for_endorser": "true"}, - ) + if agent["agent"].wallet_type != "askar-anoncreds": + created_txn = agent_container_POST( + agent["agent"], + "/schemas", + data=schema_info["schema"], + params={ + "conn_id": connection_id, + "create_transaction_for_endorser": "true", + }, + ) + else: + schema_info["schema"]["issuerId"] = context.public_dids["AUTHOR"] + schema_info["options"]["create_transaction_for_endorser"] = True + schema_info["options"]["endorser_connection_id"] = connection_id + created_txn = agent_container_POST( + agent["agent"], + "/anoncreds/schema", + data=schema_info, + ) # assert goodness if agent["agent"].endorser_role and agent["agent"].endorser_role == "author": @@ -114,7 +125,7 @@ def step_impl(context, agent_name, schema_name): else: assert created_txn["txn"]["state"] == "transaction_created" - if not "txn_ids" in context: + if "txn_ids" not in context: context.txn_ids = {} context.txn_ids["AUTHOR"] = created_txn["txn"]["transaction_id"] @@ -188,8 +199,6 @@ def step_impl(context, agent_name): def step_impl(context, agent_name, schema_name): agent = context.active_agents[agent_name] - schema_info = read_schema_data(schema_name) - schemas = {"schema_ids": []} i = 5 while 0 == len(schemas["schema_ids"]) and i > 0: @@ -199,7 +208,10 @@ def step_impl(context, agent_name, schema_name): assert len(schemas["schema_ids"]) == 1 schema_id = schemas["schema_ids"][0] - schema = agent_container_GET(agent["agent"], "/schemas/" + schema_id) + if agent["agent"].wallet_type != "askar-anoncreds": + agent_container_GET(agent["agent"], "/schemas/" + schema_id) + else: + agent_container_GET(agent["agent"], "/anoncreds/schema/" + schema_id) context.schema_name = schema_name @@ -235,7 +247,7 @@ def step_impl(context, agent_name, schema_name): assert created_txn["txn"]["state"] == "request_sent" else: assert created_txn["txn"]["state"] == "transaction_created" - if not "txn_ids" in context: + if "txn_ids" not in context: context.txn_ids = {} context.txn_ids["AUTHOR"] = created_txn["txn"]["transaction_id"] @@ -312,7 +324,7 @@ def step_impl(context, agent_name, schema_name): }, ) assert created_txn["txn"]["state"] == "transaction_created" - if not "txn_ids" in context: + if "txn_ids" not in context: context.txn_ids = {} context.txn_ids["AUTHOR"] = created_txn["txn"]["transaction_id"] @@ -419,7 +431,7 @@ def step_impl(context, agent_name, schema_name): }, ) assert created_txn["txn"]["state"] == "transaction_created" - if not "txn_ids" in context: + if "txn_ids" not in context: context.txn_ids = {} context.txn_ids["AUTHOR"] = created_txn["txn"]["transaction_id"] @@ -519,7 +531,7 @@ def step_impl(context, agent_name): # create rev_reg entry transaction created_rev_reg = agent_container_POST( agent["agent"], - f"/revocation/publish-revocations", + "/revocation/publish-revocations", data={ "rrid2crid": { context.cred_exchange["indy"]["rev_reg_id"]: [ @@ -547,7 +559,7 @@ def step_impl(context, agent_name): # create rev_reg entry transaction created_rev_reg = agent_container_POST( agent["agent"], - f"/revocation/publish-revocations", + "/revocation/publish-revocations", data={ "rrid2crid": { context.cred_exchange["indy"]["rev_reg_id"]: [ From 74b3830c6be96515e9a98c82e8d5e3b17be60115 Mon Sep 17 00:00:00 2001 From: jamshale Date: Tue, 30 Jan 2024 22:24:09 +0000 Subject: [PATCH 2/3] Use wait state / finish schem directly with anoncreds issuer Signed-off-by: jamshale --- .../anoncreds/default/legacy_indy/registry.py | 2 +- aries_cloudagent/anoncreds/events.py | 33 ------------------- .../anoncreds/models/anoncreds_schema.py | 1 - aries_cloudagent/anoncreds/routes.py | 13 +------- .../endorse_transaction/v1_0/manager.py | 12 +++---- .../v1_0/tests/test_manager.py | 22 ++++++++----- 6 files changed, 20 insertions(+), 63 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 9c9bfaa170..656a774c1e 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -298,7 +298,7 @@ async def register_schema( return SchemaResult( job_id=job_id, schema_state=SchemaState( - state=SchemaState.STATE_TRANSACTION_REQUESTED, + state=SchemaState.STATE_WAIT, schema_id=schema_id, schema=schema, ), diff --git a/aries_cloudagent/anoncreds/events.py b/aries_cloudagent/anoncreds/events.py index 75d3c4e5b0..23d0cf3792 100644 --- a/aries_cloudagent/anoncreds/events.py +++ b/aries_cloudagent/anoncreds/events.py @@ -9,12 +9,10 @@ CRED_DEF_FINISHED_EVENT = "anoncreds::credential-definition::finished" REV_REG_DEF_FINISHED_EVENT = "anoncreds::revocation-registry-definition::finished" REV_LIST_FINISHED_EVENT = "anoncreds::revocation-list::finished" -SCHEMA_REGISTRATION_FINISHED_EVENT = "anoncreds::schema::registration::finished" CRED_DEF_FINISHED_PATTERN = re.compile(CRED_DEF_FINISHED_EVENT) REV_REG_DEF_FINISHED_PATTERN = re.compile(REV_REG_DEF_FINISHED_EVENT) REV_LIST_FINISHED_PATTERN = re.compile(REV_LIST_FINISHED_EVENT) -SCHEMA_REGISTRATION_FINISHED_PATTERN = re.compile(SCHEMA_REGISTRATION_FINISHED_EVENT) class CredDefFinishedPayload(NamedTuple): @@ -127,34 +125,3 @@ def __init__(self, payload: RevListFinishedPayload): def payload(self) -> RevListFinishedPayload: """Return payload.""" return self._payload - - -class SchemaRegistrationFinishedPayload(NamedTuple): - """Payload of schema transaction event.""" - - meta_data: dict - - -class SchemaRegistrationFinishedEvent(Event): - """Event for schema post-process.""" - - def __init__(self, payload: SchemaRegistrationFinishedPayload): - """Initialize an instance. - - Args: - schema_id: schema id - meta_data: meta data - """ - self._topic = SCHEMA_REGISTRATION_FINISHED_EVENT - self._payload = payload - - @classmethod - def with_payload(cls, meta_data: dict): - """With payload.""" - payload = SchemaRegistrationFinishedPayload(meta_data) - return cls(payload) - - @property - def payload(self) -> SchemaRegistrationFinishedPayload: - """Return payload.""" - return self._payload diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index b193e75460..a6b5c83d4e 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -143,7 +143,6 @@ class SchemaState(BaseModel): STATE_FAILED = "failed" STATE_ACTION = "action" STATE_WAIT = "wait" - STATE_TRANSACTION_REQUESTED = "transaction_requested" class Meta: """SchemaState metadata.""" diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index bc4ca28ced..7ca575e50c 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -15,8 +15,7 @@ from ..admin.request_context import AdminRequestContext from ..askar.profile import AskarProfile -from ..core.event_bus import Event, EventBus -from ..core.profile import Profile +from ..core.event_bus import EventBus from ..ledger.error import LedgerError from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import ( @@ -41,7 +40,6 @@ AnonCredsRegistrationError, AnonCredsResolutionError, ) -from .events import SCHEMA_REGISTRATION_FINISHED_PATTERN from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema @@ -667,15 +665,6 @@ def register_events(event_bus: EventBus): # TODO Make this pluggable? setup_manager = DefaultRevocationSetup() setup_manager.register_events(event_bus) - event_bus.subscribe(SCHEMA_REGISTRATION_FINISHED_PATTERN, on_schema_event) - - -async def on_schema_event(profile: Profile, event: Event): - """Schema post processing.""" - await AnonCredsIssuer(profile).finish_schema( - event.payload.meta_data["context"]["job_id"], - event.payload.meta_data["context"]["schema_id"], - ) async def register(app: web.Application): diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index bf526ec77b..51c838e0fc 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -5,10 +5,9 @@ import uuid from asyncio import shield -from ....anoncreds.events import SchemaRegistrationFinishedEvent +from ....anoncreds.issuer import AnonCredsIssuer from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError -from ....core.event_bus import EventBus from ....core.profile import Profile from ....indy.issuer import IndyIssuerError from ....ledger.base import BaseLedger @@ -428,7 +427,7 @@ async def complete_transaction( self._profile, ledger_transaction, sign=False, taa_accept=False ) else: - ledger = self._profile.inject(BaseLedger) + ledger = self.profile.inject(BaseLedger) if not ledger: raise TransactionManagerError("No ledger available") @@ -808,10 +807,9 @@ async def endorsed_txn_post_processing( # Notify schema ledger write event if self._profile.settings.get("wallet.type") == "askar-anoncreds": - event_bus = self.profile.inject(EventBus) - await event_bus.notify( - self._profile, - SchemaRegistrationFinishedEvent.with_payload(meta_data), + await AnonCredsIssuer(self._profile).finish_schema( + meta_data["context"]["job_id"], + meta_data["context"]["schema_id"], ) else: await notify_schema_event(self._profile, schema_id, meta_data) diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py index 0b326dd79b..62c4d2e3f8 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py @@ -5,10 +5,10 @@ from .....admin.request_context import AdminRequestContext from .....anoncreds.default.legacy_indy.registry import LegacyIndyRegistry +from .....anoncreds.issuer import AnonCredsIssuer from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache from .....connections.models.conn_record import ConnRecord -from .....core.event_bus import EventBus from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....tests import mock @@ -506,25 +506,31 @@ async def test_complete_transaction(self): "result": { "txn": {"type": "101", "metadata": {"from": TEST_DID}}, "txnMetadata": {"txnId": SCHEMA_ID}, - } + }, } ), ) - async def test_complete_transaction_anoncreds(self, mock_txn_submit): + @mock.patch.object(AnonCredsIssuer, "finish_schema") + async def test_complete_transaction_anoncreds( + self, mock_finish_schema, mock_txn_submit + ): self.profile.settings.set_value("wallet.type", "askar-anoncreds") transaction_record = await self.manager.create_record( messages_attach=self.test_messages_attach, connection_id=self.test_connection_id, + meta_data={ + "context": { + "job_id": "217544da8ab14b12b18eccd11f07d269", + "schema_id": "FB5yHWKaZk59hiKqjJKEHs:2:author-schema:3.3", + } + }, ) future = asyncio.Future() future.set_result( mock.MagicMock(return_value=mock.MagicMock(add_record=mock.CoroutineMock())) ) self.ledger.get_indy_storage = future - self.profile.context.injector.bind_instance( - EventBus, mock.MagicMock(notify=mock.CoroutineMock()) - ) with mock.patch.object( TransactionRecord, "save", autospec=True @@ -550,9 +556,7 @@ async def test_complete_transaction_anoncreds(self, mock_txn_submit): assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ACKED assert mock_txn_submit.called - assert self.profile.context.injector.get_provider( - EventBus - )._instance.notify.called + assert mock_finish_schema.called async def test_create_refuse_response_bad_state(self): transaction_record = await self.manager.create_record( From 3a16212ca215dffb8341b6b0bf48ab003395df97 Mon Sep 17 00:00:00 2001 From: jamshale Date: Tue, 30 Jan 2024 22:31:16 +0000 Subject: [PATCH 3/3] Fix test Signed-off-by: jamshale --- .../anoncreds/tests/test_routes.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/aries_cloudagent/anoncreds/tests/test_routes.py b/aries_cloudagent/anoncreds/tests/test_routes.py index abbd9000d7..bd34d06b9d 100644 --- a/aries_cloudagent/anoncreds/tests/test_routes.py +++ b/aries_cloudagent/anoncreds/tests/test_routes.py @@ -6,7 +6,6 @@ from aries_cloudagent.admin.request_context import AdminRequestContext from aries_cloudagent.anoncreds.base import AnonCredsObjectNotFound -from aries_cloudagent.anoncreds.events import SchemaRegistrationFinishedEvent from aries_cloudagent.anoncreds.issuer import AnonCredsIssuer from aries_cloudagent.anoncreds.models.anoncreds_schema import ( AnonCredsSchema, @@ -19,7 +18,6 @@ from aries_cloudagent.core.event_bus import MockEventBus from aries_cloudagent.core.in_memory.profile import ( InMemoryProfile, - InMemoryProfileSession, ) from aries_cloudagent.revocation_anoncreds.manager import RevocationManager from aries_cloudagent.tests import mock @@ -385,7 +383,6 @@ async def test_register_events(self, mock_revocation_setup_listeners): mock_event_bus.subscribe = mock.MagicMock() test_module.register_events(mock_event_bus) assert mock_revocation_setup_listeners.call_count == 1 - assert mock_event_bus.subscribe.call_count == 1 async def test_register(self): mock_app = mock.MagicMock() @@ -398,22 +395,3 @@ async def test_post_process_routes(self): mock_app = mock.MagicMock(_state={"swagger_dict": {}}) test_module.post_process_routes(mock_app) assert "tags" in mock_app._state["swagger_dict"] - - @mock.patch.object( - InMemoryProfileSession, - "handle", - ) - @mock.patch.object(AnonCredsIssuer, "finish_schema") - async def test_on_schema_event_valid_payload(self, mock_finish, mock_handle): - mock_handle.insert = mock.CoroutineMock(return_value=None) - test_event = SchemaRegistrationFinishedEvent.with_payload( - { - "context": { - "job_id": "test_job_id", - "schema_id": "test_schema_id", - } - } - ) - - await test_module.on_schema_event(self.profile, test_event) - assert mock_finish.call_count == 1