Skip to content
Open
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
31 changes: 22 additions & 9 deletions acapy_agent/revocation/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ..core.profile import Profile
from ..ledger.base import BaseLedger
from ..ledger.error import LedgerError
from ..ledger.multiple_ledger.ledger_requests_executor import (
GET_CRED_DEF,
GET_REVOC_REG_DEF,
Expand Down Expand Up @@ -260,6 +261,7 @@ async def get_or_create_active_registry(
except StorageNotFoundError:
pass

posted_registries = []
async with self._profile.session() as session:
full_registries = await IssuerRevRegRecord.query_by_cred_def_id(
session, cred_def_id, None, IssuerRevRegRecord.STATE_FULL, 1
Expand All @@ -280,19 +282,30 @@ async def get_or_create_active_registry(
cred_def_id,
max_cred_num=any_registry.max_cred_num,
)
# if there is a posted registry, activate oldest
# if there is a posted registry, complete its setup and activate it
else:
posted_registries = await IssuerRevRegRecord.query_by_cred_def_id(
session, cred_def_id, IssuerRevRegRecord.STATE_POSTED, None, None
)
if posted_registries:
posted_registries = sorted(
posted_registries, key=lambda r: r.created_at
)
await self._set_registry_status(
revoc_reg_id=posted_registries[0].revoc_reg_id,
state=IssuerRevRegRecord.STATE_ACTIVE,
)

if posted_registries:
# A registry is only left posted when its setup stalled after the
# definition reached the ledger: the tails file upload and/or the
# initial accumulator entry are still outstanding. Complete those
# steps instead of activating with a bare state change, which would
# break every credential later issued against the registry
# (unresolvable tailsLocation, missing initial entry).
rec = min(posted_registries, key=lambda r: r.created_at)
try:
await rec.upload_tails_file(self._profile)
# publishing the initial entry transitions posted -> active
await rec.send_entry(self._profile)
except (RevocationError, LedgerError):
LOGGER.exception(
"Setup of posted revocation registry %s could not be completed; "
"not activating (will retry on next issuance attempt)",
rec.revoc_reg_id,
)
return None

async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry:
Expand Down
48 changes: 44 additions & 4 deletions acapy_agent/revocation/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,51 @@ async def test_get_or_create_active_registry_has_no_active_or_any_registry(self,
],
],
)
@mock.patch.object(IssuerRevRegRecord, "upload_tails_file", mock.CoroutineMock())
@mock.patch.object(IssuerRevRegRecord, "send_entry", mock.CoroutineMock())
async def test_get_or_create_active_registry_has_no_active_with_posted(self, *_):
result = await self.revoc.get_or_create_active_registry("cred_def_id")

assert not result
assert (
self.revoc._set_registry_status.call_args.kwargs["state"]
== IssuerRevRegRecord.STATE_ACTIVE
)
# the stalled setup pipeline is completed rather than bare-activated:
# the tails file is uploaded and the initial entry published (which
# transitions the record posted -> active)
IssuerRevRegRecord.upload_tails_file.assert_awaited_once()
IssuerRevRegRecord.send_entry.assert_awaited_once()
self.revoc._set_registry_status.assert_not_called()

@mock.patch(
"acapy_agent.revocation.indy.IndyRevocation.get_active_issuer_rev_reg_record",
mock.CoroutineMock(side_effect=StorageNotFoundError("No such record")),
)
@mock.patch(
"acapy_agent.revocation.indy.IndyRevocation._set_registry_status",
mock.CoroutineMock(return_value=None),
)
@mock.patch.object(
IssuerRevRegRecord,
"query_by_cred_def_id",
side_effect=[
[IssuerRevRegRecord(max_cred_num=3)],
[
IssuerRevRegRecord(
revoc_reg_id="test-rev-reg-id",
state=IssuerRevRegRecord.STATE_POSTED,
)
],
],
)
@mock.patch.object(
IssuerRevRegRecord,
"upload_tails_file",
mock.CoroutineMock(side_effect=RevocationError("tails server unavailable")),
)
@mock.patch.object(IssuerRevRegRecord, "send_entry", mock.CoroutineMock())
async def test_get_or_create_active_registry_posted_setup_incomplete(self, *_):
result = await self.revoc.get_or_create_active_registry("cred_def_id")

assert not result
# the registry must stay posted: activating it without a tails file
# on the server would break credentials issued against it
IssuerRevRegRecord.send_entry.assert_not_called()
self.revoc._set_registry_status.assert_not_called()
Loading