Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
341e7ab
:recycle: Improve event notification handling
ff137 Apr 29, 2025
24a137c
:loud_sound: Add warning log if event bus not found
ff137 Apr 29, 2025
3502cfb
:sparkles::recycle: Make EventBus.notify run tasks in background, usi…
ff137 Sep 9, 2025
9ff583e
:test_tube: Fix test
ff137 Sep 9, 2025
b064088
:test_tube: Update EventBus tests
ff137 Sep 9, 2025
47cc505
:test_tube: Conftest with event loop fixture
ff137 Sep 9, 2025
78e49b1
:white_check_mark: Test coverage for EventBus.shutdown
ff137 Sep 9, 2025
79d73f0
:art: Fix sonarcloud issues
ff137 Sep 9, 2025
67b440f
:art: Note: notify is now synchronous, but kept async for compatability
ff137 Sep 9, 2025
4e64814
:test_tube: Include improvements to exception checking
ff137 Sep 18, 2025
f4d7c01
:art:
ff137 Sep 18, 2025
728ee66
:sparkles: Add "wait_for_revocation_setup" option
ff137 Sep 18, 2025
12a3906
:truck: Move constants to own module, to resolve cyclic import issues
ff137 Sep 18, 2025
6986ef5
:art: Fix deprecated usage
ff137 Sep 18, 2025
6c81099
:test_tube: Test coverage for waiting for revocation setup
ff137 Sep 18, 2025
c72fe84
:art: Fix imports
ff137 Sep 18, 2025
accad4b
:art:
ff137 Sep 18, 2025
21a97d2
:art: Remove timeout parameter
ff137 Sep 18, 2025
144d988
:bug: Fix default value for wait_for_revocation_setup
ff137 Sep 18, 2025
76237ee
:sparkles: Add "wait_for_revocation_setup" option to indy case as well
ff137 Sep 18, 2025
16f7151
:art: Modify wait for active registry logic
ff137 Sep 18, 2025
a213f43
:art: Fix cyclic import
ff137 Sep 18, 2025
036a673
:test_tube: Fix tests
ff137 Sep 18, 2025
74df932
:bug: Fix cyclic import issue by moving method to util module
ff137 Sep 18, 2025
ff77710
:bug: Fix yet another circular import issue
ff137 Sep 18, 2025
86b9153
:art: Modify exception type
ff137 Sep 19, 2025
900a18f
:truck: Move "wait" method and call in event handler
ff137 Sep 19, 2025
3cbc94f
:wrench: Override wait setting if auto-create is false
ff137 Sep 19, 2025
a5485f1
:test_tube: Update tests
ff137 Sep 19, 2025
53951d9
:test_tube: Update and expand unit test coverage
ff137 Sep 19, 2025
1247747
:test_tube: Fix reusing an already awaited coroutine
ff137 Sep 19, 2025
9ea7429
:construction: Debug failing scenario test
ff137 Sep 19, 2025
55dc87f
:bug: Handle possible "No active registry" error
ff137 Sep 19, 2025
1c2def5
:art: Wait for 2 active registries
ff137 Sep 19, 2025
33598bf
:test_tube: Update tests
ff137 Sep 19, 2025
f6742b9
:test_tube: Fix unawaited coroutine ...
ff137 Sep 19, 2025
469e4d6
:bug: Retry credential issuance if no active registry
ff137 Sep 19, 2025
b4280ce
:wrench: Update default timeout to 60s
ff137 Sep 19, 2025
e69042a
:art: Cleanup
ff137 Sep 19, 2025
d7dbaf9
:art: Remove redundant tests
ff137 Sep 22, 2025
bdfbf00
:art: Remove redundant warning log
ff137 Sep 22, 2025
9f69285
:art: Ruff lint fixes
ff137 Oct 8, 2025
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
21 changes: 21 additions & 0 deletions acapy_agent/anoncreds/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Constants for AnonCreds."""

DEFAULT_CRED_DEF_TAG = "default"
DEFAULT_MAX_CRED_NUM = 1000
DEFAULT_SIGNATURE_TYPE = "CL"

CATEGORY_SCHEMA = "schema"

CATEGORY_CRED_DEF = "credential_def"
CATEGORY_CRED_DEF_PRIVATE = "credential_def_private"
CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof"

CATEGORY_REV_LIST = "revocation_list"
CATEGORY_REV_REG_DEF = "revocation_reg_def"
CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private"

STATE_FINISHED = "finished"
STATE_REVOCATION_PENDING = "pending"
STATE_REVOCATION_POSTED = "posted"

REV_REG_DEF_STATE_ACTIVE = "active"
10 changes: 5 additions & 5 deletions acapy_agent/anoncreds/default/legacy_indy/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
BaseAnonCredsRegistrar,
BaseAnonCredsResolver,
)
from ...constants import (
CATEGORY_REV_LIST,
CATEGORY_REV_REG_DEF,
CATEGORY_REV_REG_DEF_PRIVATE,
)
from ...events import RevListFinishedEvent
from ...issuer import CATEGORY_CRED_DEF, AnonCredsIssuer, AnonCredsIssuerError
from ...models.credential_definition import (
Expand All @@ -76,11 +81,6 @@
)
from ...models.schema import AnonCredsSchema, GetSchemaResult, SchemaResult, SchemaState
from ...models.schema_info import AnonCredsSchemaInfo
from ...revocation import (
CATEGORY_REV_LIST,
CATEGORY_REV_REG_DEF,
CATEGORY_REV_REG_DEF_PRIVATE,
)
from .recover import generate_ledger_rrrecovery_txn

LOGGER = logging.getLogger(__name__)
Expand Down
23 changes: 14 additions & 9 deletions acapy_agent/anoncreds/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
from ..database_manager.db_errors import DBError
from ..protocols.endorse_transaction.v1_0.util import is_author_role
from .base import AnonCredsSchemaAlreadyExists, BaseAnonCredsError
from .constants import (
CATEGORY_CRED_DEF,
CATEGORY_CRED_DEF_KEY_PROOF,
CATEGORY_CRED_DEF_PRIVATE,
CATEGORY_SCHEMA,
DEFAULT_CRED_DEF_TAG,
DEFAULT_MAX_CRED_NUM,
DEFAULT_SIGNATURE_TYPE,
STATE_FINISHED,
)
from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from .events import CredDefFinishedEvent
from .models.credential_definition import CredDef, CredDefResult
Expand All @@ -30,15 +40,6 @@

LOGGER = logging.getLogger(__name__)

DEFAULT_CRED_DEF_TAG = "default"
DEFAULT_SIGNATURE_TYPE = "CL"
DEFAULT_MAX_CRED_NUM = 1000
CATEGORY_SCHEMA = "schema"
CATEGORY_CRED_DEF = "credential_def"
CATEGORY_CRED_DEF_PRIVATE = "credential_def_private"
CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof"
STATE_FINISHED = "finished"

EVENT_PREFIX = "acapy::anoncreds::"
EVENT_SCHEMA = EVENT_PREFIX + CATEGORY_SCHEMA
EVENT_CRED_DEF = EVENT_PREFIX + CATEGORY_CRED_DEF
Expand Down Expand Up @@ -406,6 +407,7 @@ async def store_credential_definition(
) -> None:
"""Store the cred def and it's components in the wallet."""
options = options or {}

