Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix demo presentation request, optimize credential retrieval #108

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions aries_cloudagent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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:
Expand Down
51 changes: 36 additions & 15 deletions aries_cloudagent/holder/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = {},
Expand All @@ -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

"""
Expand All @@ -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):
"""
Expand Down
10 changes: 7 additions & 3 deletions aries_cloudagent/holder/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down
4 changes: 3 additions & 1 deletion aries_cloudagent/messaging/presentations/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 17 additions & 2 deletions aries_cloudagent/messaging/presentations/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -171,12 +171,23 @@ 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,
)

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)


Expand Down Expand Up @@ -334,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,
Expand Down
41 changes: 24 additions & 17 deletions demo/runners/alice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -98,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,
}

Expand Down
2 changes: 1 addition & 1 deletion demo/runners/performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down