Skip to content
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
18 changes: 13 additions & 5 deletions acapy_agent/anoncreds/revocation/revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1531,7 +1531,13 @@ async def decommission_registry(self, cred_def_id: str) -> list:
new_rev_reg_def_id = None
if new_backup_reg and not isinstance(new_backup_reg, str):
new_rev_reg_def_id = new_backup_reg.rev_reg_def_id
await self.store_revocation_registry_definition(new_backup_reg)
try:
await self.store_revocation_registry_definition(new_backup_reg)
except AnonCredsRevocationError:
LOGGER.debug(
"Registry %s already stored by event chain",
new_rev_reg_def_id,
)
Comment on lines +1534 to +1540
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a lil bit dubious to suppress an exception without checking what the exception is. The assumption here is that it can only fail in 1 way, but there should be some error detail that one can check to validate that assumption, and if something else went wrong, then raise or log at higher level.

elif isinstance(new_backup_reg, str):
LOGGER.error("Failed to create new backup registry: %s", new_backup_reg)
else:
Expand All @@ -1544,10 +1550,12 @@ async def decommission_registry(self, cred_def_id: str) -> list:
keep_ids.add(new_rev_reg_def_id)

async with self.profile.transaction() as txn:
registries = await txn.handle.fetch_all(
CATEGORY_REV_REG_DEF,
{"cred_def_id": cred_def_id},
for_update=True,
registries = list(
await txn.handle.fetch_all(
CATEGORY_REV_REG_DEF,
{"cred_def_id": cred_def_id},
for_update=True,
)
)
recs = [
r for r in registries if r.tags.get("state") != RevRegDefState.STATE_WAIT
Expand Down
1 change: 0 additions & 1 deletion acapy_agent/anoncreds/revocation/revocation_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,6 @@ async def on_cred_def(self, profile: Profile, event: CredDefFinishedEvent) -> No
)

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

async def on_registry_create_requested(
Expand Down
81 changes: 81 additions & 0 deletions acapy_agent/anoncreds/revocation/tests/test_revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,87 @@ async def test_decommission_registry_new_backup_creation_fails(self, mock_handle
assert len(result) == 2
assert result[0].tags["state"] == RevRegDefState.STATE_DECOMMISSIONED

@mock.patch.object(AskarAnonCredsProfileSession, "handle")
async def test_decommission_registry_duplicate_store_handled(self, mock_handle):
"""When event chain already stored the registry, duplicate is handled gracefully."""
mock_handle.fetch_all = mock.CoroutineMock(
side_effect=[
[
MockEntry(
name="backup-reg-reg",
tags={
"cred_def_id": "test-rev-reg-def-id",
"state": RevRegDefState.STATE_FINISHED,
"active": "false",
},
),
],
[
MockEntry(
name="active-reg-reg",
tags={
"cred_def_id": "test-rev-reg-def-id",
"state": RevRegDefState.STATE_FINISHED,
"active": "true",
},
),
MockEntry(
name="backup-reg-reg",
tags={
"cred_def_id": "test-rev-reg-def-id",
"state": RevRegDefState.STATE_FINISHED,
"active": "false",
},
),
MockEntry(
name="new-rev-reg",
tags={
"cred_def_id": "test-rev-reg-def-id",
"state": RevRegDefState.STATE_FINISHED,
"active": "false",
},
),
],
]
)
self.revocation.get_or_create_active_registry = mock.CoroutineMock(
return_value=RevRegDefResult(
job_id="test-job-id",
revocation_registry_definition_state=RevRegDefState(
state=RevRegDefState.STATE_FINISHED,
revocation_registry_definition_id="active-reg-reg",
revocation_registry_definition=rev_reg_def,
),
registration_metadata={},
revocation_registry_definition_metadata={},
)
)
self.revocation.create_and_register_revocation_registry_definition = (
mock.CoroutineMock(
return_value=RevRegDefResult(
job_id="test-job-id",
revocation_registry_definition_state=RevRegDefState(
state=RevRegDefState.STATE_ACTION,
revocation_registry_definition_id="new-rev-reg",
revocation_registry_definition=rev_reg_def,
),
registration_metadata={},
revocation_registry_definition_metadata={},
)
)
)
self.revocation.store_revocation_registry_definition = mock.CoroutineMock(
side_effect=test_module.AnonCredsRevocationError("Duplicate entry")
)
self.revocation.set_active_registry = mock.CoroutineMock(return_value=None)
mock_handle.replace = mock.CoroutineMock(return_value=None)

result = await self.revocation.decommission_registry("test-rev-reg-def-id")

assert isinstance(result, list)
self.revocation.store_revocation_registry_definition.assert_called_once()
self.revocation.set_active_registry.assert_called_once_with("backup-reg-reg")

@mock.patch.object(AskarAnonCredsProfileSession, "handle")
async def test_get_backup_registry_id_raises_when_no_backup(self, mock_handle):
"""_get_backup_registry_id raises when no finished backup exists."""
Expand Down
2 changes: 2 additions & 0 deletions acapy_agent/anoncreds/routes/revocation/registry/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ async def get_active_rev_reg(request: web.BaseRequest):
revocation = AnonCredsRevocation(profile)
active_reg = await revocation.get_or_create_active_registry(cred_def_id)
rev_reg = await _get_issuer_rev_reg_record(profile, active_reg.rev_reg_def_id)
except AnonCredsRevocationError as e:
raise web.HTTPNotFound(reason=str(e)) from e
except AnonCredsIssuerError as e:
raise web.HTTPInternalServerError(reason=str(e)) from e

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from .....issuer import AnonCredsIssuer
from .....models.issuer_cred_rev_record import IssuerCredRevRecord
from .....models.revocation import RevRegDef, RevRegDefState, RevRegDefValue
from .....revocation import AnonCredsRevocation
from .....revocation import AnonCredsRevocation, AnonCredsRevocationError
from .....tests.mock_objects import MockRevocationRegistryDefinition
from ....common.testing import BaseAnonCredsRouteTestCase
from .. import routes as test_module
from ..routes import (
get_active_rev_reg,
get_rev_reg_issued,
get_rev_reg_issued_count,
get_rev_regs,
Expand Down Expand Up @@ -178,6 +179,16 @@ async def test_active_registry_wrong_profile_403(self):
with self.assertRaises(web.HTTPForbidden):
await set_active_registry(self.request)

@mock.patch.object(
AnonCredsRevocation,
"get_or_create_active_registry",
side_effect=AnonCredsRevocationError("No active registry"),
)
async def test_get_active_rev_reg_not_found(self, mock_get):
self.request.match_info = {"cred_def_id": "test_cred_def_id"}
with self.assertRaises(web.HTTPNotFound):
await get_active_rev_reg(self.request)

async def test_get_rev_regs(self):
self.request.query = {
"cred_def_id": "test_cred_def_id",
Expand Down
23 changes: 23 additions & 0 deletions demo/runners/support/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,29 @@ async def register_schema_and_creddef_anoncreds(
"credential_definition_ids"
][0]
log_msg("Cred def ID:", credential_definition_id)

if support_revocation:
log_msg("Waiting for revocation registry to become active...")
poll_timeout = 60.0
poll_interval = 2.0
elapsed = 0.0
while elapsed < poll_timeout:
try:
active_reg = await self.admin_GET(
f"/anoncreds/revocation/active-registry/{credential_definition_id}"
)
if active_reg and active_reg.get("result"):
log_msg("Revocation registry is active.")
break
except Exception:
pass
await asyncio.sleep(poll_interval)
elapsed += poll_interval
else:
log_msg(
"WARNING: Revocation registry did not become active within timeout"
)

return schema_id, credential_definition_id

def get_agent_args(self):
Expand Down
Loading