identifier = (
cred_def_result.job_id
or cred_def_result.credential_definition_state.credential_definition_id
Expand Down Expand Up @@ -444,6 +446,7 @@ async def store_credential_definition(
CATEGORY_CRED_DEF_KEY_PROOF, identifier, key_proof.to_json_buffer()
)
await txn.commit()

if cred_def_result.credential_definition_state.state == STATE_FINISHED:
await self.notify(
CredDefFinishedEvent.with_payload(
Expand All @@ -463,6 +466,8 @@ async def finish_cred_def(
self, job_id: str, cred_def_id: str, options: Optional[dict] = None
) -> None:
"""Finish a cred def."""
options = options or {}

async with self.profile.transaction() as txn:
entry = await self._finish_registration(
txn, CATEGORY_CRED_DEF, job_id, cred_def_id
Expand Down
6 changes: 0 additions & 6 deletions acapy_agent/anoncreds/revocation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@
from .manager import RevocationManager, RevocationManagerError
from .recover import RevocRecoveryException, fetch_txns, generate_ledger_rrrecovery_txn
from .revocation import (
CATEGORY_REV_LIST,
CATEGORY_REV_REG_DEF,
CATEGORY_REV_REG_DEF_PRIVATE,
AnonCredsRevocation,
AnonCredsRevocationError,
AnonCredsRevocationRegistryFullError,
)
from .revocation_setup import DefaultRevocationSetup

__all__ = [
"CATEGORY_REV_LIST",
"CATEGORY_REV_REG_DEF",
"CATEGORY_REV_REG_DEF_PRIVATE",
"AnonCredsRevocation",
"AnonCredsRevocationError",
"AnonCredsRevocationRegistryFullError",
Expand Down
96 changes: 85 additions & 11 deletions acapy_agent/anoncreds/revocation/revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@
from ...database_manager.db_errors import DBError
from ...kanon.profile_anon_kanon import KanonAnonCredsProfileSession # type: ignore
from ...tails.anoncreds_tails_server import AnonCredsTailsServer
from ..error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from ..events import RevListFinishedEvent, RevRegDefFinishedEvent
from ..issuer import (
from ..constants import (
CATEGORY_CRED_DEF,
CATEGORY_CRED_DEF_PRIVATE,
CATEGORY_REV_LIST,
CATEGORY_REV_REG_DEF,
CATEGORY_REV_REG_DEF_PRIVATE,
STATE_FINISHED,
AnonCredsIssuer,
)
from ..error_messages import ANONCREDS_PROFILE_REQUIRED_MSG
from ..events import RevListFinishedEvent, RevRegDefFinishedEvent
from ..issuer import AnonCredsIssuer
from ..models.credential_definition import CredDef
from ..models.revocation import (
RevList,
Expand All @@ -54,12 +57,9 @@

LOGGER = logging.getLogger(__name__)

CATEGORY_REV_LIST = "revocation_list"
CATEGORY_REV_REG_DEF = "revocation_reg_def"
CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private"
STATE_REVOCATION_POSTED = "posted"
STATE_REVOCATION_PENDING = "pending"
REV_REG_DEF_STATE_ACTIVE = "active"
REVOCATION_REGISTRY_CREATION_TIMEOUT = float(
os.getenv("REVOCATION_REGISTRY_CREATION_TIMEOUT", "60.0")
)


class AnonCredsRevocationError(BaseError):
Expand Down Expand Up @@ -389,6 +389,73 @@ async def set_active_registry(self, rev_reg_def_id: str) -> None:
)
await txn.commit()

async def wait_for_active_revocation_registry(self, cred_def_id: str) -> None:
"""Wait for revocation registry setup to complete.

Polls for the creation of revocation registry definitions until we have
the 1 active registry or timeout occurs.

Args:
cred_def_id: The credential definition ID

Raises:
TimeoutError: If timeout occurs before completion

"""
LOGGER.debug(
"Waiting for revocation setup completion for cred_def_id: %s", cred_def_id
)

expected_count = 1 # Active registry
poll_interval = 0.5 # Poll every 500ms
max_iterations = int(REVOCATION_REGISTRY_CREATION_TIMEOUT / poll_interval)
registries = []

for _iteration in range(max_iterations):
try:
# Check for finished revocation registry definitions
async with self.profile.session() as session:
registries = await session.handle.fetch_all(
CATEGORY_REV_REG_DEF,
{"cred_def_id": cred_def_id, "active": "true"},
)

current_count = len(registries)
LOGGER.debug(
"Revocation setup progress for %s: %d/%d registries active",
cred_def_id,
current_count,
expected_count,
)

if current_count >= expected_count:
LOGGER.info(
"Revocation setup completed for cred_def_id: %s "
"(%d registries active)",
cred_def_id,
current_count,
)
return

except Exception as e:
LOGGER.warning(
"Error checking revocation setup progress for %s: %s", cred_def_id, e
)
# Continue polling despite errors - they might be transient

await asyncio.sleep(poll_interval) # Wait before next poll

# Timeout occurred
current_count = len(registries)

raise TimeoutError(
"Timeout waiting for revocation setup completion for credential definition "
f"{cred_def_id}. Expected {expected_count} revocation registries, but "
f"{current_count} were active within {REVOCATION_REGISTRY_CREATION_TIMEOUT} "
"seconds. Note: Revocation registry creation may still be in progress in the "
"background. You can check status using the revocation registry endpoints."
)

async def create_and_register_revocation_list(
self, rev_reg_def_id: str, options: Optional[dict] = None
) -> RevListResult:
Expand Down Expand Up @@ -1151,7 +1218,14 @@ async def _create_credential_helper(

rev_reg_def_result = None
if revocable:
rev_reg_def_result = await self.get_or_create_active_registry(cred_def_id)
try:
rev_reg_def_result = await self.get_or_create_active_registry(
cred_def_id
)
except AnonCredsRevocationError:
# No active registry, try again
continue

if (
rev_reg_def_result.revocation_registry_definition_state.state
!= STATE_FINISHED
Expand Down
4 changes: 4 additions & 0 deletions acapy_agent/anoncreds/revocation/revocation_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ async def on_cred_def(self, profile: Profile, event: CredDefFinishedEvent) -> No
options=payload.options,
)

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_rev_reg_def(
self, profile: Profile, event: RevRegDefFinishedEvent
) -> None:
Expand Down
Loading
Loading