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

Streamline peri cred def creation #683

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
5 changes: 4 additions & 1 deletion aries_cloudagent/ledger/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand Down
26 changes: 10 additions & 16 deletions aries_cloudagent/ledger/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.

Expand All @@ -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:
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -624,27 +629,16 @@ 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
):
request_json = await indy.ledger.build_cred_def_request(
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,
Expand All @@ -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:
"""
Expand Down
109 changes: 101 additions & 8 deletions aries_cloudagent/ledger/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
24 changes: 10 additions & 14 deletions aries_cloudagent/messaging/credential_definitions/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down