Skip to content

Commit 417663c

Browse files
authored
Merge pull request #4056 from OpSecId/fix-bdd-integration-revocation-flow
2 parents 637bc7a + 6c4fbdc commit 417663c

File tree

6 files changed

+131
-7
lines changed

6 files changed

+131
-7
lines changed

acapy_agent/anoncreds/revocation/revocation.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,13 @@ async def decommission_registry(self, cred_def_id: str) -> list:
15311531
new_rev_reg_def_id = None
15321532
if new_backup_reg and not isinstance(new_backup_reg, str):
15331533
new_rev_reg_def_id = new_backup_reg.rev_reg_def_id
1534-
await self.store_revocation_registry_definition(new_backup_reg)
1534+
try:
1535+
await self.store_revocation_registry_definition(new_backup_reg)
1536+
except AnonCredsRevocationError:
1537+
LOGGER.debug(
1538+
"Registry %s already stored by event chain",
1539+
new_rev_reg_def_id,
1540+
)
15351541
elif isinstance(new_backup_reg, str):
15361542
LOGGER.error("Failed to create new backup registry: %s", new_backup_reg)
15371543
else:
@@ -1544,10 +1550,12 @@ async def decommission_registry(self, cred_def_id: str) -> list:
15441550
keep_ids.add(new_rev_reg_def_id)
15451551

