Skip to content

Commit

Permalink
Merge pull request #1892 from ianco/fix/revoc_error
Browse files Browse the repository at this point in the history
Refactor ledger correction code and insert into revocation error handling
  • Loading branch information
ianco authored Aug 17, 2022
2 parents 5df3bc8 + 910aa20 commit 63f0a74
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 86 deletions.
23 changes: 23 additions & 0 deletions aries_cloudagent/revocation/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,29 @@ async def revoke_credential(
await issuer_rr_rec.mark_pending(txn, cred_rev_id)
await txn.commit()

async def update_rev_reg_revoked_state(
self,
apply_ledger_update: bool,
rev_reg_record: IssuerRevRegRecord,
genesis_transactions: dict,
) -> (dict, dict, dict):
"""
Request handler to fix ledger entry of credentials revoked against registry.
Args:
rev_reg_id: revocation registry id
apply_ledger_update: whether to apply an update to the ledger
Returns:
Number of credentials posted to ledger
"""
return await rev_reg_record.fix_ledger_entry(
self._profile,
apply_ledger_update,
genesis_transactions,
)

async def publish_pending_revocations(
self,
rrid2crid: Mapping[Text, Sequence[Text]] = None,
Expand Down
125 changes: 116 additions & 9 deletions aries_cloudagent/revocation/models/issuer_rev_reg_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from os.path import join
from pathlib import Path
from shutil import move
from typing import Any, Mapping, Sequence, Union
from typing import Any, Mapping, Sequence, Union, Tuple
from urllib.parse import urlparse

from marshmallow import fields, validate
Expand All @@ -22,6 +22,7 @@
)
from ...indy.util import indy_client_dir
from ...ledger.base import BaseLedger
from ...ledger.error import LedgerError, LedgerTransactionError
from ...messaging.models.base_record import BaseRecord, BaseRecordSchema
from ...messaging.valid import (
BASE58_SHA256_HASH,
Expand All @@ -33,7 +34,9 @@
from ...tails.base import BaseTailsServer

from ..error import RevocationError
from ..recover import generate_ledger_rrrecovery_txn

from .issuer_cred_rev_record import IssuerCredRevRecord
from .revocation_registry import RevocationRegistry

DEFAULT_REGISTRY_SIZE = 1000
Expand Down Expand Up @@ -290,14 +293,44 @@ async def send_entry(

ledger = profile.inject(BaseLedger)
async with ledger:
rev_entry_res = await ledger.send_revoc_reg_entry(
self.revoc_reg_id,
self.revoc_def_type,
self._revoc_reg_entry.ser,
self.issuer_did,
write_ledger=write_ledger,
endorser_did=endorser_did,
)
try:
rev_entry_res = await ledger.send_revoc_reg_entry(
self.revoc_reg_id,
self.revoc_def_type,
self._revoc_reg_entry.ser,
self.issuer_did,
write_ledger=write_ledger,
endorser_did=endorser_did,
)
except LedgerTransactionError as err:
if "InvalidClientRequest" in err.roll_up:
# ... if the ledger write fails (with "InvalidClientRequest")
# e.g. aries_cloudagent.ledger.error.LedgerTransactionError:
# Ledger rejected transaction request: client request invalid:
# InvalidClientRequest(...)
# In this scenario we try to post a correction
LOGGER.warn("Retry ledger update/fix due to error")
LOGGER.warn(err)
(_, _, res) = await self.fix_ledger_entry(
profile,
True,
ledger.pool.genesis_txns,
)
rev_entry_res = {"result": res}
LOGGER.warn("Ledger update/fix applied")
elif "InvalidClientTaaAcceptanceError" in err.roll_up:
# if no write access (with "InvalidClientTaaAcceptanceError")
# e.g. aries_cloudagent.ledger.error.LedgerTransactionError:
# Ledger rejected transaction request: client request invalid:
# InvalidClientTaaAcceptanceError(...)
LOGGER.error("Ledger update failed due to TAA issue")
LOGGER.error(err)
raise err
else:
# not sure what happened, raise an error
LOGGER.error("Ledger update failed due to unknown issue")
LOGGER.error(err)
raise err
if self.state == IssuerRevRegRecord.STATE_POSTED:
self.state = IssuerRevRegRecord.STATE_ACTIVE # initial entry activates
async with profile.session() as session:
Expand All @@ -307,6 +340,80 @@ async def send_entry(

return rev_entry_res

async def fix_ledger_entry(
self,
profile: Profile,
apply_ledger_update: bool,
genesis_transactions: str,
) -> Tuple[dict, dict, dict]:
"""Fix the ledger entry to match wallet-recorded credentials."""
# get rev reg delta (revocations published to ledger)
ledger = profile.inject(BaseLedger)
async with ledger:
(rev_reg_delta, _) = await ledger.get_revoc_reg_delta(self.revoc_reg_id)

# get rev reg records from wallet (revocations and status)
recs = []
rec_count = 0
accum_count = 0
recovery_txn = {}
applied_txn = {}
async with profile.session() as session:
recs = await IssuerCredRevRecord.query_by_ids(
session, rev_reg_id=self.revoc_reg_id
)

revoked_ids = []
for rec in recs:
if rec.state == IssuerCredRevRecord.STATE_REVOKED:
revoked_ids.append(int(rec.cred_rev_id))
if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]:
# await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED)
rec_count += 1

LOGGER.debug(">>> fixed entry recs count = %s", rec_count)
LOGGER.debug(
">>> rev_reg_record.revoc_reg_entry.value: %s",
self.revoc_reg_entry.value,
)
LOGGER.debug(
'>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value")
)

# if we had any revocation discrepencies, check the accumulator value
if rec_count > 0:
if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not (
self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"]
):
# self.revoc_reg_entry = rev_reg_delta["value"]
# await self.save(session)
accum_count += 1

calculated_txn = await generate_ledger_rrrecovery_txn(
genesis_transactions,
self.revoc_reg_id,
revoked_ids,
)
recovery_txn = json.loads(calculated_txn.to_json())

LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update)
if apply_ledger_update:
ledger = session.inject_or(BaseLedger)
if not ledger:
reason = "No ledger available"
if not session.context.settings.get_value("wallet.type"):
reason += ": missing wallet-type?"
raise LedgerError(reason=reason)

async with ledger:
ledger_response = await ledger.send_revoc_reg_entry(
self.revoc_reg_id, "CL_ACCUM", recovery_txn
)

applied_txn = ledger_response["result"]

return (rev_reg_delta, recovery_txn, applied_txn)

@property
def has_local_tails_file(self) -> bool:
"""Check if a local copy of the tails file is available."""
Expand Down
118 changes: 43 additions & 75 deletions aries_cloudagent/revocation/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
IssuerCredRevRecordSchema,
)
from .models.issuer_rev_reg_record import IssuerRevRegRecord, IssuerRevRegRecordSchema
from .recover import generate_ledger_rrrecovery_txn
from .util import (
REVOCATION_EVENT_PREFIX,
REVOCATION_REG_INIT_EVENT,
Expand Down Expand Up @@ -729,13 +728,13 @@ async def get_rev_reg_indy_recs(request: web.BaseRequest):
@response_schema(RevRegWalletUpdatedResultSchema(), 200, description="")
async def update_rev_reg_revoked_state(request: web.BaseRequest):
"""
Request handler to get number of credentials issued against revocation registry.
Request handler to fix ledger entry of credentials revoked against registry.
Args:
request: aiohttp request object
Returns:
Number of credentials updated in wallet
Number of credentials posted to ledger
"""
context: AdminRequestContext = request["context"]
Expand All @@ -746,89 +745,58 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest):
LOGGER.debug(">>> apply_ledger_update_json = %s", apply_ledger_update_json)
apply_ledger_update = json.loads(request.query.get("apply_ledger_update", "false"))

# get rev reg delta (revocations published to ledger)
revoc = IndyRevocation(context.profile)
rev_reg_delta = await revoc.get_issuer_rev_reg_delta(rev_reg_id)

# get rev reg records from wallet (revocations and status)
recs = []
rec_count = 0
accum_count = 0
recovery_txn = {}
applied_txn = {}
rev_reg_record = None
genesis_transactions = None
async with context.profile.session() as session:
try:
rev_reg_record = await IssuerRevRegRecord.retrieve_by_revoc_reg_id(
session, rev_reg_id
)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err
recs = await IssuerCredRevRecord.query_by_ids(session, rev_reg_id=rev_reg_id)

revoked_ids = []
for rec in recs:
if rec.state == IssuerCredRevRecord.STATE_REVOKED:
revoked_ids.append(int(rec.cred_rev_id))
if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]:
# await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED)
rec_count += 1

