Skip to content

Commit 60d4fa1

Browse files
committed
Add distributed lock to anoncreds issuance
Signed-off-by: jamshale <jamiehalebc@gmail.com>
1 parent 8694816 commit 60d4fa1

1 file changed

Lines changed: 69 additions & 53 deletions

File tree

acapy_agent/anoncreds/revocation.py

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from uuid_utils import uuid4
2828

2929
from ..askar.profile_anon import AskarAnonCredsProfile, AskarAnonCredsProfileSession
30+
from ..core.async_lock.async_lock import AsyncLock
3031
from ..core.error import BaseError
3132
from ..core.event_bus import Event, EventBus
3233
from ..core.profile import Profile, ProfileSession
@@ -975,7 +976,9 @@ async def _create_credential(
975976
976977
"""
977978

978-
def _handle_missing_entries(rev_list: Entry, rev_reg_def: Entry, rev_key: Entry):
979+
def _raise_error_when_missing_entries(
980+
rev_list: Entry, rev_reg_def: Entry, rev_key: Entry
981+
):
979982
if not rev_list:
980983
raise AnonCredsRevocationError("Revocation registry list not found")
981984
if not rev_reg_def:
@@ -988,66 +991,79 @@ def _handle_missing_entries(rev_list: Entry, rev_reg_def: Entry, rev_key: Entry)
988991
def _has_required_id_and_tails_path():
989992
return rev_reg_def_id and tails_file_path
990993

994+
lock = self.profile.inject(AsyncLock)
991995
revoc = None
992996
credential_revocation_id = None
993997
rev_list = None
994998

995-
if _has_required_id_and_tails_path():
996-
async with self.profile.session() as session:
997-
rev_reg_def = await session.handle.fetch(
998-
CATEGORY_REV_REG_DEF, rev_reg_def_id
999-
)
1000-
rev_list = await session.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id)
1001-
rev_key = await session.handle.fetch(
1002-
CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id
1003-
)
1004-
1005-
_handle_missing_entries(rev_list, rev_reg_def, rev_key)
1006-
1007-
rev_list_value_json = rev_list.value_json
1008-
rev_list_tags = rev_list.tags
999+
# This lock is used to ensure that only one process on multiple instances
1000+
# can acquire the credential index for the same revocation registry.
1001+
# This is important to avoid because the same index could be used by multiple
1002+
# processes and cause issues with the revocation list.
1003+
# The lock is released when the taken index is updated on the revocation registry.
1004+
async with lock.lock(rev_reg_def_id, timeout=60):
1005+
if _has_required_id_and_tails_path():
1006+
async with self.profile.session() as session:
1007+
rev_reg_def = await session.handle.fetch(
1008+
CATEGORY_REV_REG_DEF, rev_reg_def_id
1009+
)
1010+
rev_list = await session.handle.fetch(
1011+
CATEGORY_REV_LIST, rev_reg_def_id
1012+
)
1013+
rev_key = await session.handle.fetch(
1014+
CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id
1015+
)
10091016

1010-
# If the rev_list state is failed then the tails file was never uploaded,
1011-
# try to upload it now and finish the revocation list
1012-
if rev_list_tags.get("state") == RevListState.STATE_FAILED:
1013-
await self.upload_tails_file(
1014-
RevRegDef.deserialize(rev_reg_def.value_json)
1015-
)
1016-
rev_list_tags["state"] = RevListState.STATE_FINISHED
1017+
_raise_error_when_missing_entries(rev_list, rev_reg_def, rev_key)
10171018

1018-
rev_reg_index = rev_list_value_json["next_index"]
1019-
try:
1020-
rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value)
1021-
rev_list = RevocationStatusList.load(rev_list_value_json["rev_list"])
1022-
except AnoncredsError as err:
1023-
raise AnonCredsRevocationError(
1024-
"Error loading revocation registry"
1025-
) from err
1019+
rev_list_value_json = rev_list.value_json
1020+
rev_list_tags = rev_list.tags
10261021

1027-
# NOTE: we increment the index ahead of time to keep the
1028-
# transaction short. The revocation registry itself will NOT
1029-
# be updated because we always use ISSUANCE_BY_DEFAULT.
1030-
# If something goes wrong later, the index will be skipped.
1031-
# FIXME - double check issuance type in case of upgraded wallet?
1032-
if rev_reg_index > rev_reg_def.max_cred_num:
1033-
raise AnonCredsRevocationRegistryFullError("Revocation registry is full")
1034-
rev_list_value_json["next_index"] = rev_reg_index + 1
1035-
async with self.profile.transaction() as txn:
1036-
await txn.handle.replace(
1037-
CATEGORY_REV_LIST,
1038-
rev_reg_def_id,
1039-
value_json=rev_list_value_json,
1040-
tags=rev_list_tags,
1041-
)
1042-
await txn.commit()
1022+
# If the rev_list state is failed then the tails file was never uploaded,
1023+
# try to upload it now and finish the revocation list
1024+
if rev_list_tags.get("state") == RevListState.STATE_FAILED:
1025+
await self.upload_tails_file(
1026+
RevRegDef.deserialize(rev_reg_def.value_json)
1027+
)
1028+
rev_list_tags["state"] = RevListState.STATE_FINISHED
10431029

1044-
revoc = CredentialRevocationConfig(
1045-
rev_reg_def,
1046-
rev_key.raw_value,
1047-
rev_list,
1048-
rev_reg_index,
1049-
)
1050-
credential_revocation_id = str(rev_reg_index)
1030+
rev_reg_index = rev_list_value_json["next_index"]
1031+
try:
1032+
rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value)
1033+
rev_list = RevocationStatusList.load(rev_list_value_json["rev_list"])
1034+
except AnoncredsError as err:
1035+
raise AnonCredsRevocationError(
1036+
"Error loading revocation registry"
1037+
) from err
1038+
1039+
# NOTE: we increment the index ahead of time to keep the
1040+
# transaction short. The revocation registry itself will NOT
1041+
# be updated because we always use ISSUANCE_BY_DEFAULT.
1042+
# If something goes wrong later, the index will be skipped.
1043+
# FIXME - double check issuance type in case of upgraded wallet?
1044+
if rev_reg_index > rev_reg_def.max_cred_num:
1045+
raise AnonCredsRevocationRegistryFullError(
1046+
"Revocation registry is full"
1047+
)
1048+
rev_list_value_json["next_index"] = rev_reg_index + 1
1049+
async with self.profile.transaction() as txn:
1050+
await txn.handle.replace(
1051+
CATEGORY_REV_LIST,
1052+
rev_reg_def_id,
1053+
value_json=rev_list_value_json,
1054+
tags=rev_list_tags,
1055+
)
1056+
await txn.commit()
1057+
# Free the distributed lock after commit the revocation list update
1058+
# by exiting with the async with block.
1059+
1060+
revoc = CredentialRevocationConfig(
1061+
rev_reg_def,
1062+
rev_key.raw_value,
1063+
rev_list,
1064+
rev_reg_index,
1065+
)
1066+
credential_revocation_id = str(rev_reg_index)
10511067

10521068
cred_def, cred_def_private = await self._get_cred_def_objects(
10531069
credential_definition_id

0 commit comments

Comments
 (0)