Skip to content

Commit

Permalink
refactor / fix test
Browse files Browse the repository at this point in the history
Signed-off-by: Jamie Hale <jamiehalebc@gmail.com>
  • Loading branch information
jamshale committed Oct 21, 2024
1 parent 5e7c45f commit f8efffd
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 153 deletions.
157 changes: 79 additions & 78 deletions acapy_agent/revocation/models/issuer_rev_reg_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,107 +362,108 @@ async def send_entry(

return rev_entry_res

def _get_revoked_discrepancies(
self, recs: Sequence[IssuerCredRevRecord], rev_reg_delta: dict
) -> Tuple[list, int]:
revoked_ids = []
rec_count = 0
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"]:
rec_count += 1

return revoked_ids, rec_count

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."""
recovery_txn = {}
applied_txn = {}

# 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
revoked_ids, rec_count = self._get_revoked_discrepancies(recs, rev_reg_delta)

LOGGER.debug(f"Fixed entry recs count = {rec_count}")
LOGGER.debug(f"Rev reg entry value: {self.revoc_reg_entry.value}")
LOGGER.debug(f'Rev reg delta: {rev_reg_delta.get("value")}')

# No update required if no discrepancies
if rec_count == 0:
return (rev_reg_delta, {}, {})

# We have revocation discrepancies, generate the recovery txn
async with profile.session() as session:
# We need the cred_def and rev_reg_def_private to generate the recovery txn
issuer_rev_reg_record = await IssuerRevRegRecord.retrieve_by_revoc_reg_id(
session, self.revoc_reg_id
)
cred_def_id = issuer_rev_reg_record.cred_def_id
cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id)
rev_reg_def_private = await session.handle.fetch(
CATEGORY_REV_REG_DEF_PRIVATE, self.revoc_reg_id
)

LOGGER.debug(">>> fixed entry recs count = %s", rec_count)
LOGGER.debug(
">>> rev_reg_record.revoc_reg_entry.value: %s",
self.revoc_reg_entry.value,
credx_module = importlib.import_module("indy_credx")
cred_defn = credx_module.CredentialDefinition.load(cred_def.value_json)
rev_reg_defn_private = credx_module.RevocationRegistryDefinitionPrivate.load(
rev_reg_def_private.value_json
)
calculated_txn = await generate_ledger_rrrecovery_txn(
genesis_transactions,
self.revoc_reg_id,
revoked_ids,
cred_defn,
rev_reg_defn_private,
)
LOGGER.debug('>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value"))

# if we had any revocation discrepancies, 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
recovery_txn = json.loads(calculated_txn.to_json())

LOGGER.debug(f"Applying ledger update: {apply_ledger_update}")
if apply_ledger_update:
async with profile.session() as session:
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"]

# Update the local wallets rev reg entry with the new accumulator value
async with profile.session() as session:
issuer_rev_reg_record = await IssuerRevRegRecord.retrieve_by_revoc_reg_id(
session, self.revoc_reg_id
rev_reg = await session.handle.fetch(
CATEGORY_REV_REG, self.revoc_reg_id, for_update=True
)
cred_def_id = issuer_rev_reg_record.cred_def_id
_cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id)
_rev_reg_def_private = await session.handle.fetch(
CATEGORY_REV_REG_DEF_PRIVATE, self.revoc_reg_id
new_value_json = rev_reg.value_json
new_value_json["value"]["accum"] = applied_txn["txn"]["data"]["value"][
"accum"
]
await session.handle.replace(
CATEGORY_REV_REG,
rev_reg.name,
json.dumps(new_value_json),
rev_reg.tags,
)
credx_module = importlib.import_module("indy_credx")
cred_defn = credx_module.CredentialDefinition.load(_cred_def.value_json)
rev_reg_defn_private = credx_module.RevocationRegistryDefinitionPrivate.load(
_rev_reg_def_private.value_json
)
calculated_txn = await generate_ledger_rrrecovery_txn(
genesis_transactions,
self.revoc_reg_id,
revoked_ids,
cred_defn,
rev_reg_defn_private,
)
recovery_txn = json.loads(calculated_txn.to_json())

LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update)
if apply_ledger_update:
async with profile.session() as session:
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"]

# Update the wallets rev reg entry with the new accumulator value
async with profile.session() as session:
rev_reg = await session.handle.fetch(
CATEGORY_REV_REG, self.revoc_reg_id, for_update=True
)
new_value_json = rev_reg.value_json
new_value_json["value"]["accum"] = applied_txn["txn"]["data"][
"value"
]["accum"]
await session.handle.replace(
CATEGORY_REV_REG,
rev_reg.name,
json.dumps(new_value_json),
rev_reg.tags,
)

return (rev_reg_delta, recovery_txn, applied_txn)

Expand Down
107 changes: 32 additions & 75 deletions acapy_agent/revocation/models/tests/test_issuer_rev_reg_record.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import importlib
import json
from os.path import join
from typing import Any, Mapping, Optional, Type
from unittest import IsolatedAsyncioTestCase

from acapy_agent.tests import mock

from ....config.injection_context import InjectionContext
from ....core.in_memory import InMemoryProfile, InMemoryProfileSession
from ....core.profile import Profile, ProfileSession
from ....core.in_memory import InMemoryProfile
from ....core.in_memory.profile import InMemoryProfileSession
from ....indy.issuer import IndyIssuer, IndyIssuerError
from ....indy.util import indy_client_dir
from ....ledger.base import BaseLedger
Expand Down Expand Up @@ -40,6 +38,7 @@
},
"tailsHash": TAILS_HASH,
"tailsLocation": TAILS_URI,
"accum": "21 11792B036AED0AAA12A46CF39347EB35C865DAC99F767B286F6E37FF0FF4F1CBE 21 12571556D2A1B4475E81295FC8A4F0B66D00FB78EE8C7E15C29C2CA862D0217D4 6 92166D2C2A3BC621AD615136B7229AF051AB026704BF8874F9F0B0106122BF4F 4 2C47BCBBC32904161E2A2926F120AD8F40D94C09D1D97DA735191D27370A68F8F 6 8CC19FDA63AB16BEA45050D72478115BC1CCB8E47A854339D2DD5E112976FFF7 4 298B2571FFC63A737B79C131AC7048A1BD474BF907AF13BC42E533C79FB502C7",
},
}
REV_REG_ENTRY = {
Expand Down Expand Up @@ -80,7 +79,8 @@ async def test_order(self):
await rec1.save(session, reason="another record")
assert rec0 < rec1

async def test_fix_ledger_entry(self):
@mock.patch.object(InMemoryProfileSession, "handle")
async def test_fix_ledger_entry(self, mock_handle):
mock_cred_def = {
"ver": "1.0",
"id": "55GkHamhTU1ZbTbV2ab9DE:3:CL:15:tag",
Expand Down Expand Up @@ -119,73 +119,14 @@ async def test_fix_ledger_entry(self):
}
}

class TestProfile(InMemoryProfile):
def session(
self, context: Optional[InjectionContext] = None
) -> "ProfileSession":
return TestProfileSession(self, context=context)

@classmethod
def test_profile(
cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None
) -> "TestProfile":
profile = TestProfile(
context=InjectionContext(enforce_typing=False, settings=settings),
name=InMemoryProfile.TEST_PROFILE_NAME,
)
if bind:
for k, v in bind.items():
if v:
profile.context.injector.bind_instance(k, v)
else:
profile.context.injector.clear_binding(k)
return profile

@classmethod
def test_session(
cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None
) -> "TestProfileSession":
session = TestProfileSession(cls.test_profile(), settings=settings)
session._active = True
session._init_context()
if bind:
for k, v in bind.items():
if v:
session.context.injector.bind_instance(k, v)
else:
session.context.injector.clear_binding(k)
return session

class TestProfileSession(InMemoryProfileSession):
def __init__(
self,
profile: Profile,
*,
context: Optional[InjectionContext] = None,
settings: Mapping[str, Any] = None,
):
super().__init__(profile=profile, context=context, settings=settings)
self.handle_counter = 0

@property
def handle(self):
if self.handle_counter == 0:
self.handle_counter = self.handle_counter + 1
return mock.MagicMock(
fetch=mock.CoroutineMock(
return_value=mock.MagicMock(
value_json=json.dumps(mock_cred_def)
)
)
)
else:
return mock.MagicMock(
fetch=mock.CoroutineMock(
return_value=mock.MagicMock(
value_json=json.dumps(mock_reg_rev_def_private),
),
)
)
mock_handle.fetch = mock.CoroutineMock(
side_effect=[
mock.MagicMock(value_json=json.dumps(mock_cred_def)),
mock.MagicMock(value_json=json.dumps(mock_reg_rev_def_private)),
mock.MagicMock(value_json=REV_REG_DEF),
]
)
mock_handle.replace = mock.CoroutineMock()

credx_module = importlib.import_module("indy_credx")
rev_reg_delta_json = json.dumps(
Expand Down Expand Up @@ -225,10 +166,18 @@ def handle(self):
)
self.ledger.send_revoc_reg_entry = mock.CoroutineMock(
return_value={
"result": {"...": "..."},
"result": {
"txn": {
"data": {
"value": {
"accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000"
}
}
}
},
},
)
_test_session = TestProfile.test_session(
_test_session = InMemoryProfile.test_session(
settings={"tails_server_base_url": "http://1.2.3.4:8088"},
)
_test_profile = _test_session.profile
Expand Down Expand Up @@ -264,7 +213,15 @@ def handle(self):
"accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000"
},
},
{"...": "..."},
{
"txn": {
"data": {
"value": {
"accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000"
},
}
}
},
) == await rec.fix_ledger_entry(
profile=_test_profile,
apply_ledger_update=True,
Expand Down

0 comments on commit f8efffd

Please sign in to comment.