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

Cred def 2 schema #223

Merged
merged 3 commits into from
Oct 16, 2019
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
6 changes: 3 additions & 3 deletions aries_cloudagent/cache/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Abstract base classes for cache."""

from abc import ABC, abstractmethod
from typing import Any, Text
from typing import Any, Sequence, Text, Union


class BaseCache(ABC):
Expand All @@ -21,12 +21,12 @@ async def get(self, key: Text):
"""

@abstractmethod
async def set(self, key: Text, value: Any, ttl: int = None):
async def set(self, keys: Union[Text, Sequence[Text]], value: Any, ttl: int = None):
"""
Add an item to the cache with an optional ttl.

Args:
key: the key to set an item for
keys: the key or keys for which to set an item
value: the value to store in the cache
ttl: number of second that the record should persist

Expand Down
15 changes: 9 additions & 6 deletions aries_cloudagent/cache/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import datetime, timedelta

from typing import Any, Text
from typing import Any, Sequence, Text, Union

from .base import BaseCache

Expand Down Expand Up @@ -40,26 +40,29 @@ async def get(self, key: Text):
self._remove_expired_cache_items()
return self._cache.get(key)["value"] if self._cache.get(key) else None

async def set(self, key: Text, value: Any, ttl: int = None):
async def set(self, keys: Union[Text, Sequence[Text]], value: Any, ttl: int = None):
"""
Add an item to the cache with an optional ttl.

Overwrites existing cache entries.

Args:
key: the key to set an item for
keys: the key or keys for which to set an item
value: the value to store in the cache
ttl: number of seconds that the record should persist

"""
self._remove_expired_cache_items()
now = datetime.now()
expires_ts = None
if ttl:
expires = now + timedelta(seconds=ttl)
expires_ts = expires.timestamp()
self._cache[key] = {"expires": expires_ts, "value": value}
else:
self._cache[key] = {"expires": None, "value": value}
for key in ([keys] if isinstance(keys, Text) else keys):
self._cache[key] = {
"expires": expires_ts,
"value": value
}

async def clear(self, key: Text):
"""
Expand Down
20 changes: 20 additions & 0 deletions aries_cloudagent/cache/tests/test_basic_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ async def test_set_dict(self, cache):
assert cache._cache["key"] is not None
assert cache._cache["key"]["value"] == {"dictkey": "dval"}

@pytest.mark.asyncio
async def test_set_multi(self, cache):
item = await cache.set([f"key{i}" for i in range(4)], {"dictkey": "dval"})
for key in [f"key{i}" for i in range(4)]:
assert cache._cache[key] is not None
assert cache._cache[key]["value"] == {"dictkey": "dval"}

@pytest.mark.asyncio
async def test_set_expires(self, cache):
item = await cache.set("key", {"dictkey": "dval"}, 0.05)
Expand All @@ -45,6 +52,19 @@ async def test_set_expires(self, cache):
item = await cache.get("key")
assert item is None

@pytest.mark.asyncio
async def test_set_expires_multi(self, cache):
item = await cache.set([f"key{i}" for i in range(4)], {"dictkey": "dval"}, 0.05)
for key in [f"key{i}" for i in range(4)]:
assert cache._cache[key] is not None
assert cache._cache[key]["value"] == {"dictkey": "dval"}

await sleep(0.05)

for key in [f"key{i}" for i in range(4)]:
item = await cache.get(key)
assert item is None

@pytest.mark.asyncio
async def test_flush(self, cache):
await cache.flush()
Expand Down
81 changes: 52 additions & 29 deletions aries_cloudagent/ledger/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
LedgerTransactionError,
)


