From 94a3fb51596f2d9dd7adbdeb5f7e820d10a917f6 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 30 Jul 2019 14:45:50 -0700 Subject: [PATCH 1/4] enable additional logging around credential and presentation exchanges Signed-off-by: Andrew Whitehead --- aries_cloudagent/config/argparse.py | 14 ++++++++++++++ .../credentials/models/credential_exchange.py | 1 + .../messaging/presentations/manager.py | 4 +++- .../presentations/models/presentation_exchange.py | 1 + aries_cloudagent/messaging/presentations/routes.py | 11 +++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index 63b29b8d0f..b4ab375b4e 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -174,6 +174,16 @@ def add_arguments(self, parser: ArgumentParser): action="store_true", help="Enable additional logging around connections", ) + parser.add_argument( + "--debug-credentials", + action="store_true", + help="Enable additional logging around credential exchanges", + ) + parser.add_argument( + "--debug-presentations", + action="store_true", + help="Enable additional logging around presentation exchanges", + ) parser.add_argument( "--invite", action="store_true", @@ -224,6 +234,10 @@ def get_settings(self, args: Namespace) -> dict: settings["debug.enabled"] = True if args.debug_connections: settings["debug.connections"] = True + if args.debug_credentials: + settings["debug.credentials"] = True + if args.debug_presentations: + settings["debug.presentations"] = True if args.debug_seed: settings["debug.seed"] = args.debug_seed if args.invite: diff --git a/aries_cloudagent/messaging/credentials/models/credential_exchange.py b/aries_cloudagent/messaging/credentials/models/credential_exchange.py index 08a0512ed9..fd6d6bb7fe 100644 --- a/aries_cloudagent/messaging/credentials/models/credential_exchange.py +++ b/aries_cloudagent/messaging/credentials/models/credential_exchange.py @@ -16,6 +16,7 @@ class Meta: RECORD_TYPE = "credential_exchange" RECORD_ID_NAME = "credential_exchange_id" WEBHOOK_TOPIC = "credentials" + LOG_STATE_FLAG = "debug.credentials" INITIATOR_SELF = "self" INITIATOR_EXTERNAL = "external" diff --git a/aries_cloudagent/messaging/presentations/manager.py b/aries_cloudagent/messaging/presentations/manager.py index ba5e3c933f..6f43e34c5b 100644 --- a/aries_cloudagent/messaging/presentations/manager.py +++ b/aries_cloudagent/messaging/presentations/manager.py @@ -208,7 +208,9 @@ async def create_presentation( ) presentation_exchange_record.presentation = presentation await presentation_exchange_record.save( - self.context, reason="Create presentation" + self.context, + reason="Create presentation", + log_params={"requested_credentials": requested_credentials}, ) return presentation_exchange_record, presentation_message diff --git a/aries_cloudagent/messaging/presentations/models/presentation_exchange.py b/aries_cloudagent/messaging/presentations/models/presentation_exchange.py index 6faa464723..63249359a9 100644 --- a/aries_cloudagent/messaging/presentations/models/presentation_exchange.py +++ b/aries_cloudagent/messaging/presentations/models/presentation_exchange.py @@ -16,6 +16,7 @@ class Meta: RECORD_TYPE = "presentation_exchange" RECORD_ID_NAME = "presentation_exchange_id" WEBHOOK_TOPIC = "presentations" + LOG_STATE_FLAG = "debug.presentations" INITIATOR_SELF = "self" INITIATOR_EXTERNAL = "external" diff --git a/aries_cloudagent/messaging/presentations/routes.py b/aries_cloudagent/messaging/presentations/routes.py index 6a023345dd..3c6c1ba8f9 100644 --- a/aries_cloudagent/messaging/presentations/routes.py +++ b/aries_cloudagent/messaging/presentations/routes.py @@ -177,6 +177,17 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): extra_query, ) + presentation_exchange_record.log_state( + context, + "Retrieved presentation credentials", + { + "presentation_exchange_id": presentation_exchange_id, + "referent": presentation_referent, + "extra_query": extra_query, + "credentials": credentials, + }, + ) + return web.json_response(credentials) From d20b3352fc0e015f039020395b50673afb5e4c56 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 30 Jul 2019 14:46:05 -0700 Subject: [PATCH 2/4] auto-store credentials in alice agent Signed-off-by: Andrew Whitehead --- demo/runners/alice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/demo/runners/alice.py b/demo/runners/alice.py index d26753c775..c884c8d70c 100644 --- a/demo/runners/alice.py +++ b/demo/runners/alice.py @@ -25,7 +25,11 @@ def __init__(self, http_port: int, admin_port: int, **kwargs): http_port, admin_port, prefix="Alice", - extra_args=["--auto-accept-invites", "--auto-accept-requests"], + extra_args=[ + "--auto-accept-invites", + "--auto-accept-requests", + "--auto-store-credential", + ], seed=None, **kwargs, ) From 65c8fc4eaf3d82fcaaaa632c053f9877bebd5cc1 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 30 Jul 2019 14:51:06 -0700 Subject: [PATCH 3/4] auto-store credentials in performance demo alice agent Signed-off-by: Andrew Whitehead --- demo/runners/performance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/runners/performance.py b/demo/runners/performance.py index 3d86db5c5a..2ea7474482 100644 --- a/demo/runners/performance.py +++ b/demo/runners/performance.py @@ -73,7 +73,7 @@ def __init__(self, port: int, **kwargs): super().__init__("Alice", port, seed=None, **kwargs) self.credential_state = {} self.credential_event = asyncio.Event() - self.extra_args = ["--auto-respond-credential-offer"] + self.extra_args = ["--auto-respond-credential-offer", "--auto-store-credential"] async def handle_credentials(self, payload): cred_id = payload["credential_exchange_id"] From 03d5180298d3bac71532e19d472e0ebe0ae13c32 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 30 Jul 2019 16:15:30 -0700 Subject: [PATCH 4/4] optimize credential retrieval for presentations Signed-off-by: Andrew Whitehead --- aries_cloudagent/holder/indy.py | 51 +++++++++++++------ aries_cloudagent/holder/tests/test_indy.py | 10 ++-- .../handlers/presentation_request_handler.py | 4 +- .../messaging/presentations/routes.py | 8 ++- demo/runners/alice.py | 35 +++++++------ 5 files changed, 70 insertions(+), 38 deletions(-) diff --git a/aries_cloudagent/holder/indy.py b/aries_cloudagent/holder/indy.py index 259cdc5c29..6c52ea7b01 100644 --- a/aries_cloudagent/holder/indy.py +++ b/aries_cloudagent/holder/indy.py @@ -3,6 +3,9 @@ import json import logging +from collections import OrderedDict +from typing import Sequence + import indy.anoncreds from indy.error import ErrorCode, IndyError @@ -115,7 +118,7 @@ async def get_credentials(self, start: int, count: int, wql: dict): async def get_credentials_for_presentation_request_by_referent( self, presentation_request: dict, - referent: str, + referents: Sequence[str], start: int, count: int, extra_query: dict = {}, @@ -125,9 +128,9 @@ async def get_credentials_for_presentation_request_by_referent( Args: presentation_request: Valid presentation request from issuer - referent: Presentation request referent to use to search for creds + referents: Presentation request referents to use to search for creds start: Starting index - count: Number of records to return + count: Maximum number of records to return extra_query: wql query dict """ @@ -138,27 +141,45 @@ async def get_credentials_for_presentation_request_by_referent( json.dumps(extra_query), ) - # We need to move the database cursor position manually... - if start > 0: - # TODO: move cursors in chunks to avoid exploding memory - await indy.anoncreds.prover_fetch_credentials_for_proof_req( - search_handle, referent, start + if not referents: + referents = ( + *presentation_request["requested_attributes"], + *presentation_request["requested_predicates"], ) + creds_dict = OrderedDict() try: - ( - credentials_json - ) = await indy.anoncreds.prover_fetch_credentials_for_proof_req( - search_handle, referent, count - ) + for reft in referents: + # We need to move the database cursor position manually... + if start > 0: + # TODO: move cursors in chunks to avoid exploding memory + await indy.anoncreds.prover_fetch_credentials_for_proof_req( + search_handle, reft, start + ) + ( + credentials_json + ) = await indy.anoncreds.prover_fetch_credentials_for_proof_req( + search_handle, reft, count + ) + credentials = json.loads(credentials_json) + for cred in credentials: + cred_id = cred["cred_info"]["referent"] + if cred_id not in creds_dict: + cred["presentation_referents"] = {reft} + creds_dict[cred_id] = cred + else: + creds_dict[cred_id]["presentation_referents"].add(reft) + finally: # Always close await indy.anoncreds.prover_close_credentials_search_for_proof_req( search_handle ) - credentials = json.loads(credentials_json) - return credentials + for cred in creds_dict.values(): + cred["presentation_referents"] = list(cred["presentation_referents"]) + + return tuple(creds_dict.values())[:count] async def get_credential(self, credential_id: str): """ diff --git a/aries_cloudagent/holder/tests/test_indy.py b/aries_cloudagent/holder/tests/test_indy.py index be73bd7108..25988454fe 100644 --- a/aries_cloudagent/holder/tests/test_indy.py +++ b/aries_cloudagent/holder/tests/test_indy.py @@ -110,13 +110,15 @@ async def test_get_credentials_for_presentation_request_by_referent( mock_prover_search_credentials_for_proof_req, ): mock_prover_search_credentials_for_proof_req.return_value = "search_handle" - mock_prover_fetch_credentials_for_proof_req.return_value = '{"x": "y"}' + mock_prover_fetch_credentials_for_proof_req.return_value = ( + '[{"cred_info": {"referent": "asdb"}}]' + ) mock_wallet = async_mock.MagicMock() holder = IndyHolder(mock_wallet) credentials = await holder.get_credentials_for_presentation_request_by_referent( - {"p": "r"}, "asdb", 2, 3, {"e": "q"} + {"p": "r"}, ("asdb",), 2, 3, {"e": "q"} ) mock_prover_search_credentials_for_proof_req.assert_called_once_with( @@ -132,7 +134,9 @@ async def test_get_credentials_for_presentation_request_by_referent( "search_handle" ) - assert credentials == json.loads('{"x": "y"}') + assert credentials == ( + {"cred_info": {"referent": "asdb"}, "presentation_referents": ["asdb"]}, + ) @async_mock.patch("indy.anoncreds.prover_get_credential") async def test_get_credential(self, mock_get_cred): diff --git a/aries_cloudagent/messaging/presentations/handlers/presentation_request_handler.py b/aries_cloudagent/messaging/presentations/handlers/presentation_request_handler.py index 34cb46f5c4..0c1e58045d 100644 --- a/aries_cloudagent/messaging/presentations/handlers/presentation_request_handler.py +++ b/aries_cloudagent/messaging/presentations/handlers/presentation_request_handler.py @@ -51,7 +51,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): ( credentials ) = await holder.get_credentials_for_presentation_request_by_referent( - presentation_request, referent, 0, 2, {} + presentation_request, (referent,), 0, 2, {} ) if len(credentials) != 1: self._logger.warning( @@ -73,7 +73,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): ( credentials ) = await holder.get_credentials_for_presentation_request_by_referent( - presentation_request, referent, 0, 2, {} + presentation_request, (referent,), 0, 2, {} ) if len(credentials) != 1: self._logger.warning( diff --git a/aries_cloudagent/messaging/presentations/routes.py b/aries_cloudagent/messaging/presentations/routes.py index 3c6c1ba8f9..d2e23241d4 100644 --- a/aries_cloudagent/messaging/presentations/routes.py +++ b/aries_cloudagent/messaging/presentations/routes.py @@ -148,7 +148,7 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): context = request.app["request_context"] presentation_exchange_id = request.match_info["id"] - presentation_referent = request.match_info["referent"] + presentation_referent = request.match_info.get("referent") try: presentation_exchange_record = await PresentationExchange.retrieve_by_id( @@ -171,7 +171,7 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): holder: BaseHolder = await context.inject(BaseHolder) credentials = await holder.get_credentials_for_presentation_request_by_referent( presentation_exchange_record.presentation_request, - presentation_referent, + (presentation_referent,) if presentation_referent else (), start, count, extra_query, @@ -345,6 +345,10 @@ async def register(app: web.Application): [ web.get("/presentation_exchange", presentation_exchange_list), web.get("/presentation_exchange/{id}", presentation_exchange_retrieve), + web.get( + "/presentation_exchange/{id}/credentials", + presentation_exchange_credentials_list, + ), web.get( "/presentation_exchange/{id}/credentials/{referent}", presentation_exchange_credentials_list, diff --git a/demo/runners/alice.py b/demo/runners/alice.py index c884c8d70c..23507a2d38 100644 --- a/demo/runners/alice.py +++ b/demo/runners/alice.py @@ -102,35 +102,38 @@ async def handle_presentations(self, message): ) # include self-attested attributes (not included in credentials) + credentials_by_reft = {} revealed = {} self_attested = {} predicates = {} - for referent in presentation_request["requested_attributes"]: + # select credentials to provide for the proof + credentials = await self.admin_GET( + f"/presentation_exchange/{presentation_exchange_id}/credentials" + ) + if credentials: + for row in credentials: + for referent in row["presentation_referents"]: + if referent not in credentials_by_reft: + credentials_by_reft[referent] = row - # select credentials to provide for the proof - credentials = await self.admin_GET( - f"/presentation_exchange/{presentation_exchange_id}" - + f"/credentials/{referent}" - ) - if credentials: + for referent in presentation_request["requested_attributes"]: + if referent in credentials_by_reft: revealed[referent] = { - "cred_id": credentials[0]["cred_info"]["referent"], + "cred_id": credentials_by_reft[referent]["cred_info"][ + "referent" + ], "revealed": True, } else: self_attested[referent] = "my self-attested value" for referent in presentation_request["requested_predicates"]: - - # select credentials to provide for the proof - credentials = await self.admin_GET( - f"/presentation_exchange/{presentation_exchange_id}" - f"/credentials/{referent}" - ) - if credentials: + if referent in credentials_by_reft: predicates[referent] = { - "cred_id": credentials[0]["cred_info"]["referent"], + "cred_id": credentials_by_reft[referent]["cred_info"][ + "referent" + ], "revealed": True, }