Skip to content

Commit 213d2fa

Browse files
fix IssuerCredRevRecord state update on revocation publish; add unit test
Signed-off-by: Andrew Whitehead <cywolf@gmail.com>
1 parent 4240fa9 commit 213d2fa

5 files changed

Lines changed: 125 additions & 60 deletions

File tree

aries_cloudagent/indy/sdk/issuer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ async def revoke_credentials(
283283
tails_reader_handle = await create_tails_reader(tails_file_path)
284284

285285
result_json = None
286-
for cred_rev_id in cred_rev_ids:
286+
for cred_rev_id in set(cred_rev_ids):
287287
with IndyErrorHandler(
288288
"Exception when revoking credential", IndyIssuerError
289289
):

aries_cloudagent/indy/sdk/tests/test_issuer.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,20 @@ async def test_create_revoke_credentials_x(
311311
values = json.loads(call_values)
312312
assert "attr1" in values
313313

314-
mock_indy_revoke_credential.side_effect = [
315-
json.dumps(TEST_RR_DELTA),
316-
IndyError(
317-
error_code=ErrorCode.AnoncredsInvalidUserRevocId,
318-
error_details={"message": "already revoked"},
319-
),
320-
IndyError(
314+
def mock_revoke(_h, _t, _r, cred_rev_id):
315+
if cred_rev_id == "42":
316+
return json.dumps(TEST_RR_DELTA)
317+
if cred_rev_id == "54":
318+
raise IndyError(
319+
error_code=ErrorCode.AnoncredsInvalidUserRevocId,
320+
error_details={"message": "already revoked"},
321+
)
322+
raise IndyError(
321323
error_code=ErrorCode.UnknownCryptoTypeError,
322324
error_details={"message": "truly an outlier"},
323-
),
324-
]
325+
)
326+
327+
mock_indy_revoke_credential.side_effect = mock_revoke
325328
mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA)
326329
(result, failed) = await self.issuer.revoke_credentials(
327330
REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids

aries_cloudagent/revocation/manager.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async def revoke_credential(
133133
rev_reg = await revoc.get_ledger_registry(rev_reg_id)
134134
await rev_reg.get_or_fetch_local_tails_path()
135135
# pick up pending revocations on input revocation registry
136-
crids = list(set(issuer_rr_rec.pending_pub + [cred_rev_id]))
136+
crids = (issuer_rr_rec.pending_pub or []) + [cred_rev_id]
137137
(delta_json, _) = await issuer.revoke_credentials(
138138
issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids
139139
)
@@ -145,9 +145,9 @@ async def revoke_credential(
145145
issuer_rr_upd.revoc_reg_entry = json.loads(delta_json)
146146
await issuer_rr_upd.clear_pending(txn, crids)
147147
await txn.commit()
148+
await self.set_cred_revoked_state(rev_reg_id, crids)
148149
if delta_json:
149150
await issuer_rr_upd.send_entry(self._profile)
150-
await self.set_cred_revoked_state(rev_reg_id, [cred_rev_id])
151151
await notify_revocation_published_event(
152152
self._profile, rev_reg_id, [cred_rev_id]
153153
)
@@ -215,11 +215,11 @@ async def publish_pending_revocations(
215215
issuer_rr_upd.revoc_reg_entry = json.loads(delta_json)
216216
await issuer_rr_upd.clear_pending(txn, crids)
217217
await txn.commit()
218+
await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids)
218219
if delta_json:
219220
await issuer_rr_upd.send_entry(self._profile)
220221
published = sorted(crid for crid in crids if crid not in failed_crids)
221222
result[issuer_rr_rec.revoc_reg_id] = published
222-
await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids)
223223
await notify_revocation_published_event(
224224
self._profile, issuer_rr_rec.revoc_reg_id, crids
225225
)
@@ -289,34 +289,40 @@ async def set_cred_revoked_state(
289289
290290
"""
291291
for cred_rev_id in cred_rev_ids:
292+
cred_ex_id = None
293+
294+
try:
295+
async with self._profile.transaction() as txn:
296+
rev_rec = await IssuerCredRevRecord.retrieve_by_ids(
297+
txn, rev_reg_id, cred_rev_id, for_update=True
298+
)
299+
cred_ex_id = rev_rec.cred_ex_id
300+
rev_rec.state = IssuerCredRevRecord.STATE_REVOKED
301+
await rev_rec.save(txn, reason="revoke credential")
302+
await txn.commit()
303+
except StorageNotFoundError:
304+
continue
305+
292306
async with self._profile.transaction() as txn:
293307
try:
294-
rev_rec = await IssuerCredRevRecord.retrieve_by_ids(
295-
txn, rev_reg_id, cred_rev_id
308+
cred_ex_record = await V10CredentialExchange.retrieve_by_id(
309+
txn, cred_ex_id, for_update=True
310+
)
311+
cred_ex_record.state = (
312+
V10CredentialExchange.STATE_CREDENTIAL_REVOKED
296313
)
297-
try:
298-
cred_ex_record = await V10CredentialExchange.retrieve_by_id(
299-
txn, rev_rec.cred_ex_id, for_update=True
300-
)
301-
cred_ex_record.state = (
302-
V10CredentialExchange.STATE_CREDENTIAL_REVOKED
303-
)
304-
await cred_ex_record.save(txn, reason="revoke credential")
305-
await txn.commit()
306-
307-
except StorageNotFoundError:
308-
try:
309-
cred_ex_record = await V20CredExRecord.retrieve_by_id(
310-
txn, rev_rec.cred_ex_id, for_update=True
311-
)
312-
cred_ex_record.state = (
313-
V20CredExRecord.STATE_CREDENTIAL_REVOKED
314-
)
315-
await cred_ex_record.save(txn, reason="revoke credential")
316-
await txn.commit()
317-
318-
except StorageNotFoundError:
319-
pass
314+
await cred_ex_record.save(txn, reason="revoke credential")
315+
await txn.commit()
316+
continue # skip 2.0 record check
317+
except StorageNotFoundError:
318+
pass
320319

320+
try:
321+
cred_ex_record = await V20CredExRecord.retrieve_by_id(
322+
txn, cred_ex_id, for_update=True
323+
)
324+
cred_ex_record.state = V20CredExRecord.STATE_CREDENTIAL_REVOKED
325+
await cred_ex_record.save(txn, reason="revoke credential")
326+
await txn.commit()
321327
except StorageNotFoundError:
322328
pass

aries_cloudagent/revocation/models/issuer_cred_rev_record.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,15 @@ async def retrieve_by_ids(
9090
session: ProfileSession,
9191
rev_reg_id: str,
9292
cred_rev_id: str,
93+
*,
94+
for_update: bool = False,
9395
) -> "IssuerCredRevRecord":
9496
"""Retrieve an issuer cred rev record by rev reg id and cred rev id."""
9597
return await cls.retrieve_by_tag_filter(
96-
session, {"rev_reg_id": rev_reg_id}, {"cred_rev_id": cred_rev_id}
98+
session,
99+
{"rev_reg_id": rev_reg_id},
100+
{"cred_rev_id": cred_rev_id},
101+
for_update=for_update,
97102
)
98103

99104
@classmethod

aries_cloudagent/revocation/tests/test_manager.py

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
from asynctest import TestCase as AsyncTestCase
55
from more_itertools import side_effect
66

7+
from aries_cloudagent.revocation.models.issuer_cred_rev_record import (
8+
IssuerCredRevRecord,
9+
)
10+
711
from ...core.in_memory import InMemoryProfile
812
from ...indy.issuer import IndyIssuer
913
from ...protocols.issue_credential.v1_0.models.credential_exchange import (
@@ -38,7 +42,25 @@ async def test_revoke_credential_publish(self):
3842
tails_local_path=TAILS_LOCAL,
3943
send_entry=async_mock.CoroutineMock(),
4044
clear_pending=async_mock.CoroutineMock(),
45+
pending_pub=["2"],
4146
)
47+
issuer = async_mock.MagicMock(IndyIssuer, autospec=True)
48+
issuer.revoke_credentials = async_mock.CoroutineMock(
49+
return_value=(
50+
json.dumps(
51+
{
52+
"ver": "1.0",
53+
"value": {
54+
"prevAccum": "1 ...",
55+
"accum": "21 ...",
56+
"issued": [1],
57+
},
58+
}
59+
),
60+
[],
61+
)
62+
)
63+
self.profile.context.injector.bind_instance(IndyIssuer, issuer)
4264

4365
with async_mock.patch.object(
4466
test_module.IssuerCredRevRecord,
@@ -64,26 +86,14 @@ async def test_revoke_credential_publish(self):
6486
return_value=mock_rev_reg
6587
)
6688

67-
issuer = async_mock.MagicMock(IndyIssuer, autospec=True)
68-
issuer.revoke_credentials = async_mock.CoroutineMock(
69-
return_value=(
70-
json.dumps(
71-
{
72-
"ver": "1.0",
73-
"value": {
74-
"prevAccum": "1 ...",
75-
"accum": "21 ...",
76-
"issued": [1],
77-
},
78-
}
79-
),
80-
[],
81-
)
82-
)
83-
self.profile.context.injector.bind_instance(IndyIssuer, issuer)
84-
8589
await self.manager.revoke_credential_by_cred_ex_id(CRED_EX_ID, publish=True)
8690

91+
issuer.revoke_credentials.assert_awaited_once_with(
92+
mock_issuer_rev_reg_record.revoc_reg_id,
93+
mock_issuer_rev_reg_record.tails_local_path,
94+
["2", "1"],
95+
)
96+
8797
async def test_revoke_cred_by_cxid_not_found(self):
8898
CRED_EX_ID = "dummy-cxid"
8999

@@ -128,6 +138,8 @@ async def test_revoke_credential_pend(self):
128138
mock_issuer_rev_reg_record = async_mock.MagicMock(
129139
mark_pending=async_mock.CoroutineMock()
130140
)
141+
issuer = async_mock.MagicMock(IndyIssuer, autospec=True)
142+
self.profile.context.injector.bind_instance(IndyIssuer, issuer)
131143

132144
with async_mock.patch.object(
133145
test_module, "IndyRevocation", autospec=True
@@ -148,14 +160,13 @@ async def test_revoke_credential_pend(self):
148160
return_value=mock_issuer_rev_reg_record
149161
)
150162

151-
issuer = async_mock.MagicMock(IndyIssuer, autospec=True)
152-
self.profile.context.injector.bind_instance(IndyIssuer, issuer)
153-
154163
await self.manager.revoke_credential(REV_REG_ID, CRED_REV_ID, False)
155164
mock_issuer_rev_reg_record.mark_pending.assert_called_once_with(
156165
session.return_value, CRED_REV_ID
157166
)
158167

168+
issuer.revoke_credentials.assert_not_awaited()
169+
159170
async def test_publish_pending_revocations_basic(self):
160171
deltas = [
161172
{
@@ -415,3 +426,43 @@ async def test_retrieve_records(self):
415426
)
416427
assert ret_ex.connection_id == str(index)
417428
assert ret_ex.thread_id == str(1000 + index)
429+
430+
async def test_set_revoked_state(self):
431+
CRED_REV_ID = "1"
432+
433+
async with self.profile.session() as session:
434+
exchange_record = V10CredentialExchange(
435+
connection_id="mark-revoked-cid",
436+
thread_id="mark-revoked-tid",
437+
initiator=V10CredentialExchange.INITIATOR_SELF,
438+
revoc_reg_id=REV_REG_ID,
439+
revocation_id=CRED_REV_ID,
440+
role=V10CredentialExchange.ROLE_ISSUER,
441+
state=V10CredentialExchange.STATE_ISSUED,
442+
)
443+
await exchange_record.save(session)
444+
445+
crev_record = IssuerCredRevRecord(
446+
cred_ex_id=exchange_record.credential_exchange_id,
447+
cred_def_id=CRED_DEF_ID,
448+
rev_reg_id=REV_REG_ID,
449+
cred_rev_id=CRED_REV_ID,
450+
state=IssuerCredRevRecord.STATE_ISSUED,
451+
)
452+
await crev_record.save(session)
453+
454+
await self.manager.set_cred_revoked_state(REV_REG_ID, [CRED_REV_ID])
455+
456+
async with self.profile.session() as session:
457+
check_exchange_record = await V10CredentialExchange.retrieve_by_id(
458+
session, exchange_record.credential_exchange_id
459+
)
460+
assert (
461+
check_exchange_record.state
462+
== V10CredentialExchange.STATE_CREDENTIAL_REVOKED
463+
)
464+
465+
check_crev_record = await IssuerCredRevRecord.retrieve_by_id(
466+
session, crev_record.record_id
467+
)
468+
assert check_crev_record.state == IssuerCredRevRecord.STATE_REVOKED

0 commit comments

Comments
 (0)