LOGGER.debug(">>> fixed entry recs count = %s", rec_count)
LOGGER.debug(
">>> rev_reg_record.revoc_reg_entry.value: %s",
rev_reg_record.revoc_reg_entry.value,
)
LOGGER.debug('>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value"))

# if we had any revocation discrepencies, check the accumulator value
if rec_count > 0:
if (
rev_reg_record.revoc_reg_entry.value and rev_reg_delta.get("value")
) and not (
rev_reg_record.revoc_reg_entry.value.accum
== rev_reg_delta["value"]["accum"]
):
# rev_reg_record.revoc_reg_entry = rev_reg_delta["value"]
# await rev_reg_record.save(session)
accum_count += 1

genesis_transactions = context.settings.get("ledger.genesis_transactions")
if not genesis_transactions:
ledger_manager = context.injector.inject(BaseMultipleLedgerManager)
write_ledgers = await ledger_manager.get_write_ledger()
LOGGER.debug(f"write_ledgers = {write_ledgers}")
pool = write_ledgers[1].pool
LOGGER.debug(f"write_ledger pool = {pool}")

genesis_transactions = pool.genesis_txns

if not genesis_transactions:
raise web.HTTPInternalServerError(
reason="no genesis_transactions for writable ledger"
)
genesis_transactions = context.settings.get("ledger.genesis_transactions")
if not genesis_transactions:
ledger_manager = context.injector.inject(BaseMultipleLedgerManager)
write_ledgers = await ledger_manager.get_write_ledger()
LOGGER.debug(f"write_ledgers = {write_ledgers}")
pool = write_ledgers[1].pool
LOGGER.debug(f"write_ledger pool = {pool}")

genesis_transactions = pool.genesis_txns

calculated_txn = await generate_ledger_rrrecovery_txn(
genesis_transactions,
rev_reg_id,
revoked_ids,
if not genesis_transactions:
raise web.HTTPInternalServerError(
reason="no genesis_transactions for writable ledger"
)
recovery_txn = json.loads(calculated_txn.to_json())

LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update)
if apply_ledger_update:
ledger = session.inject_or(BaseLedger)
if not ledger:
reason = "No ledger available"
if not session.context.settings.get_value("wallet.type"):
reason += ": missing wallet-type?"
raise web.HTTPInternalServerError(reason=reason)

async with ledger:
ledger_response = await ledger.send_revoc_reg_entry(
rev_reg_id, "CL_ACCUM", recovery_txn
)

applied_txn = ledger_response["result"]
if apply_ledger_update:
ledger = session.inject_or(BaseLedger)
if not ledger:
reason = "No ledger available"
if not session.context.settings.get_value("wallet.type"):
reason += ": missing wallet-type?"
raise web.HTTPInternalServerError(reason=reason)

rev_manager = RevocationManager(context.profile)
try:
(
rev_reg_delta,
recovery_txn,
applied_txn,
) = await rev_manager.update_rev_reg_revoked_state(
apply_ledger_update, rev_reg_record, genesis_transactions
)
except (
RevocationManagerError,
RevocationError,
StorageError,
IndyIssuerError,
LedgerError,
) as err:
raise web.HTTPBadRequest(reason=err.roll_up)
except Exception as err:
raise web.HTTPBadRequest(reason=str(err))

return web.json_response(
{
Expand Down
Loading

0 comments on commit 63f0a74

Please sign in to comment.