GENESIS_TRANSACTION_PATH = tempfile.gettempdir()
GENESIS_TRANSACTION_PATH = path.join(
GENESIS_TRANSACTION_PATH, "indy_genesis_transactions.txt"
Expand Down Expand Up @@ -341,7 +342,7 @@ async def check_existing_schema(
) -> str:
"""Check if a schema has already been published."""
fetch_schema_id = f"{public_did}:2:{schema_name}:{schema_version}"
schema = await self.fetch_schema(fetch_schema_id)
schema = await self.fetch_schema_by_id(fetch_schema_id)
if schema:
fetched_attrs = schema["attrNames"].copy()
fetched_attrs.sort()
Expand All @@ -359,24 +360,30 @@ async def get_schema(self, schema_id: str):
Get a schema from the cache if available, otherwise fetch from the ledger.

Args:
schema_id: The schema id to retrieve
schema_id: The schema id (or stringified sequence number) to retrieve

"""
if self.cache:
result = await self.cache.get(f"schema::{schema_id}")
if result:
return result
return await self.fetch_schema(schema_id)

async def fetch_schema(self, schema_id: str):
if schema_id.isdigit():
return await self.fetch_schema_by_seq_no(int(schema_id))
else:
return await self.fetch_schema_by_id(schema_id)

async def fetch_schema_by_id(self, schema_id: str):
"""
Get schema from ledger.

Args:
schema_id: The schema id to retrieve
schema_id: The schema id (or stringified sequence number) to retrieve

"""
Returns:
Indy schema dict

"""
public_did = await self.wallet.get_public_did()

with IndyErrorHandler("Exception when building schema request"):
Expand All @@ -398,11 +405,45 @@ async def fetch_schema(self, schema_id: str):
parsed_response = json.loads(parsed_schema_json)
if parsed_response and self.cache:
await self.cache.set(
f"schema::{schema_id}", parsed_response, self.cache_duration
[f"schema::{schema_id}", f"schema::{response['result']['seqNo']}"],
parsed_response,
self.cache_duration
)

return parsed_response

async def fetch_schema_by_seq_no(self, seq_no: int):
"""
Fetch a schema by its sequence number.

Args:
seq_no: schema ledger sequence number

Returns:
Indy schema dict

"""
# get txn by sequence number, retrieve schema identifier components
request_json = await indy.ledger.build_get_txn_request(
None, None, seq_no=seq_no
)
response = json.loads(await self._submit(request_json))

# transaction data format assumes node protocol >= 1.4 (circa 2018-07)
data_txn = (response["result"].get("data", {}) or {}).get("txn", {})
if data_txn.get("type", None) == "101": # marks indy-sdk schema txn type
(origin_did, name, version) = (
data_txn["metadata"]["from"],
data_txn["data"]["data"]["name"],
data_txn["data"]["data"]["version"],
)
schema_id = f"{origin_did}:2:{name}:{version}"
return await self.get_schema(schema_id)

raise LedgerTransactionError(
f"Could not get schema from ledger for seq no {seq_no}"
)

async def send_credential_definition(self, schema_id: str, tag: str = "default"):
"""
Send credential definition to ledger and store relevant key matter in wallet.
Expand Down Expand Up @@ -492,14 +533,15 @@ async def get_credential_definition(self, credential_definition_id: str):
)
if result:
return result

return await self.fetch_credential_definition(credential_definition_id)

async def fetch_credential_definition(self, credential_definition_id: str):
"""
Get a credential definition from the ledger by id.

Args:
credential_definition_id: The schema id of the schema to fetch cred def for
credential_definition_id: The cred def id of the cred def to fetch

"""

Expand Down Expand Up @@ -546,28 +588,9 @@ async def credential_definition_id2schema_id(self, credential_definition_id):
if len(tokens) == 8: # node protocol >= 1.4: cred def id has 5 or 8 tokens
return ":".join(tokens[3:7]) # schema id spans 0-based positions 3-6

seq_no = int(tokens[3])

# get txn by sequence number, retrieve schema identifier components
request_json = await indy.ledger.build_get_txn_request(
None, None, seq_no=seq_no
)
response = json.loads(await self._submit(request_json))

# transaction data format assumes node protocol >= 1.4 (circa 2018-07)
data_txn = (response["result"].get("data", {}) or {}).get("txn", {})
if data_txn.get("type", None) == "101": # marks indy-sdk schema txn type
(origin_did, name, version) = (
data_txn["metadata"]["from"],
data_txn["data"]["data"]["name"],
data_txn["data"]["data"]["version"],
)
return f"{origin_did}:2:{name}:{version}"

raise LedgerTransactionError(
"Could not get schema identifier from ledger for "
+ f"credential definition id {credential_definition_id}"
)
seq_no = tokens[3]
return (await self.get_schema(seq_no))["id"]

async def get_key_for_did(self, did: str) -> str:
"""Fetch the verkey for a ledger DID.
Expand Down
44 changes: 14 additions & 30 deletions aries_cloudagent/ledger/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,16 @@ async def test_submit_rejected(
@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._submit")
@async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.fetch_schema")
@async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.fetch_schema_by_id")
@async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.fetch_schema_by_seq_no")
@async_mock.patch("indy.anoncreds.issuer_create_schema")
@async_mock.patch("indy.ledger.build_schema_request")
async def test_send_schema(
self,
mock_build_schema_req,
mock_create_schema,
mock_fetch_schema,
mock_fetch_schema_by_seq_no,
mock_fetch_schema_by_id,
mock_submit,
mock_close,
mock_open,
Expand All @@ -218,7 +220,8 @@ async def test_send_schema(
ledger = IndyLedger("name", mock_wallet)

mock_create_schema.return_value = ("schema_id", "{}")
mock_fetch_schema.return_value = None
mock_fetch_schema_by_id.return_value = None
mock_fetch_schema_by_seq_no.return_value = None

async with ledger:
mock_wallet.get_public_did = async_mock.CoroutineMock()
Expand Down Expand Up @@ -413,48 +416,29 @@ async def test_get_credential_definition(

@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._submit")
@async_mock.patch("indy.ledger.build_get_txn_request")
@async_mock.patch("aries_cloudagent.ledger.indy.IndyLedger.get_schema")
async def test_credential_definition_id2schema_id(
self,
mock_build_get_txn_req,
mock_submit,
mock_get_schema,
mock_close,
mock_open,
):
mock_wallet = async_mock.MagicMock()
mock_wallet.WALLET_TYPE = "indy"

mock_build_get_txn_req.return_value = json.dumps("dummy")
mock_submit.return_value = json.dumps({
"result": {
"data": {
"txn": {
"type": "101",
"metadata": {
"from": f"{TestIndyLedger.test_did}"
},
"data": {
"data": {
"name": "favourite_drink",
"version": "1.0"
}
}
}
}
}
})
S_ID = f"{TestIndyLedger.test_did}:2:favourite_drink:1.0"
SEQ_NO = "9999"
mock_get_schema.return_value = {"id": S_ID}

ledger = IndyLedger("name", mock_wallet)

async with ledger:
s_id_short = await ledger.credential_definition_id2schema_id(
f"{TestIndyLedger.test_did}:3:CL:9999:tag"
f"{TestIndyLedger.test_did}:3:CL:{SEQ_NO}:tag"
)
mock_build_get_txn_req.assert_called_once_with(None, None, seq_no=9999)
mock_submit.assert_called_once_with(mock_build_get_txn_req.return_value)
mock_get_schema.assert_called_once_with(SEQ_NO)

assert s_id_short == f"{TestIndyLedger.test_did}:2:favourite_drink:1.0"
assert s_id_short == S_ID
s_id_long = await ledger.credential_definition_id2schema_id(
f"{TestIndyLedger.test_did}:3:CL:{s_id_short}:tag"
)
Expand Down
9 changes: 8 additions & 1 deletion aries_cloudagent/messaging/credential_definitions/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class CredentialDefinitionSendRequestSchema(Schema):
description="Schema identifier",
**INDY_SCHEMA_ID
)
tag = fields.Str(
required=False,
description="Credential definition identifier tag",
default="default",
example="default",
)


class CredentialDefinitionSendResultsSchema(Schema):
Expand Down Expand Up @@ -90,11 +96,12 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq
body = await request.json()

schema_id = body.get("schema_id")
tag = body.get("tag")

ledger: BaseLedger = await context.inject(BaseLedger)
async with ledger:
credential_definition_id = await shield(
ledger.send_credential_definition(schema_id)
ledger.send_credential_definition(schema_id, tag)
)

return web.json_response({"credential_definition_id": credential_definition_id})
Expand Down
Loading