15461552
async with self.profile.transaction() as txn:
1547-
registries = await txn.handle.fetch_all(
1548-
CATEGORY_REV_REG_DEF,
1549-
{"cred_def_id": cred_def_id},
1550-
for_update=True,
1553+
registries = list(
1554+
await txn.handle.fetch_all(
1555+
CATEGORY_REV_REG_DEF,
1556+
{"cred_def_id": cred_def_id},
1557+
for_update=True,
1558+
)
15511559
)
15521560
recs = [
15531561
r for r in registries if r.tags.get("state") != RevRegDefState.STATE_WAIT

acapy_agent/anoncreds/revocation/revocation_setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ async def on_cred_def(self, profile: Profile, event: CredDefFinishedEvent) -> No
389389
)
390390

391391
if event.payload.options.get("wait_for_revocation_setup"):
392-
# Wait for registry activation, if configured to do so
393392
await revoc.wait_for_active_revocation_registry(payload.cred_def_id)
394393

395394
async def on_registry_create_requested(

acapy_agent/anoncreds/revocation/tests/test_revocation.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,87 @@ async def test_decommission_registry_new_backup_creation_fails(self, mock_handle
10461046
assert len(result) == 2
10471047
assert result[0].tags["state"] == RevRegDefState.STATE_DECOMMISSIONED
10481048

1049+
@mock.patch.object(AskarAnonCredsProfileSession, "handle")
1050+
async def test_decommission_registry_duplicate_store_handled(self, mock_handle):
1051+
"""When event chain already stored the registry, duplicate is handled gracefully."""
1052+
mock_handle.fetch_all = mock.CoroutineMock(
1053+
side_effect=[
1054+
[
1055+
MockEntry(
1056+
name="backup-reg-reg",
1057+
tags={
1058+
"cred_def_id": "test-rev-reg-def-id",
1059+
"state": RevRegDefState.STATE_FINISHED,
1060+
"active": "false",
1061+
},
1062+
),
1063+
],
1064+
[
1065+
MockEntry(
1066+
name="active-reg-reg",
1067+
tags={
1068+
"cred_def_id": "test-rev-reg-def-id",
1069+
"state": RevRegDefState.STATE_FINISHED,
1070+
"active": "true",
1071+
},
1072+
),
1073+
MockEntry(
1074+
name="backup-reg-reg",
1075+
tags={
1076+
"cred_def_id": "test-rev-reg-def-id",
1077+
"state": RevRegDefState.STATE_FINISHED,
1078+
"active": "false",
1079+
},
1080+
),
1081+
MockEntry(
1082+
name="new-rev-reg",
1083+
tags={
1084+
"cred_def_id": "test-rev-reg-def-id",
1085+
"state": RevRegDefState.STATE_FINISHED,
1086+
"active": "false",
1087+
},
1088+
),
1089+
],
1090+
]
1091+
)
1092+
self.revocation.get_or_create_active_registry = mock.CoroutineMock(
1093+
return_value=RevRegDefResult(
1094+
job_id="test-job-id",
1095+
revocation_registry_definition_state=RevRegDefState(
1096+
state=RevRegDefState.STATE_FINISHED,
1097+
revocation_registry_definition_id="active-reg-reg",
1098+
revocation_registry_definition=rev_reg_def,
1099+
),
1100+
registration_metadata={},
1101+
revocation_registry_definition_metadata={},
1102+
)
1103+
)
1104+
self.revocation.create_and_register_revocation_registry_definition = (
1105+
mock.CoroutineMock(
1106+
return_value=RevRegDefResult(
1107+
job_id="test-job-id",
1108+
revocation_registry_definition_state=RevRegDefState(
1109+
state=RevRegDefState.STATE_ACTION,
1110+
revocation_registry_definition_id="new-rev-reg",
1111+
revocation_registry_definition=rev_reg_def,
1112+
),
1113+
registration_metadata={},
1114+
revocation_registry_definition_metadata={},
1115+
)
1116+
)
1117+
)
1118+
self.revocation.store_revocation_registry_definition = mock.CoroutineMock(
1119+
side_effect=test_module.AnonCredsRevocationError("Duplicate entry")
1120+
)
1121+
self.revocation.set_active_registry = mock.CoroutineMock(return_value=None)
1122+
mock_handle.replace = mock.CoroutineMock(return_value=None)
1123+
1124+
result = await self.revocation.decommission_registry("test-rev-reg-def-id")
1125+
1126+
assert isinstance(result, list)
1127+
self.revocation.store_revocation_registry_definition.assert_called_once()
1128+
self.revocation.set_active_registry.assert_called_once_with("backup-reg-reg")
1129+
10491130
@mock.patch.object(AskarAnonCredsProfileSession, "handle")
10501131
async def test_get_backup_registry_id_raises_when_no_backup(self, mock_handle):
10511132
"""_get_backup_registry_id raises when no finished backup exists."""

acapy_agent/anoncreds/routes/revocation/registry/routes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ async def get_active_rev_reg(request: web.BaseRequest):
277277
revocation = AnonCredsRevocation(profile)
278278
active_reg = await revocation.get_or_create_active_registry(cred_def_id)
279279
rev_reg = await _get_issuer_rev_reg_record(profile, active_reg.rev_reg_def_id)
280+
except AnonCredsRevocationError as e:
281+
raise web.HTTPNotFound(reason=str(e)) from e
280282
except AnonCredsIssuerError as e:
281283
raise web.HTTPInternalServerError(reason=str(e)) from e
282284

acapy_agent/anoncreds/routes/revocation/registry/tests/test_routes.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
from .....issuer import AnonCredsIssuer
1212
from .....models.issuer_cred_rev_record import IssuerCredRevRecord
1313
from .....models.revocation import RevRegDef, RevRegDefState, RevRegDefValue
14-
from .....revocation import AnonCredsRevocation
14+
from .....revocation import AnonCredsRevocation, AnonCredsRevocationError
1515
from .....tests.mock_objects import MockRevocationRegistryDefinition
1616
from ....common.testing import BaseAnonCredsRouteTestCase
1717
from .. import routes as test_module
1818
from ..routes import (
19+
get_active_rev_reg,
1920
get_rev_reg_issued,
2021
get_rev_reg_issued_count,
2122
get_rev_regs,
@@ -178,6 +179,16 @@ async def test_active_registry_wrong_profile_403(self):
178179
with self.assertRaises(web.HTTPForbidden):
179180
await set_active_registry(self.request)
180181

182+
@mock.patch.object(
183+
AnonCredsRevocation,
184+
"get_or_create_active_registry",
185+
side_effect=AnonCredsRevocationError("No active registry"),
186+
)
187+
async def test_get_active_rev_reg_not_found(self, mock_get):
188+
self.request.match_info = {"cred_def_id": "test_cred_def_id"}
189+
with self.assertRaises(web.HTTPNotFound):
190+
await get_active_rev_reg(self.request)
191+
181192
async def test_get_rev_regs(self):
182193
self.request.query = {
183194
"cred_def_id": "test_cred_def_id",

demo/runners/support/agent.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,29 @@ async def register_schema_and_creddef_anoncreds(
494494
"credential_definition_ids"
495495
][0]
496496
log_msg("Cred def ID:", credential_definition_id)
497+
498+
if support_revocation:
499+
log_msg("Waiting for revocation registry to become active...")
500+
poll_timeout = 60.0
501+
poll_interval = 2.0
502+
elapsed = 0.0
503+
while elapsed < poll_timeout:
504+
try:
505+
active_reg = await self.admin_GET(
506+
f"/anoncreds/revocation/active-registry/{credential_definition_id}"
507+
)
508+
if active_reg and active_reg.get("result"):
509+
log_msg("Revocation registry is active.")
510+
break
511+
except Exception:
512+
pass
513+
await asyncio.sleep(poll_interval)
514+
elapsed += poll_interval
515+
else:
516+
log_msg(
517+
"WARNING: Revocation registry did not become active within timeout"
518+
)
519+
497520
return schema_id, credential_definition_id
498521

499522
def get_agent_args(self):

0 commit comments

Comments
 (0)