diff --git a/aries_cloudagent/config/ledger.py b/aries_cloudagent/config/ledger.py index 750ba314ff..99d8c6b3d6 100644 --- a/aries_cloudagent/config/ledger.py +++ b/aries_cloudagent/config/ledger.py @@ -17,6 +17,7 @@ from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerError +from ..multitenant.base import BaseMultitenantManager from ..utils.http import fetch, FetchError from ..wallet.base import BaseWallet @@ -137,7 +138,15 @@ async def ledger_config( async with ledger: # Check transaction author agreement acceptance if not ledger.read_only: - taa_info = await ledger.get_txn_author_agreement() + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + taa_info = await ledger.get_txn_author_agreement( + sign_did_info=sign_did_info + ) + else: + taa_info = await ledger.get_txn_author_agreement() if taa_info["taa_required"] and public_did: taa_accepted = await ledger.get_latest_txn_author_acceptance() if ( diff --git a/aries_cloudagent/config/multi_ledger_config.yml b/aries_cloudagent/config/multi_ledger_config.yml new file mode 100644 index 0000000000..138bd46dd6 --- /dev/null +++ b/aries_cloudagent/config/multi_ledger_config.yml @@ -0,0 +1,11 @@ +- id: bcovrin1 + is_production: true + is_write: true + genesis_url: 'http://test.bcovrin.vonx.io/genesis' + endorser_did: '8nFG9mvdT44tBh3wksrYXD' + endorser_alias: 'endorser_1' +- id: bcovrin2 + is_write: true + genesis_url: 'http://dev.bcovrin.vonx.io/genesis' + endorser_did: 'L9b2uHgsabypjMmhF6xREm' + endorser_alias: 'endorser_2' \ No newline at end of file diff --git a/aries_cloudagent/config/wallet.py b/aries_cloudagent/config/wallet.py index a49aae304f..d037f3f6c0 100644 --- a/aries_cloudagent/config/wallet.py +++ b/aries_cloudagent/config/wallet.py @@ -1,12 +1,19 @@ """Wallet configuration.""" import logging -from typing import Tuple +import json + +from typing import Tuple, Mapping, Any from ..core.error import ProfileNotFoundError from ..core.profile import Profile, ProfileManager, ProfileSession -from ..storage.base import BaseStorage -from ..storage.error import StorageNotFoundError +from ..ledger.base import BaseLedger +from ..storage.base import BaseStorage, StorageRecord +from ..storage.error import ( + StorageNotFoundError, + StorageDuplicateError, + StorageError, +) from ..version import RECORD_TYPE_ACAPY_VERSION, __version__ from ..wallet.base import BaseWallet from ..wallet.crypto import seed_to_did @@ -29,6 +36,63 @@ } +def is_multi_ledger(settings: Mapping[str, Any]) -> bool: + """Check whether mutli-ledger mode is enabled.""" + if ( + settings.get_value("ledger.ledger_config_list") + and len(settings.get_value("ledger.ledger_config_list")) >= 1 + ): + return True + return False + + +async def update_public_did_ledger_id_map( + session, + info: DIDInfo, + config: dict, +): + """Add or update acapy_ledger_public_did_map storage record.""" + _curr_write_ledger = session.inject_or(BaseLedger) + storage = session.inject_or(BaseStorage) + ledger_id = config.get("ledger_pool_name") or _curr_write_ledger.pool_name + record_type_ledger_did_map = ( + config.get("record_type_name") or "acapy_ledger_public_did_map" + ) + try: + ledger_id_public_did_map_record: StorageRecord = await storage.find_record( + type_filter=record_type_ledger_did_map, tag_query={} + ) + ledger_id_public_did_map = json.loads(ledger_id_public_did_map_record.value) + ledger_id_public_did_map[ledger_id] = { + "did": info.did, + "routing_keys": config.get("routing_keys"), + "mediation_endpoint": config.get("mediator_endpoint"), + "connection_id": config.get("connection_id"), + "write_ledger": config.get("write_ledger"), + } + await storage.update_record( + ledger_id_public_did_map_record, + json.dumps(ledger_id_public_did_map), + {}, + ) + except (StorageError, StorageNotFoundError, StorageDuplicateError): + ledger_id_public_did_map = { + ledger_id: { + "did": info.did, + "routing_keys": config.get("routing_keys"), + "mediation_endpoint": config.get("mediator_endpoint"), + "connection_id": config.get("connection_id"), + "write_ledger": config.get("write_ledger"), + } + } + record = StorageRecord( + record_type_ledger_did_map, + json.dumps(ledger_id_public_did_map), + {}, + ) + await storage.add_record(record) + + async def wallet_config( context: InjectionContext, provision: bool = False ) -> Tuple[Profile, DIDInfo]: @@ -110,7 +174,10 @@ async def wallet_config( print(f"Verkey: {local_did_info.verkey}") else: public_did_info = await wallet.create_public_did( - method=SOV, key_type=ED25519, seed=wallet_seed + method=SOV, + key_type=ED25519, + seed=wallet_seed, + context=context, ) public_did = public_did_info.did if provision: diff --git a/aries_cloudagent/indy/credx/holder.py b/aries_cloudagent/indy/credx/holder.py index d593838319..8e32603d6a 100644 --- a/aries_cloudagent/indy/credx/holder.py +++ b/aries_cloudagent/indy/credx/holder.py @@ -21,6 +21,8 @@ from ...askar.profile import AskarProfile from ...ledger.base import BaseLedger +from ...multitenant.base import BaseMultitenantManager +from ...wallet.base import BaseWallet from ...wallet.error import WalletNotFoundError from ..holder import IndyHolder, IndyHolderError @@ -389,11 +391,23 @@ async def credential_revoked( if rev_reg_id: cred_rev_id = cred.rev_reg_index - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_reg_id, - fro, - to, - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + sign_did_info, + ) + else: + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + ) return cred_rev_id in rev_reg_delta["value"].get("revoked", []) else: return False diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index e2ab4c3d4a..648d9877f4 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -54,11 +54,11 @@ def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" @abstractmethod - async def is_ledger_read_only(self) -> bool: + async def is_ledger_read_only(self, sign_did_info: DIDInfo = None) -> bool: """Check if ledger is read-only including TAA.""" @abstractmethod - async def get_key_for_did(self, did: str) -> str: + async def get_key_for_did(self, did: str, sign_did_info: DIDInfo = None) -> str: """Fetch the verkey for a ledger DID. Args: @@ -67,7 +67,10 @@ async def get_key_for_did(self, did: str) -> str: @abstractmethod async def get_endpoint_for_did( - self, did: str, endpoint_type: EndpointType = EndpointType.ENDPOINT + self, + did: str, + endpoint_type: EndpointType = EndpointType.ENDPOINT, + sign_did_info: DIDInfo = None, ) -> str: """Fetch the endpoint for a ledger DID. @@ -77,7 +80,9 @@ async def get_endpoint_for_did( """ @abstractmethod - async def get_all_endpoints_for_did(self, did: str) -> dict: + async def get_all_endpoints_for_did( + self, did: str, sign_did_info: DIDInfo = None + ) -> dict: """Fetch all endpoints for a ledger DID. Args: @@ -142,6 +147,7 @@ async def register_nym( role: str = None, write_ledger: bool = True, endorser_did: str = None, + sign_did_info: DIDInfo = None, ) -> Tuple[bool, dict]: """Register a nym on the ledger. @@ -153,7 +159,7 @@ async def register_nym( """ @abstractmethod - async def get_nym_role(self, did: str): + async def get_nym_role(self, did: str, sign_did_info: DIDInfo = None): """Return the role registered to input public DID on the ledger. Args: @@ -165,7 +171,9 @@ def nym_to_did(self, nym: str) -> str: """Format a nym with the ledger's DID prefix.""" @abstractmethod - async def rotate_public_did_keypair(self, next_seed: str = None) -> None: + async def rotate_public_did_keypair( + self, next_seed: str = None, sign_did_info: DIDInfo = None + ) -> None: """Rotate keypair for public DID: create new key, submit to ledger, update wallet. Args: @@ -182,11 +190,13 @@ async def get_wallet_public_did(self) -> DIDInfo: """Fetch the public DID from the wallet.""" @abstractmethod - async def get_txn_author_agreement(self, reload: bool = False): + async def get_txn_author_agreement( + self, reload: bool = False, sign_did_info: DIDInfo = None + ): """Get the current transaction author agreement, fetching it if necessary.""" @abstractmethod - async def fetch_txn_author_agreement(self): + async def fetch_txn_author_agreement(self, sign_did_info: DIDInfo = None): """Fetch the current AML and TAA from the ledger.""" @abstractmethod @@ -226,7 +236,9 @@ async def txn_submit( """Write the provided (signed and possibly endorsed) transaction to the ledger.""" @abstractmethod - async def fetch_schema_by_id(self, schema_id: str) -> dict: + async def fetch_schema_by_id( + self, schema_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get schema from ledger. Args: @@ -255,10 +267,11 @@ async def check_existing_schema( schema_name: str, schema_version: str, attribute_names: Sequence[str], + sign_did_info: DIDInfo = None, ) -> Tuple[str, dict]: """Check if a schema has already been published.""" fetch_schema_id = f"{public_did}:2:{schema_name}:{schema_version}" - schema = await self.fetch_schema_by_id(fetch_schema_id) + schema = await self.fetch_schema_by_id(fetch_schema_id, sign_did_info) if schema: fetched_attrs = schema["attrNames"].copy() fetched_attrs.sort() @@ -279,6 +292,7 @@ async def create_and_send_schema( attribute_names: Sequence[str], write_ledger: bool = True, endorser_did: str = None, + sign_did_info: DIDInfo = None, ) -> Tuple[str, dict]: """Send schema to ledger. @@ -290,7 +304,7 @@ async def create_and_send_schema( """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError("Cannot publish schema without a public DID") @@ -306,7 +320,7 @@ async def create_and_send_schema( LOGGER.warning("Schema already exists on ledger. Returning details.") schema_id, schema_def = schema_info else: - if await self.is_ledger_read_only(): + if await self.is_ledger_read_only(sign_did_info): raise LedgerError( "Error cannot write schema when ledger is in read only mode, " "or TAA is required and not accepted" @@ -383,7 +397,9 @@ async def _create_schema_request( """Create the ledger request for publishing a schema.""" @abstractmethod - async def get_revoc_reg_def(self, revoc_reg_id: str) -> dict: + async def get_revoc_reg_def( + self, revoc_reg_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Look up a revocation registry definition by ID.""" @abstractmethod @@ -417,6 +433,7 @@ async def create_and_send_credential_definition( support_revocation: bool = False, write_ledger: bool = True, endorser_did: str = None, + sign_did_info: DIDInfo = None, ) -> Tuple[str, dict, bool]: """Send credential definition to ledger and store relevant key matter in wallet. @@ -431,7 +448,7 @@ async def create_and_send_credential_definition( Tuple with cred def id, cred def structure, and whether it's novel """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError( "Cannot publish credential definition without a public DID" @@ -449,7 +466,8 @@ async def create_and_send_credential_definition( public_info.did, schema, signature_type, test_tag ) ledger_cred_def = await self.fetch_credential_definition( - credential_definition_id + credential_definition_id, + sign_did_info, ) if ledger_cred_def: LOGGER.warning( @@ -501,7 +519,7 @@ async def create_and_send_credential_definition( except IndyIssuerError as err: raise LedgerError(err.message) from err - if await self.is_ledger_read_only(): + if await self.is_ledger_read_only(sign_did_info): raise LedgerError( "Error cannot write cred def when ledger is in read only mode, " "or TAA is required and not accepted" @@ -543,12 +561,16 @@ async def get_credential_definition(self, credential_definition_id: str) -> dict @abstractmethod async def get_revoc_reg_delta( - self, revoc_reg_id: str, timestamp_from=0, timestamp_to=None + self, + revoc_reg_id: str, + timestamp_from=0, + timestamp_to=None, + sign_did_info: DIDInfo = None, ) -> Tuple[dict, int]: """Look up a revocation registry delta by ID.""" @abstractmethod - async def get_schema(self, schema_id: str) -> dict: + async def get_schema(self, schema_id: str, sign_did_info: DIDInfo = None) -> dict: """Get a schema from the cache if available, otherwise fetch from the ledger. Args: @@ -558,7 +580,7 @@ async def get_schema(self, schema_id: str) -> dict: @abstractmethod async def get_revoc_reg_entry( - self, revoc_reg_id: str, timestamp: int + self, revoc_reg_id: str, timestamp: int, sign_did_info: DIDInfo = None ) -> Tuple[dict, int]: """Get revocation registry entry by revocation registry ID and timestamp.""" diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index 436923951f..0f66ed7b65 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -292,12 +292,12 @@ def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" return self.pool.read_only - async def is_ledger_read_only(self) -> bool: + async def is_ledger_read_only(self, sign_did_info: DIDInfo = None) -> bool: """Check if ledger is read-only including TAA.""" if self.read_only: return self.read_only # if TAA is required and not accepted we should be in read-only mode - taa = await self.get_txn_author_agreement() + taa = await self.get_txn_author_agreement(sign_did_info=sign_did_info) if taa["taa_required"]: taa_acceptance = await self.get_latest_txn_author_acceptance() if "mechanism" not in taa_acceptance: @@ -483,7 +483,7 @@ async def _create_schema_request( return request_json - async def get_schema(self, schema_id: str) -> dict: + async def get_schema(self, schema_id: str, sign_did_info: DIDInfo = None) -> dict: """Get a schema from the cache if available, otherwise fetch from the ledger. Args: @@ -498,9 +498,11 @@ async def get_schema(self, schema_id: str) -> dict: if schema_id.isdigit(): return await self.fetch_schema_by_seq_no(int(schema_id)) else: - return await self.fetch_schema_by_id(schema_id) + return await self.fetch_schema_by_id(schema_id, sign_did_info) - async def fetch_schema_by_id(self, schema_id: str) -> dict: + async def fetch_schema_by_id( + self, schema_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get schema from ledger. Args: @@ -511,7 +513,7 @@ async def fetch_schema_by_id(self, schema_id: str) -> dict: """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building schema request", LedgerError): @@ -591,7 +593,9 @@ async def _create_credential_definition_request( return request_json - async def get_credential_definition(self, credential_definition_id: str) -> dict: + async def get_credential_definition( + self, credential_definition_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get a credential definition from the cache if available, otherwise the ledger. Args: @@ -605,9 +609,13 @@ async def get_credential_definition(self, credential_definition_id: str) -> dict if result: return result - return await self.fetch_credential_definition(credential_definition_id) + return await self.fetch_credential_definition( + credential_definition_id, sign_did_info + ) - async def fetch_credential_definition(self, credential_definition_id: str) -> dict: + async def fetch_credential_definition( + self, credential_definition_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get a credential definition from the ledger by id. Args: @@ -615,7 +623,7 @@ async def fetch_credential_definition(self, credential_definition_id: str) -> di """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building cred def request", LedgerError): @@ -664,14 +672,14 @@ async def credential_definition_id2schema_id(self, credential_definition_id): seq_no = tokens[3] return (await self.get_schema(seq_no))["id"] - async def get_key_for_did(self, did: str) -> str: + async def get_key_for_did(self, did: str, sign_did_info: DIDInfo = None) -> str: """Fetch the verkey for a ledger DID. Args: did: The DID to look up on the ledger or in the cache """ nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building nym request", LedgerError): request_json = await indy.ledger.build_get_nym_request(public_did, nym) @@ -679,14 +687,18 @@ async def get_key_for_did(self, did: str) -> str: data_json = (json.loads(response_json))["result"]["data"] return full_verkey(did, json.loads(data_json)["verkey"]) if data_json else None - async def get_all_endpoints_for_did(self, did: str) -> dict: + async def get_all_endpoints_for_did( + self, + did: str, + sign_did_info: DIDInfo = None, + ) -> dict: """Fetch all endpoints for a ledger DID. Args: did: The DID to look up on the ledger or in the cache """ nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building attribute request", LedgerError): request_json = await indy.ledger.build_get_attrib_request( @@ -703,7 +715,10 @@ async def get_all_endpoints_for_did(self, did: str) -> dict: return endpoints async def get_endpoint_for_did( - self, did: str, endpoint_type: EndpointType = None + self, + did: str, + endpoint_type: EndpointType = None, + sign_did_info: DIDInfo = None, ) -> str: """Fetch the endpoint for a ledger DID. @@ -715,7 +730,7 @@ async def get_endpoint_for_did( if not endpoint_type: endpoint_type = EndpointType.ENDPOINT nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building attribute request", LedgerError): request_json = await indy.ledger.build_get_attrib_request( @@ -739,6 +754,7 @@ async def update_endpoint_for_did( write_ledger: bool = True, endorser_did: str = None, routing_keys: List[str] = None, + sign_did_info: DIDInfo = None, ) -> bool: """Check and update the endpoint on the ledger. @@ -747,7 +763,7 @@ async def update_endpoint_for_did( endpoint: The endpoint address endpoint_type: The type of the endpoint """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError( "Cannot update endpoint at ledger without a public DID" @@ -756,7 +772,7 @@ async def update_endpoint_for_did( if not endpoint_type: endpoint_type = EndpointType.ENDPOINT - all_exist_endpoints = await self.get_all_endpoints_for_did(did) + all_exist_endpoints = await self.get_all_endpoints_for_did(did, sign_did_info) exist_endpoint_of_type = ( all_exist_endpoints.get(endpoint_type.indy, None) if all_exist_endpoints @@ -764,7 +780,7 @@ async def update_endpoint_for_did( ) if exist_endpoint_of_type != endpoint: - if await self.is_ledger_read_only(): + if await self.is_ledger_read_only(sign_did_info): raise LedgerError( "Error cannot update endpoint when ledger is in read only mode, " "or TAA is required and not accepted" @@ -807,6 +823,7 @@ async def register_nym( role: str = None, write_ledger: bool = True, endorser_did: str = None, + sign_did_info: DIDInfo = None, ) -> Tuple[bool, dict]: """Register a nym on the ledger. @@ -816,13 +833,13 @@ async def register_nym( alias: Human-friendly alias to assign to the DID. role: For permissioned ledgers, what role should the new DID have. """ - if await self.is_ledger_read_only(): + if await self.is_ledger_read_only(sign_did_info): raise LedgerError( "Error cannot register nym when ledger is in read only mode, " "or TAA is required and not accepted" ) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise WalletNotFoundError( f"Cannot register NYM to ledger: wallet {self.profile.name} " @@ -852,13 +869,13 @@ async def register_nym( await wallet.replace_local_did_metadata(did, metadata) return True, None - async def get_nym_role(self, did: str) -> Role: + async def get_nym_role(self, did: str, sign_did_info: DIDInfo = None) -> Role: """Return the role of the input public DID's NYM on the ledger. Args: did: DID to query for role on the ledger. """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None with IndyErrorHandler("Exception building get-nym request", LedgerError): @@ -894,7 +911,9 @@ async def submit_get_nym_request(self, request_json: str) -> str: response_json = await self._submit(request_json) return response_json - async def rotate_public_did_keypair(self, next_seed: str = None) -> None: + async def rotate_public_did_keypair( + self, next_seed: str = None, sign_did_info: DIDInfo = None + ) -> None: """Rotate keypair for public DID: create new key, submit to ledger, update wallet. Args: @@ -933,22 +952,26 @@ async def rotate_public_did_keypair(self, next_seed: str = None) -> None: txn_data_data = txn_resp_data["txn"]["data"] role_token = Role.get(txn_data_data.get("role")).token() alias = txn_data_data.get("alias") - await self.register_nym(public_did, verkey, role_token, alias) + await self.register_nym( + public_did, verkey, role_token, alias, sign_did_info=sign_did_info + ) # update wallet async with self.profile.session() as session: wallet = session.inject(BaseWallet) await wallet.rotate_did_keypair_apply(public_did) - async def get_txn_author_agreement(self, reload: bool = False) -> dict: + async def get_txn_author_agreement( + self, reload: bool = False, sign_did_info: DIDInfo = None + ) -> dict: """Get the current transaction author agreement, fetching it if necessary.""" if not self.pool.taa_cache or reload: - self.pool.taa_cache = await self.fetch_txn_author_agreement() + self.pool.taa_cache = await self.fetch_txn_author_agreement(sign_did_info) return self.pool.taa_cache - async def fetch_txn_author_agreement(self) -> dict: + async def fetch_txn_author_agreement(self, sign_did_info: DIDInfo = None) -> dict: """Fetch the current AML and TAA from the ledger.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None get_aml_req = await indy.ledger.build_get_acceptance_mechanisms_request( @@ -1047,9 +1070,11 @@ async def get_latest_txn_author_acceptance(self) -> dict: ) return acceptance - async def get_revoc_reg_def(self, revoc_reg_id: str) -> dict: + async def get_revoc_reg_def( + self, revoc_reg_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get revocation registry definition by ID; augment with ledger timestamp.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() try: fetch_req = await indy.ledger.build_get_revoc_reg_def_request( public_info and public_info.did, revoc_reg_id @@ -1072,9 +1097,11 @@ async def get_revoc_reg_def(self, revoc_reg_id: str) -> dict: assert found_id == revoc_reg_id return found_def - async def get_revoc_reg_entry(self, revoc_reg_id: str, timestamp: int): + async def get_revoc_reg_entry( + self, revoc_reg_id: str, timestamp: int, sign_did_info: DIDInfo = None + ): """Get revocation registry entry by revocation registry ID and timestamp.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() with IndyErrorHandler("Exception fetching rev reg entry", LedgerError): try: fetch_req = await indy.ledger.build_get_revoc_reg_request( @@ -1096,7 +1123,7 @@ async def get_revoc_reg_entry(self, revoc_reg_id: str, timestamp: int): return json.loads(found_reg_json), ledger_timestamp async def get_revoc_reg_delta( - self, revoc_reg_id: str, fro=0, to=None + self, revoc_reg_id: str, fro=0, to=None, sign_did_info: DIDInfo = None ) -> Tuple[dict, int]: """Look up a revocation registry delta by ID. @@ -1108,7 +1135,7 @@ async def get_revoc_reg_delta( """ if to is None: to = int(time()) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() with IndyErrorHandler("Exception building rev reg delta request", LedgerError): fetch_req = await indy.ledger.build_get_revoc_reg_delta_request( public_info and public_info.did, diff --git a/aries_cloudagent/ledger/indy_vdr.py b/aries_cloudagent/ledger/indy_vdr.py index d4b7dbada5..834b913453 100644 --- a/aries_cloudagent/ledger/indy_vdr.py +++ b/aries_cloudagent/ledger/indy_vdr.py @@ -282,12 +282,12 @@ def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" return self.pool.read_only - async def is_ledger_read_only(self) -> bool: + async def is_ledger_read_only(self, sign_did_info: DIDInfo = None) -> bool: """Check if ledger is read-only including TAA.""" if self.read_only: return self.read_only # if TAA is required and not accepted we should be in read-only mode - taa = await self.get_txn_author_agreement() + taa = await self.get_txn_author_agreement(sign_did_info=sign_did_info) if taa["taa_required"]: taa_acceptance = await self.get_latest_txn_author_acceptance() if "mechanism" not in taa_acceptance: @@ -393,7 +393,7 @@ async def _create_schema_request( return schema_req - async def get_schema(self, schema_id: str) -> dict: + async def get_schema(self, schema_id: str, sign_did_info: DIDInfo = None) -> dict: """Get a schema from the cache if available, otherwise fetch from the ledger. Args: @@ -408,9 +408,11 @@ async def get_schema(self, schema_id: str) -> dict: if schema_id.isdigit(): return await self.fetch_schema_by_seq_no(int(schema_id)) else: - return await self.fetch_schema_by_id(schema_id) + return await self.fetch_schema_by_id(schema_id, sign_did_info) - async def fetch_schema_by_id(self, schema_id: str) -> dict: + async def fetch_schema_by_id( + self, schema_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get schema from ledger. Args: @@ -421,7 +423,7 @@ async def fetch_schema_by_id(self, schema_id: str) -> dict: """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None try: @@ -509,7 +511,9 @@ async def _create_credential_definition_request( return cred_def_req - async def get_credential_definition(self, credential_definition_id: str) -> dict: + async def get_credential_definition( + self, credential_definition_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get a credential definition from the cache if available, otherwise the ledger. Args: @@ -523,15 +527,19 @@ async def get_credential_definition(self, credential_definition_id: str) -> dict result = entry.result else: result = await self.fetch_credential_definition( - credential_definition_id + credential_definition_id, sign_did_info ) if result: await entry.set_result(result, self.pool.cache_duration) return result - return await self.fetch_credential_definition(credential_definition_id) + return await self.fetch_credential_definition( + credential_definition_id, sign_did_info + ) - async def fetch_credential_definition(self, credential_definition_id: str) -> dict: + async def fetch_credential_definition( + self, credential_definition_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get a credential definition from the ledger by id. Args: @@ -539,7 +547,7 @@ async def fetch_credential_definition(self, credential_definition_id: str) -> di """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None try: @@ -587,14 +595,14 @@ async def credential_definition_id2schema_id(self, credential_definition_id): seq_no = tokens[3] return (await self.get_schema(seq_no))["id"] - async def get_key_for_did(self, did: str) -> str: + async def get_key_for_did(self, did: str, sign_did_info: DIDInfo = None) -> str: """Fetch the verkey for a ledger DID. Args: did: The DID to look up on the ledger or in the cache """ nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None # current public_did may be non-indy -> create nym request with empty public did @@ -610,14 +618,16 @@ async def get_key_for_did(self, did: str) -> str: data_json = response["data"] return json.loads(data_json)["verkey"] if data_json else None - async def get_all_endpoints_for_did(self, did: str) -> dict: + async def get_all_endpoints_for_did( + self, did: str, sign_did_info: DIDInfo = None + ) -> dict: """Fetch all endpoints for a ledger DID. Args: did: The DID to look up on the ledger or in the cache """ nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None try: attrib_req = ledger.build_get_attrib_request( @@ -637,7 +647,10 @@ async def get_all_endpoints_for_did(self, did: str) -> dict: return endpoints async def get_endpoint_for_did( - self, did: str, endpoint_type: EndpointType = None + self, + did: str, + endpoint_type: EndpointType = None, + sign_did_info: DIDInfo = None, ) -> str: """Fetch the endpoint for a ledger DID. @@ -649,7 +662,7 @@ async def get_endpoint_for_did( if not endpoint_type: endpoint_type = EndpointType.ENDPOINT nym = self.did_to_nym(did) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None try: attrib_req = ledger.build_get_attrib_request( @@ -676,6 +689,7 @@ async def update_endpoint_for_did( write_ledger: bool = True, endorser_did: str = None, routing_keys: List[str] = None, + sign_did_info: DIDInfo = None, ) -> bool: """Check and update the endpoint on the ledger. @@ -684,7 +698,7 @@ async def update_endpoint_for_did( endpoint: The endpoint address endpoint_type: The type of the endpoint """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError( "Cannot update endpoint at ledger without a public DID" @@ -693,7 +707,7 @@ async def update_endpoint_for_did( if not endpoint_type: endpoint_type = EndpointType.ENDPOINT - all_exist_endpoints = await self.get_all_endpoints_for_did(did) + all_exist_endpoints = await self.get_all_endpoints_for_did(did, sign_did_info) exist_endpoint_of_type = ( all_exist_endpoints.get(endpoint_type.indy, None) if all_exist_endpoints @@ -743,6 +757,7 @@ async def register_nym( role: str = None, write_ledger: bool = True, endorser_did: str = None, + sign_did_info: DIDInfo = None, ) -> Tuple[bool, dict]: """Register a nym on the ledger. @@ -757,7 +772,7 @@ async def register_nym( "Error cannot register nym when ledger is in read only mode" ) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError("Cannot register NYM without a public DID") @@ -787,13 +802,13 @@ async def register_nym( await wallet.replace_local_did_metadata(did, metadata) return True, None - async def get_nym_role(self, did: str) -> Role: + async def get_nym_role(self, did: str, sign_did_info: DIDInfo = None) -> Role: """Return the role of the input public DID's NYM on the ledger. Args: did: DID to query for role on the ledger. """ - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None try: @@ -830,7 +845,9 @@ async def submit_get_nym_request(self, request_json: str) -> str: response_json = await self._submit(request_json) return response_json - async def rotate_public_did_keypair(self, next_seed: str = None) -> None: + async def rotate_public_did_keypair( + self, next_seed: str = None, sign_did_info: DIDInfo = None + ) -> None: """Rotate keypair for public DID: create new key, submit to ledger, update wallet. Args: @@ -875,7 +892,13 @@ async def rotate_public_did_keypair(self, next_seed: str = None) -> None: role_token = Role.get(txn_data_data.get("role")).token() alias = txn_data_data.get("alias") # submit the updated nym record - await self.register_nym(public_did, verkey, alias=alias, role=role_token) + await self.register_nym( + public_did, + verkey, + alias=alias, + role=role_token, + sign_did_info=sign_did_info, + ) # update wallet async with self.profile.transaction() as txn: @@ -884,15 +907,17 @@ async def rotate_public_did_keypair(self, next_seed: str = None) -> None: del wallet await txn.commit() - async def get_txn_author_agreement(self, reload: bool = False) -> dict: + async def get_txn_author_agreement( + self, reload: bool = False, sign_did_info: DIDInfo = None + ) -> dict: """Get the current transaction author agreement, fetching it if necessary.""" if not self.pool.taa_cache or reload: - self.pool.taa_cache = await self.fetch_txn_author_agreement() + self.pool.taa_cache = await self.fetch_txn_author_agreement(sign_did_info) return self.pool.taa_cache - async def fetch_txn_author_agreement(self) -> dict: + async def fetch_txn_author_agreement(self, sign_did_info: DIDInfo = None) -> dict: """Fetch the current AML and TAA from the ledger.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() public_did = public_info.did if public_info else None get_aml_req = ledger.build_get_acceptance_mechanisms_request( @@ -975,9 +1000,11 @@ async def get_latest_txn_author_acceptance(self) -> dict: ) return acceptance - async def get_revoc_reg_def(self, revoc_reg_id: str) -> dict: + async def get_revoc_reg_def( + self, revoc_reg_id: str, sign_did_info: DIDInfo = None + ) -> dict: """Get revocation registry definition by ID.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() try: fetch_req = ledger.build_get_revoc_reg_def_request( public_info and public_info.did, revoc_reg_id @@ -999,10 +1026,10 @@ async def get_revoc_reg_def(self, revoc_reg_id: str) -> dict: return revoc_reg_def async def get_revoc_reg_entry( - self, revoc_reg_id: str, timestamp: int + self, revoc_reg_id: str, timestamp: int, sign_did_info: DIDInfo = None ) -> Tuple[dict, int]: """Get revocation registry entry by revocation registry ID and timestamp.""" - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() try: fetch_req = ledger.build_get_revoc_reg_request( public_info and public_info.did, revoc_reg_id, timestamp @@ -1025,7 +1052,11 @@ async def get_revoc_reg_entry( return reg_entry, ledger_timestamp async def get_revoc_reg_delta( - self, revoc_reg_id: str, timestamp_from=0, timestamp_to=None + self, + revoc_reg_id: str, + timestamp_from=0, + timestamp_to=None, + sign_did_info: DIDInfo = None, ) -> Tuple[dict, int]: """Look up a revocation registry delta by ID. @@ -1037,7 +1068,7 @@ async def get_revoc_reg_delta( """ if timestamp_to is None: timestamp_to = int(time()) - public_info = await self.get_wallet_public_did() + public_info = sign_did_info or await self.get_wallet_public_did() try: fetch_req = ledger.build_get_revoc_reg_delta_request( public_info and public_info.did, diff --git a/aries_cloudagent/ledger/multiple_ledger/base_manager.py b/aries_cloudagent/ledger/multiple_ledger/base_manager.py index 69e1f2d26c..7fc089348d 100644 --- a/aries_cloudagent/ledger/multiple_ledger/base_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/base_manager.py @@ -1,13 +1,22 @@ """Manager for multiple ledger.""" +import json + from abc import ABC, abstractmethod from typing import Optional, Tuple, Mapping, List +from ...admin.request_context import AdminRequestContext from ...core.error import BaseError from ...core.profile import Profile from ...ledger.base import BaseLedger +from ...storage.base import BaseStorage, StorageRecord +from ...storage.error import StorageNotFoundError, StorageDuplicateError, StorageError from ...messaging.valid import IndyDID from ...multitenant.manager import BaseMultitenantManager +from ...wallet.base import BaseWallet, DEFAULT_PUBLIC_DID +from ...wallet.routes import promote_wallet_public_did + +RECORD_TYPE_LEDGER_PUBLIC_DID_MAP = "acapy_ledger_public_did_map" class MultipleLedgerManagerError(BaseError): @@ -28,10 +37,6 @@ def get_endorser_info_for_ledger(self, ledger_id: str) -> Optional[Tuple[str, st async def get_write_ledgers(self) -> List[str]: """Return write ledger.""" - @abstractmethod - async def get_ledger_id_by_ledger_pool_name(self, pool_name: str) -> str: - """Return ledger_id by ledger pool name.""" - @abstractmethod async def get_prod_ledgers(self) -> Mapping: """Return configured production ledgers.""" @@ -63,8 +68,22 @@ def extract_did_from_identifier(self, identifier: str) -> str: else: return identifier.split(":")[0] - async def set_profile_write_ledger(self, ledger_id: str, profile: Profile) -> str: + async def get_ledger_id_by_ledger_pool_name(self, pool_name: str) -> str: + """Return ledger_id by ledger pool name.""" + multi_ledgers = self.production_ledgers | self.non_production_ledgers + for ledger_id, ledger in multi_ledgers.items(): + if ledger.pool_name == pool_name: + return ledger_id + raise MultipleLedgerManagerError( + f"Provided Ledger pool name {pool_name} not found " + "in either production_ledgers or non_production_ledgers" + ) + + async def set_profile_write_ledger( + self, ledger_id: str, context: AdminRequestContext + ) -> str: """Set the write ledger for the profile.""" + profile = context.profile if ledger_id not in self.writable_ledgers: raise MultipleLedgerManagerError( f"Provided Ledger identifier {ledger_id} is not write configurable." @@ -83,6 +102,68 @@ async def set_profile_write_ledger(self, ledger_id: str, profile: Profile) -> st profile.context.settings["wallet.id"], extra_settings, ) + set_default_public_did = False + try: + async with profile.session() as session: + storage = session.inject_or(BaseStorage) + write_ledger = session.inject(BaseLedger) + ledger_id_public_did_map_record: StorageRecord = ( + await storage.find_record( + type_filter=RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + tag_query={}, + ) + ) + ledger_id = await self.get_ledger_id_by_ledger_pool_name( + write_ledger.pool_name + ) + ledger_id_public_did_map = json.loads( + ledger_id_public_did_map_record.value + ) + public_did_config = ledger_id_public_did_map.get(ledger_id) + if public_did_config: + info, _ = await promote_wallet_public_did( + profile=profile, + context=context, + session_fn=context.session, + did=public_did_config.get("did"), + write_ledger=public_did_config.get("write_ledger"), + connection_id=public_did_config.get("connection_id"), + routing_keys=public_did_config.get("routing_keys"), + mediator_endpoint=public_did_config.get( + "mediator_endpoint" + ), + ledger_pool_name=write_ledger.pool_name, + record_type_name=RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + ) + assert info + set_default_public_did = False + else: + set_default_public_did = True + except ( + StorageError, + StorageNotFoundError, + StorageDuplicateError, + ): + set_default_public_did = True + if set_default_public_did: + try: + async with self.profile.session() as session: + storage = session.inject_or(BaseStorage) + wallet = session.inject_or(BaseWallet) + default_public_did_record: StorageRecord = ( + await storage.find_record( + type_filter=DEFAULT_PUBLIC_DID, tag_query={} + ) + ) + default_public_did = default_public_did_record.value + info = await wallet.set_public_did(default_public_did) + assert info + except ( + StorageError, + StorageNotFoundError, + StorageDuplicateError, + ): + pass return ledger_id raise MultipleLedgerManagerError(f"No ledger info found for {ledger_id}.") diff --git a/aries_cloudagent/ledger/multiple_ledger/indy_manager.py b/aries_cloudagent/ledger/multiple_ledger/indy_manager.py index cf379b7913..34cd128354 100644 --- a/aries_cloudagent/ledger/multiple_ledger/indy_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/indy_manager.py @@ -79,17 +79,6 @@ async def get_nonprod_ledgers(self) -> Mapping: """Return non_production ledgers mapping.""" return self.non_production_ledgers - async def get_ledger_id_by_ledger_pool_name(self, pool_name: str) -> str: - """Return ledger_id by ledger pool name.""" - multi_ledgers = self.production_ledgers | self.non_production_ledgers - for ledger_id, indy_vdr_ledger in multi_ledgers.items(): - if indy_vdr_ledger.pool_name == pool_name: - return ledger_id - raise MultipleLedgerManagerError( - f"Provided Ledger pool name {pool_name} not found " - "in either production_ledgers or non_production_ledgers" - ) - async def _get_ledger_by_did( self, ledger_id: str, diff --git a/aries_cloudagent/ledger/multiple_ledger/indy_vdr_manager.py b/aries_cloudagent/ledger/multiple_ledger/indy_vdr_manager.py index 8d2c51c576..2a25b5e058 100644 --- a/aries_cloudagent/ledger/multiple_ledger/indy_vdr_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/indy_vdr_manager.py @@ -79,17 +79,6 @@ async def get_ledger_inst_by_id(self, ledger_id: str) -> Optional[BaseLedger]: ledger_id ) or self.non_production_ledgers.get(ledger_id) - async def get_ledger_id_by_ledger_pool_name(self, pool_name: str) -> str: - """Return ledger_id by ledger pool name.""" - multi_ledgers = self.production_ledgers | self.non_production_ledgers - for ledger_id, indy_vdr_ledger in multi_ledgers.items(): - if indy_vdr_ledger.pool_name == pool_name: - return ledger_id - raise MultipleLedgerManagerError( - f"Provided Ledger pool name {pool_name} not found " - "in either production_ledgers or non_production_ledgers" - ) - async def _get_ledger_by_did( self, ledger_id: str, diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py index f280c8b06c..27f9209990 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py @@ -8,6 +8,7 @@ from collections import OrderedDict +from ....admin.request_context import AdminRequestContext from ....cache.base import BaseCache from ....cache.in_memory import InMemoryCache from ....core.in_memory import InMemoryProfile @@ -113,8 +114,13 @@ async def test_set_profile_write_ledger(self): profile = InMemoryProfile.test_profile() assert not profile.inject_or(BaseLedger) assert "test_prod_2" in manager.writable_ledgers + test_ctx = AdminRequestContext( + profile=profile, + root_profile=profile, + metadata={}, + ) new_write_ledger_id = await manager.set_profile_write_ledger( - profile=profile, ledger_id="test_prod_2" + context=test_ctx, ledger_id="test_prod_2" ) assert new_write_ledger_id == "test_prod_2" new_write_ledger = profile.inject_or(BaseLedger) @@ -122,9 +128,14 @@ async def test_set_profile_write_ledger(self): async def test_set_profile_write_ledger_x(self): profile = InMemoryProfile.test_profile() + test_ctx = AdminRequestContext( + profile=profile, + root_profile=profile, + metadata={}, + ) with self.assertRaises(MultipleLedgerManagerError) as cm: new_write_ledger_id = await self.manager.set_profile_write_ledger( - profile=profile, ledger_id="test_non_prod_1" + context=test_ctx, ledger_id="test_non_prod_1" ) assert "is not write configurable" in str(cm.exception.message) diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py index 196bd87390..d1d3ce534a 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py @@ -8,17 +8,23 @@ from collections import OrderedDict +from ....admin.request_context import AdminRequestContext from ....cache.base import BaseCache from ....cache.in_memory import InMemoryCache from ....core.in_memory import InMemoryProfile from ....ledger.base import BaseLedger from ....messaging.responder import BaseResponder +from ....multitenant.manager import MultitenantManager +from ....wallet.did_method import SOV, DIDMethods +from ....wallet.key_type import ED25519 from ...error import LedgerError from ...indy_vdr import IndyVdrLedger, IndyVdrLedgerPool from ...merkel_validation.tests.test_data import GET_NYM_REPLY from .. import indy_vdr_manager as test_module +from .. import base_manager as base_test_module + from ..base_manager import MultipleLedgerManagerError from ..indy_vdr_manager import MultiIndyVDRLedgerManager @@ -142,8 +148,125 @@ async def test_set_profile_write_ledger(self): profile = InMemoryProfile.test_profile() assert not profile.inject_or(BaseLedger) assert "test_prod_2" in manager.writable_ledgers + test_ctx = AdminRequestContext( + profile=profile, + root_profile=profile, + metadata={}, + ) new_write_ledger_id = await manager.set_profile_write_ledger( - profile=profile, ledger_id="test_prod_2" + context=test_ctx, ledger_id="test_prod_2" + ) + assert new_write_ledger_id == "test_prod_2" + new_write_ledger = profile.inject_or(BaseLedger) + assert new_write_ledger.pool_name == "test_prod_2" + + async def test_set_profile_write_ledger_promote_public_did_from_record(self): + writable_ledgers = set() + writable_ledgers.add("test_prod_1") + writable_ledgers.add("test_prod_2") + endorser_info_map = {} + endorser_info_map["test_prod_2"] = { + "endorser_did": "test_public_did_2", + "endorser_alias": "endorser_2", + } + self.profile.context.injector.bind_instance( + base_test_module.BaseMultitenantManager, + mock.MagicMock(MultitenantManager, autospec=True), + ) + manager = MultiIndyVDRLedgerManager( + self.profile, + production_ledgers=self.production_ledger, + non_production_ledgers=self.non_production_ledger, + writable_ledgers=writable_ledgers, + endorser_map=endorser_info_map, + ) + profile = InMemoryProfile.test_profile() + profile.context.settings["wallet.id"] = "test_wallet_id" + session = await profile.session() + storage = session.inject_or(base_test_module.BaseStorage) + record = base_test_module.StorageRecord( + base_test_module.RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + json.dumps( + { + "test_prod_1": { + "did": "test_public_did_1", + }, + "test_prod_2": { + "did": "test_public_did_2", + "write_ledger": True, + "connection_id": "test_conn_id", + "routing_keys": ["test1", "test2"], + }, + } + ), + {}, + ) + await storage.add_record(record) + assert "test_prod_2" in manager.writable_ledgers + test_ctx = AdminRequestContext( + profile=profile, + root_profile=self.profile, + metadata={}, + ) + with mock.patch.object( + base_test_module, + "promote_wallet_public_did", + mock.CoroutineMock( + return_value=(mock.MagicMock(did="test_public_did_2"), None) + ), + ): + new_write_ledger_id = await manager.set_profile_write_ledger( + context=test_ctx, ledger_id="test_prod_2" + ) + assert new_write_ledger_id == "test_prod_2" + new_write_ledger = profile.inject_or(BaseLedger) + assert new_write_ledger.pool_name == "test_prod_2" + + async def test_set_profile_write_ledger_set_default_public_did(self): + writable_ledgers = set() + writable_ledgers.add("test_prod_1") + writable_ledgers.add("test_prod_2") + endorser_info_map = {} + endorser_info_map["test_prod_2"] = { + "endorser_did": "test_public_did_2", + "endorser_alias": "endorser_2", + } + self.profile.context.injector.bind_instance( + base_test_module.BaseMultitenantManager, + mock.MagicMock(MultitenantManager, autospec=True), + ) + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + manager = MultiIndyVDRLedgerManager( + self.profile, + production_ledgers=self.production_ledger, + non_production_ledgers=self.non_production_ledger, + writable_ledgers=writable_ledgers, + endorser_map=endorser_info_map, + ) + session = await self.profile.session() + wallet = session.inject_or(base_test_module.BaseWallet) + await wallet.create_local_did( + SOV, + ED25519, + did="DJGEjaMunDtFtBVrn1qJMT", + ) + storage = session.inject_or(base_test_module.BaseStorage) + record = base_test_module.StorageRecord( + base_test_module.DEFAULT_PUBLIC_DID, + "DJGEjaMunDtFtBVrn1qJMT", + {}, + ) + await storage.add_record(record) + profile = InMemoryProfile.test_profile() + profile.context.settings["wallet.id"] = "test_wallet_id" + assert "test_prod_2" in manager.writable_ledgers + test_ctx = AdminRequestContext( + profile=profile, + root_profile=self.profile, + metadata={}, + ) + new_write_ledger_id = await manager.set_profile_write_ledger( + context=test_ctx, ledger_id="test_prod_2" ) assert new_write_ledger_id == "test_prod_2" new_write_ledger = profile.inject_or(BaseLedger) @@ -151,9 +274,14 @@ async def test_set_profile_write_ledger(self): async def test_set_profile_write_ledger_x(self): profile = InMemoryProfile.test_profile() + test_ctx = AdminRequestContext( + profile=profile, + root_profile=profile, + metadata={}, + ) with self.assertRaises(MultipleLedgerManagerError) as cm: new_write_ledger_id = await self.manager.set_profile_write_ledger( - profile=profile, ledger_id="test_non_prod_1" + context=test_ctx, ledger_id="test_non_prod_1" ) assert "is not write configurable" in str(cm.exception.message) diff --git a/aries_cloudagent/ledger/routes.py b/aries_cloudagent/ledger/routes.py index 46aa2612fe..c7b4af2dc0 100644 --- a/aries_cloudagent/ledger/routes.py +++ b/aries_cloudagent/ledger/routes.py @@ -44,7 +44,9 @@ get_endorser_connection_id, is_author_role, ) -from ..storage.error import StorageError, StorageNotFoundError +from ..storage.base import BaseStorage, StorageRecord +from ..storage.error import StorageError, StorageNotFoundError, StorageDuplicateError +from ..wallet.base import BaseWallet from ..wallet.error import WalletError, WalletNotFoundError from .base import BaseLedger from .base import Role as LedgerRole @@ -53,6 +55,7 @@ from .multiple_ledger.base_manager import ( BaseMultipleLedgerManager, MultipleLedgerManagerError, + RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, ) from .multiple_ledger.ledger_config_schema import ( ConfigurableWriteLedgersSchema, @@ -348,20 +351,75 @@ async def register_ledger_nym(request: web.BaseRequest): write_ledger_nym_transaction = True # special case - if we are an author with no public DID if create_transaction_for_endorser: - public_info = await ledger.get_wallet_public_did() + async with context.profile.session() as session: + wallet = session.inject(BaseWallet) + public_info = await wallet.get_public_did() + _write_ledger_nym_txn = False if not public_info: + _write_ledger_nym_txn = True + else: + _curr_public_did = public_info.did + async with context.profile.session() as session: + storage = session.inject_or(BaseStorage) + write_ledger = session.inject(BaseLedger) + multiledger_mgr = session.inject_or(BaseMultipleLedgerManager) + try: + ledger_id_public_did_map_record: StorageRecord = ( + await storage.find_record( + type_filter=RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + tag_query={}, + ) + ) + ledger_id_public_did_map = json.loads( + ledger_id_public_did_map_record.value + ) + ledger_id = ( + await multiledger_mgr.get_ledger_id_by_ledger_pool_name( + write_ledger.pool_name + ) + ) + if ledger_id in ledger_id_public_did_map: + assert ( + _curr_public_did + == ledger_id_public_did_map[ledger_id]["did"] + ) + _write_ledger_nym_txn = False + else: + _write_ledger_nym_txn = True + except ( + StorageError, + StorageNotFoundError, + StorageDuplicateError, + ): + _write_ledger_nym_txn = False + if _write_ledger_nym_txn: write_ledger_nym_transaction = False success = False txn = {"signed_txn": json.dumps(meta_data)} if write_ledger_nym_transaction: - (success, txn) = await ledger.register_nym( - did, - verkey, - alias, - role, - write_ledger=write_ledger, - endorser_did=endorser_did, - ) + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + (success, txn) = await ledger.register_nym( + did=did, + verkey=verkey, + alias=alias, + role=role, + write_ledger=write_ledger, + endorser_did=endorser_did, + sign_did_info=sign_did_info, + ) + else: + (success, txn) = await ledger.register_nym( + did=did, + verkey=verkey, + alias=alias, + role=role, + write_ledger=write_ledger, + endorser_did=endorser_did, + ) except LedgerTransactionError as err: raise web.HTTPForbidden(reason=err.roll_up) except LedgerError as err: @@ -457,7 +515,14 @@ async def get_nym_role(request: web.BaseRequest): async with ledger: try: - role = await ledger.get_nym_role(did) + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + role = await ledger.get_nym_role(did, sign_did_info) + else: + role = await ledger.get_nym_role(did) except LedgerTransactionError as err: raise web.HTTPForbidden(reason=err.roll_up) except BadLedgerRequestError as err: @@ -489,7 +554,15 @@ async def rotate_public_did_keypair(request: web.BaseRequest): raise web.HTTPForbidden(reason=reason) async with ledger: try: - await ledger.rotate_public_did_keypair() # do not take seed over the wire + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + await ledger.rotate_public_did_keypair(sign_did_info=sign_did_info) + else: + # do not take seed over the wire + await ledger.rotate_public_did_keypair() except (WalletError, BadLedgerRequestError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err @@ -532,7 +605,14 @@ async def get_did_verkey(request: web.BaseRequest): async with ledger: try: - result = await ledger.get_key_for_did(did) + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + wallet = session.inject(BaseWallet) + sign_did_info = await wallet.get_public_did() + result = await ledger.get_key_for_did(did, sign_did_info) + else: + result = await ledger.get_key_for_did(did) if not result: raise web.HTTPNotFound(reason=f"DID {did} is not on the ledger") except LedgerError as err: @@ -583,7 +663,16 @@ async def get_did_endpoint(request: web.BaseRequest): async with ledger: try: - r = await ledger.get_endpoint_for_did(did, endpoint_type) + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + r = await ledger.get_endpoint_for_did( + did, endpoint_type, sign_did_info + ) + else: + r = await ledger.get_endpoint_for_did(did, endpoint_type) except LedgerError as err: raise web.HTTPBadRequest(reason=err.roll_up) from err @@ -616,7 +705,16 @@ async def ledger_get_taa(request: web.BaseRequest): async with ledger: try: - taa_info = await ledger.get_txn_author_agreement() + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + taa_info = await ledger.get_txn_author_agreement( + sign_did_info=sign_did_info + ) + else: + taa_info = await ledger.get_txn_author_agreement() accepted = None if taa_info["taa_required"]: accept_record = await ledger.get_latest_txn_author_acceptance() @@ -658,7 +756,16 @@ async def ledger_accept_taa(request: web.BaseRequest): LOGGER.info(">>> accepting TAA with: %s", accept_input) async with ledger: try: - taa_info = await ledger.get_txn_author_agreement() + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + taa_info = await ledger.get_txn_author_agreement( + sign_did_info=sign_did_info + ) + else: + taa_info = await ledger.get_txn_author_agreement() if not taa_info["taa_required"]: raise web.HTTPBadRequest( reason=f"Ledger {ledger.pool_name} TAA not available" @@ -760,7 +867,7 @@ async def set_write_ledger(request: web.BaseRequest): try: set_ledger_id = await multiledger_mgr.set_profile_write_ledger( ledger_id=req_ledger_id, - profile=context.profile, + context=context, ) except MultipleLedgerManagerError as err: raise web.HTTPBadRequest(reason=err.roll_up) from err diff --git a/aries_cloudagent/ledger/tests/test_routes.py b/aries_cloudagent/ledger/tests/test_routes.py index 4347823376..0064d27f0c 100644 --- a/aries_cloudagent/ledger/tests/test_routes.py +++ b/aries_cloudagent/ledger/tests/test_routes.py @@ -1,7 +1,8 @@ -from typing import Tuple +import json -from unittest import IsolatedAsyncioTestCase from aries_cloudagent.tests import mock +from typing import Tuple +from unittest import IsolatedAsyncioTestCase from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger @@ -14,6 +15,8 @@ ) from ...multitenant.base import BaseMultitenantManager from ...multitenant.manager import MultitenantManager +from ...wallet.did_method import SOV, DIDMethods +from ...wallet.key_type import ED25519 from .. import routes as test_module from ..indy import Role @@ -314,7 +317,327 @@ async def test_register_nym_wallet_error(self): with self.assertRaises(test_module.web.HTTPBadRequest): await test_module.register_ledger_nym(self.request) - async def test_register_nym_create_transaction_for_endorser(self): + async def test_register_nym_create_transaction_for_endorser_did_in_record(self): + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + session = await self.profile.session() + wallet = session.inject_or(test_module.BaseWallet) + await wallet.create_local_did( + SOV, + ED25519, + did="DJGEjaMunDtFtBVrn1qJMT", + ) + await wallet.set_public_did("DJGEjaMunDtFtBVrn1qJMT") + storage = session.inject_or(test_module.BaseStorage) + record = test_module.StorageRecord( + test_module.RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + json.dumps( + { + "test_ledger_id_1": { + "did": "test_public_did_1", + }, + "test_ledger_id_2": { + "did": "test_public_did_2", + }, + "test_ledger_id": { + "did": "DJGEjaMunDtFtBVrn1qJMT", + }, + } + ), + {}, + ) + await storage.add_record(record) + mock_multiledger_mgr = mock.MagicMock( + get_ledger_id_by_ledger_pool_name=mock.AsyncMock( + return_value="test_ledger_id" + ), + ) + self.profile.context.injector.bind_instance( + BaseMultipleLedgerManager, mock_multiledger_mgr + ) + self.request_dict = { + "context": self.context, + "outbound_message_router": mock.AsyncMock(), + } + self.request = mock.MagicMock( + app={}, + match_info={}, + query={}, + __getitem__=lambda _, k: self.request_dict[k], + ) + self.request.query = { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "ENDORSER", + "create_transaction_for_endorser": "true", + "conn_id": "dummy", + } + + with mock.patch.object( + ConnRecord, "retrieve_by_id", mock.AsyncMock() + ) as mock_conn_rec_retrieve, mock.patch.object( + test_module, "TransactionManager", mock.MagicMock() + ) as mock_txn_mgr, mock.patch.object( + test_module.web, "json_response", mock.MagicMock() + ) as mock_response: + mock_txn_mgr.return_value = mock.MagicMock( + create_record=mock.AsyncMock( + return_value=mock.MagicMock( + serialize=mock.MagicMock(return_value={"...": "..."}) + ) + ) + ) + mock_conn_rec_retrieve.return_value = mock.MagicMock( + metadata_get=mock.AsyncMock( + return_value={ + "endorser_did": ("did"), + "endorser_name": ("name"), + } + ) + ) + self.ledger.register_nym.return_value: Tuple[bool, dict] = ( + True, + {"signed_txn": {"...": "..."}}, + ) + + result = await test_module.register_ledger_nym(self.request) + assert result == mock_response.return_value + mock_response.assert_called_once_with( + {"success": True, "txn": {"signed_txn": {"...": "..."}}} + ) + + async def test_register_nym_create_transaction_for_endorser_did_in_record_multitenant( + self, + ): + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + session = await self.profile.session() + wallet = session.inject_or(test_module.BaseWallet) + await wallet.create_local_did( + SOV, + ED25519, + did="DJGEjaMunDtFtBVrn1qJMT", + ) + await wallet.set_public_did("DJGEjaMunDtFtBVrn1qJMT") + storage = session.inject_or(test_module.BaseStorage) + record = test_module.StorageRecord( + test_module.RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + json.dumps( + { + "test_ledger_id_1": { + "did": "test_public_did_1", + }, + "test_ledger_id_2": { + "did": "test_public_did_2", + }, + "test_ledger_id": { + "did": "DJGEjaMunDtFtBVrn1qJMT", + }, + } + ), + {}, + ) + await storage.add_record(record) + mock_multiledger_mgr = mock.MagicMock( + get_ledger_id_by_ledger_pool_name=mock.AsyncMock( + return_value="test_ledger_id" + ), + ) + self.profile.context.injector.bind_instance( + BaseMultipleLedgerManager, mock_multiledger_mgr + ) + self.profile.context.injector.bind_instance( + BaseMultitenantManager, + mock.MagicMock(MultitenantManager, autospec=True), + ) + self.request_dict = { + "context": self.context, + "outbound_message_router": mock.AsyncMock(), + } + self.request = mock.MagicMock( + app={}, + match_info={}, + query={}, + __getitem__=lambda _, k: self.request_dict[k], + ) + self.request.query = { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "ENDORSER", + "create_transaction_for_endorser": "true", + "conn_id": "dummy", + } + + with mock.patch.object( + ConnRecord, "retrieve_by_id", mock.AsyncMock() + ) as mock_conn_rec_retrieve, mock.patch.object( + test_module, "TransactionManager", mock.MagicMock() + ) as mock_txn_mgr, mock.patch.object( + test_module.web, "json_response", mock.MagicMock() + ) as mock_response: + mock_txn_mgr.return_value = mock.MagicMock( + create_record=mock.AsyncMock( + return_value=mock.MagicMock( + serialize=mock.MagicMock(return_value={"...": "..."}) + ) + ) + ) + mock_conn_rec_retrieve.return_value = mock.MagicMock( + metadata_get=mock.AsyncMock( + return_value={ + "endorser_did": ("did"), + "endorser_name": ("name"), + } + ) + ) + self.ledger.register_nym.return_value: Tuple[bool, dict] = ( + True, + {"signed_txn": {"...": "..."}}, + ) + + result = await test_module.register_ledger_nym(self.request) + assert result == mock_response.return_value + mock_response.assert_called_once_with( + {"success": True, "txn": {"signed_txn": {"...": "..."}}} + ) + + async def test_register_nym_create_transaction_for_endorser_no_did_in_record(self): + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + session = await self.profile.session() + wallet = session.inject_or(test_module.BaseWallet) + await wallet.create_local_did( + SOV, + ED25519, + did="DJGEjaMunDtFtBVrn1qJMT", + ) + await wallet.set_public_did("DJGEjaMunDtFtBVrn1qJMT") + storage = session.inject_or(test_module.BaseStorage) + record = test_module.StorageRecord( + test_module.RECORD_TYPE_LEDGER_PUBLIC_DID_MAP, + json.dumps( + { + "test_ledger_id_1": { + "did": "test_public_did_1", + }, + "test_ledger_id_2": { + "did": "test_public_did_2", + }, + } + ), + {}, + ) + await storage.add_record(record) + mock_multiledger_mgr = mock.MagicMock( + get_ledger_id_by_ledger_pool_name=mock.AsyncMock( + return_value="test_ledger_id" + ), + ) + self.profile.context.injector.bind_instance( + BaseMultipleLedgerManager, mock_multiledger_mgr + ) + self.request_dict = { + "context": self.context, + "outbound_message_router": mock.AsyncMock(), + } + self.request = mock.MagicMock( + app={}, + match_info={}, + query={}, + __getitem__=lambda _, k: self.request_dict[k], + ) + self.request.query = { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "ENDORSER", + "create_transaction_for_endorser": "true", + "conn_id": "dummy", + } + + with mock.patch.object( + ConnRecord, "retrieve_by_id", mock.AsyncMock() + ) as mock_conn_rec_retrieve, mock.patch.object( + test_module, "TransactionManager", mock.MagicMock() + ) as mock_txn_mgr, mock.patch.object( + test_module.web, "json_response", mock.MagicMock() + ) as mock_response: + mock_txn_mgr.return_value = mock.MagicMock( + create_record=mock.AsyncMock( + return_value=mock.MagicMock( + serialize=mock.MagicMock( + return_value={ + "signed_txn": { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "", + } + } + ) + ) + ) + ) + mock_conn_rec_retrieve.return_value = mock.MagicMock( + metadata_get=mock.AsyncMock( + return_value={ + "endorser_did": ("did"), + "endorser_name": ("name"), + } + ) + ) + + result = await test_module.register_ledger_nym(self.request) + assert result == mock_response.return_value + mock_response.assert_called_once_with( + { + "success": False, + "txn": { + "signed_txn": json.dumps( + { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "ENDORSER", + } + ) + }, + } + ) + + async def test_register_nym_create_transaction_for_endorser_no_record(self): + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + session = await self.profile.session() + wallet = session.inject_or(test_module.BaseWallet) + await wallet.create_local_did( + SOV, + ED25519, + did="DJGEjaMunDtFtBVrn1qJMT", + ) + await wallet.set_public_did("DJGEjaMunDtFtBVrn1qJMT") + mock_storage = mock.MagicMock( + find_record=mock.AsyncMock( + side_effect=test_module.StorageNotFoundError() + ), + ) + self.profile.context.injector.bind_instance( + test_module.BaseStorage, mock_storage + ) + mock_multiledger_mgr = mock.MagicMock( + get_ledger_id_by_ledger_pool_name=mock.AsyncMock(), + ) + self.profile.context.injector.bind_instance( + BaseMultipleLedgerManager, mock_multiledger_mgr + ) + self.request_dict = { + "context": self.context, + "outbound_message_router": mock.AsyncMock(), + } + self.request = mock.MagicMock( + app={}, + match_info={}, + query={}, + __getitem__=lambda _, k: self.request_dict[k], + ) self.request.query = { "did": "a_test_did", "verkey": "a_test_verkey", @@ -358,6 +681,20 @@ async def test_register_nym_create_transaction_for_endorser(self): ) async def test_register_nym_create_transaction_for_endorser_no_public_did(self): + mock_wallet = mock.MagicMock( + get_public_did=mock.AsyncMock(return_value=None), + ) + self.profile.context.injector.bind_instance(test_module.BaseWallet, mock_wallet) + self.request_dict = { + "context": self.context, + "outbound_message_router": mock.AsyncMock(), + } + self.request = mock.MagicMock( + app={}, + match_info={}, + query={}, + __getitem__=lambda _, k: self.request_dict[k], + ) self.request.query = { "did": "a_test_did", "verkey": "a_test_verkey", @@ -390,15 +727,23 @@ async def test_register_nym_create_transaction_for_endorser_no_public_did(self): } ) ) - self.ledger.register_nym.return_value: Tuple[bool, dict] = ( - True, - {"signed_txn": {"...": "..."}}, - ) result = await test_module.register_ledger_nym(self.request) assert result == mock_response.return_value mock_response.assert_called_once_with( - {"success": True, "txn": {"signed_txn": {"...": "..."}}} + { + "success": False, + "txn": { + "signed_txn": json.dumps( + { + "did": "a_test_did", + "verkey": "a_test_verkey", + "alias": "did_alias", + "role": "", + } + ) + }, + } ) async def test_register_nym_create_transaction_for_endorser_storage_x(self): diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index 2137f23a5d..6d6890ede8 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -14,6 +14,7 @@ from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import notify_cred_def_event from ....messaging.schemas.util import notify_schema_event +from ....multitenant.base import BaseMultitenantManager from ....revocation.util import ( notify_revocation_reg_endorsed_event, notify_revocation_entry_endorsed_event, @@ -281,14 +282,29 @@ async def create_endorse_response( elif txn_goal_code == TransactionRecord.WRITE_DID_TRANSACTION: # get DID info from transaction.meta_data meta_data = json.loads(transaction_json) - (success, txn) = await shield( - ledger.register_nym( - meta_data["did"], - meta_data["verkey"], - meta_data["alias"], - meta_data["role"], - ) - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + (success, txn) = await shield( + ledger.register_nym( + did=meta_data["did"], + verkey=meta_data["verkey"], + alias=meta_data["alias"], + role=meta_data["role"], + sign_did_info=sign_did_info, + ) + ) + else: + (success, txn) = await shield( + ledger.register_nym( + meta_data["did"], + meta_data["verkey"], + meta_data["alias"], + meta_data["role"], + ) + ) # we don't have an endorsed transaction so just return did meta-data ledger_response = { "result": { diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index dcc356bfc5..40437dd039 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -28,6 +28,7 @@ from ....revocation.models.revocation_registry import RevocationRegistry from ....storage.base import BaseStorage from ....storage.error import StorageError, StorageNotFoundError +from ....wallet.base import BaseWallet from ...out_of_band.v1_0.models.oob_record import OobRecord from .messages.credential_ack import CredentialAck @@ -804,9 +805,19 @@ async def store_credential( raw_cred_serde.de.cred_def_id ) if raw_cred_serde.de.rev_reg_id: - revoc_reg_def = await ledger.get_revoc_reg_def( - raw_cred_serde.de.rev_reg_id - ) + async with self.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + revoc_reg_def = await ledger.get_revoc_reg_def( + revoc_reg_id=raw_cred_serde.de.rev_reg_id, + sign_did_info=sign_did_info, + ) + else: + revoc_reg_def = await ledger.get_revoc_reg_def( + raw_cred_serde.de.rev_reg_id + ) holder = self._profile.inject(IndyHolder) if ( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index d6329cfcb6..1cf9f800fd 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -29,6 +29,7 @@ from ......revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ......revocation.models.revocation_registry import RevocationRegistry from ......storage.base import BaseStorage +from ......wallet.base import BaseWallet from ...message_types import ( ATTACHMENT_FORMAT, @@ -456,7 +457,16 @@ async def store_credential( async with ledger: cred_def = await ledger.get_credential_definition(cred["cred_def_id"]) if cred.get("rev_reg_id"): - rev_reg_def = await ledger.get_revoc_reg_def(cred["rev_reg_id"]) + async with self.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + rev_reg_def = await ledger.get_revoc_reg_def( + revoc_reg_id=cred["rev_reg_id"], sign_did_info=sign_did_info + ) + else: + rev_reg_def = await ledger.get_revoc_reg_def(cred["rev_reg_id"]) holder = self.profile.inject(IndyHolder) cred_offer_message = cred_ex_record.cred_offer diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 4bff88cc89..9073c37d05 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -16,6 +16,7 @@ ) from ....multitenant.base import BaseMultitenantManager from ....revocation.models.revocation_registry import RevocationRegistry +from ....wallet.base import BaseWallet from ..v1_0.models.presentation_exchange import V10PresentationExchange from ..v2_0.messages.pres_format import V20PresFormat @@ -116,11 +117,29 @@ async def return_presentation( if credential.get("rev_reg_id"): revocation_registry_id = credential["rev_reg_id"] if revocation_registry_id not in revocation_registries: - revocation_registries[ - revocation_registry_id - ] = RevocationRegistry.from_definition( - await ledger.get_revoc_reg_def(revocation_registry_id), True - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + revocation_registries[ + revocation_registry_id + ] = RevocationRegistry.from_definition( + await ledger.get_revoc_reg_def( + revoc_reg_id=revocation_registry_id, + sign_did_info=sign_did_info, + ), + True, + ) + else: + revocation_registries[ + revocation_registry_id + ] = RevocationRegistry.from_definition( + await ledger.get_revoc_reg_def( + revocation_registry_id + ), + True, + ) # Get delta with non-revocation interval defined in "non_revoked" # of the presentation request or attributes epoch_now = int(time.time()) @@ -152,11 +171,29 @@ async def return_presentation( f"{reft_non_revoc_interval.get('to', epoch_now)}" ) if key not in revoc_reg_deltas: - (delta, delta_timestamp) = await ledger.get_revoc_reg_delta( - rev_reg_id, - reft_non_revoc_interval.get("from", 0), - reft_non_revoc_interval.get("to", epoch_now), - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + ( + delta, + delta_timestamp, + ) = await ledger.get_revoc_reg_delta( + rev_reg_id, + reft_non_revoc_interval.get("from", 0), + reft_non_revoc_interval.get("to", epoch_now), + sign_did_info, + ) + else: + ( + delta, + delta_timestamp, + ) = await ledger.get_revoc_reg_delta( + rev_reg_id, + reft_non_revoc_interval.get("from", 0), + reft_non_revoc_interval.get("to", epoch_now), + ) revoc_reg_deltas[key] = ( rev_reg_id, credential_id, @@ -258,9 +295,23 @@ async def process_pres_identifiers( if identifier.get("rev_reg_id"): if identifier["rev_reg_id"] not in rev_reg_defs: - rev_reg_defs[ - identifier["rev_reg_id"] - ] = await ledger.get_revoc_reg_def(identifier["rev_reg_id"]) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + rev_reg_defs[ + identifier["rev_reg_id"] + ] = await ledger.get_revoc_reg_def( + revoc_reg_id=identifier["rev_reg_id"], + sign_did_info=sign_did_info, + ) + else: + rev_reg_defs[ + identifier["rev_reg_id"] + ] = await ledger.get_revoc_reg_def( + identifier["rev_reg_id"] + ) if identifier.get("timestamp"): rev_reg_entries.setdefault(identifier["rev_reg_id"], {}) @@ -269,12 +320,29 @@ async def process_pres_identifiers( identifier["timestamp"] not in rev_reg_entries[identifier["rev_reg_id"]] ): - ( - found_rev_reg_entry, - _found_timestamp, - ) = await ledger.get_revoc_reg_entry( - identifier["rev_reg_id"], identifier["timestamp"] - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or( + BaseMultitenantManager + ) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + ( + found_rev_reg_entry, + _found_timestamp, + ) = await ledger.get_revoc_reg_entry( + revoc_reg_id=identifier["rev_reg_id"], + timestamp=identifier["timestamp"], + sign_did_info=sign_did_info, + ) + else: + ( + found_rev_reg_entry, + _found_timestamp, + ) = await ledger.get_revoc_reg_entry( + identifier["rev_reg_id"], + identifier["timestamp"], + ) rev_reg_entries[identifier["rev_reg_id"]][ identifier["timestamp"] ] = found_rev_reg_entry diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index ce18f75c67..3f60ab6e24 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -20,6 +20,7 @@ ) from ...messaging.valid import IndyDID from ...multitenant.base import BaseMultitenantManager +from ...wallet.base import BaseWallet from ...wallet.key_type import ED25519 from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType @@ -177,8 +178,20 @@ async def _resolve( try: async with ledger: - recipient_key = await ledger.get_key_for_did(did) - endpoints: Optional[dict] = await ledger.get_all_endpoints_for_did(did) + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + recipient_key = await ledger.get_key_for_did(did, sign_did_info) + endpoints: Optional[ + dict + ] = await ledger.get_all_endpoints_for_did(did, sign_did_info) + else: + recipient_key = await ledger.get_key_for_did(did) + endpoints: Optional[ + dict + ] = await ledger.get_all_endpoints_for_did(did) except LedgerError as err: raise DIDNotFound(f"DID {did} could not be resolved") from err diff --git a/aries_cloudagent/revocation/indy.py b/aries_cloudagent/revocation/indy.py index 622ef3f583..0665f3c7de 100644 --- a/aries_cloudagent/revocation/indy.py +++ b/aries_cloudagent/revocation/indy.py @@ -16,6 +16,7 @@ is_author_role, ) from ..storage.base import StorageNotFoundError +from ..wallet.base import BaseWallet from .error import ( RevocationError, @@ -212,11 +213,23 @@ async def get_issuer_rev_reg_delta( """ ledger = await self.get_ledger_for_registry(rev_reg_id) async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_reg_id, - fro, - to, - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + sign_did_info, + ) + else: + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + ) return rev_reg_delta @@ -257,9 +270,21 @@ async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry: ledger = await self.get_ledger_for_registry(revoc_reg_id) async with ledger: - rev_reg = RevocationRegistry.from_definition( - await ledger.get_revoc_reg_def(revoc_reg_id), True - ) + async with self._profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + rev_reg = RevocationRegistry.from_definition( + await ledger.get_revoc_reg_def( + revoc_reg_id=revoc_reg_id, sign_did_info=sign_did_info + ), + True, + ) + else: + rev_reg = RevocationRegistry.from_definition( + await ledger.get_revoc_reg_def(revoc_reg_id), True + ) IndyRevocation.REV_REG_CACHE[revoc_reg_id] = rev_reg return rev_reg diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index a027010937..332be935b3 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -40,7 +40,9 @@ INDY_REV_REG_ID_VALIDATE, UUID4_EXAMPLE, ) +from ...multitenant.base import BaseMultitenantManager from ...tails.base import BaseTailsServer +from ...wallet.base import BaseWallet from ..error import RevocationError from ..recover import generate_ledger_rrrecovery_txn from .issuer_cred_rev_record import IssuerCredRevRecord @@ -371,7 +373,18 @@ async def fix_ledger_entry( # 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) + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + revoc_reg_id=self.revoc_reg_id, sign_did_info=sign_did_info + ) + else: + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + self.revoc_reg_id + ) # get rev reg records from wallet (revocations and status) recs = [] diff --git a/aries_cloudagent/wallet/base.py b/aries_cloudagent/wallet/base.py index 65e871a692..9f5dd1dc93 100644 --- a/aries_cloudagent/wallet/base.py +++ b/aries_cloudagent/wallet/base.py @@ -1,8 +1,10 @@ """Wallet base class.""" from abc import ABC, abstractmethod -from typing import List, Sequence, Tuple, Union +from typing import List, Sequence, Tuple, Union, TYPE_CHECKING +from ..storage.base import BaseStorage, StorageRecord +from ..storage.error import StorageError, StorageNotFoundError, StorageDuplicateError from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from .error import WalletError @@ -11,6 +13,11 @@ from .key_type import KeyType from .did_method import SOV, DIDMethod +if TYPE_CHECKING: + from ..indy.sdk.profile import IndySdkProfile + +DEFAULT_PUBLIC_DID = "acapy_default_public_did" + class BaseWallet(ABC): """Abstract wallet interface.""" @@ -112,6 +119,7 @@ async def create_public_did( seed: str = None, did: str = None, metadata: dict = {}, + **kwargs, ) -> DIDInfo: """Create and store a new public DID. @@ -129,7 +137,40 @@ async def create_public_did( did_info = await self.create_local_did( method=method, key_type=key_type, seed=seed, did=did, metadata=metadata ) - return await self.set_public_did(did_info) + public_did_info = await self.set_public_did(did_info) + if hasattr(self, "_session"): + storage = self._session.inject_or(BaseStorage) + elif hasattr(self, "profile"): + async with self.profile.session() as _session: + storage = _session.inject_or(BaseStorage) + elif hasattr(self, "opened"): + _opened = self.opened + _context = kwargs.get("context") + _profile = IndySdkProfile(_opened, _context) + async with _profile.session() as _session: + storage = _session.inject_or(BaseStorage) + else: + raise WalletError( + "Unsupported BaseWallet type, only AskarWallet, " + "InMemoryWallet and IndySdkWallet are supported" + ) + try: + default_public_did_record = await storage.find_record( + type_filter=DEFAULT_PUBLIC_DID, tag_query={} + ) + await storage.update_record( + default_public_did_record, + public_did_info.did, + {}, + ) + except (StorageError, StorageNotFoundError, StorageDuplicateError): + record = StorageRecord( + DEFAULT_PUBLIC_DID, + public_did_info.did, + {}, + ) + await storage.add_record(record) + return public_did_info @abstractmethod async def get_public_did(self) -> DIDInfo: diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 58903a2963..840ce3f8bc 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -10,12 +10,14 @@ from marshmallow import fields, validate from ..admin.request_context import AdminRequestContext +from ..config.wallet import is_multi_ledger, update_public_did_ledger_id_map from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError, LedgerError + from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod from ..messaging.models.base import BaseModelError from ..messaging.models.openapi import OpenAPISchema @@ -43,6 +45,7 @@ StrOrDictField, Uri, ) +from ..multitenant.base import BaseMultitenantManager from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ..protocols.endorse_transaction.v1_0.manager import ( TransactionManager, @@ -676,8 +679,8 @@ async def wallet_set_public_did(request: web.BaseRequest): try: info, attrib_def = await promote_wallet_public_did( - context, - did, + context=context, + did=did, write_ledger=write_ledger, connection_id=connection_id, routing_keys=routing_keys, @@ -728,6 +731,7 @@ async def promote_wallet_public_did( connection_id: str = None, routing_keys: List[str] = None, mediator_endpoint: str = None, + **kwargs, ) -> Tuple[DIDInfo, Optional[dict]]: """Promote supplied DID to the wallet public DID.""" info: DIDInfo = None @@ -747,7 +751,15 @@ async def promote_wallet_public_did( raise PermissionError(reason) async with ledger: - if not await ledger.get_key_for_did(did): + async with context.profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + subwallet = session.inject(BaseWallet) + sign_did_info = await subwallet.get_public_did() + _key = await ledger.get_key_for_did(did, sign_did_info) + else: + _key = await ledger.get_key_for_did(did) + if not _key: raise LookupError(f"DID {did} is not posted to the ledger") # check if we need to endorse @@ -800,9 +812,9 @@ async def promote_wallet_public_did( if info: # Publish endpoint if necessary endpoint = did_info.metadata.get("endpoint") - if is_indy_did and not endpoint: endpoint = mediator_endpoint or context.settings.get("default_endpoint") + attrib_def = await wallet.set_did_endpoint( info.did, endpoint, @@ -812,6 +824,20 @@ async def promote_wallet_public_did( routing_keys=routing_keys, ) + if is_multi_ledger(session.settings): + config_dict = {} + config_dict["write_ledger"] = write_ledger + config_dict["routing_keys"] = routing_keys + config_dict["mediation_endpoint"] = mediator_endpoint + config_dict["connection_id"] = connection_id + config_dict["ledger_pool_name"] = kwargs.get("ledger_pool_name") + config_dict["record_type_name"] = kwargs.get("record_type_name") + await update_public_did_ledger_id_map( + session, + info, + config_dict, + ) + if info: # Route the public DID route_manager = context.profile.inject(RouteManager) @@ -1157,7 +1183,9 @@ async def on_register_nym_event(profile: Profile, event: Event): connection_id = event.payload.get("connection_id") try: _info, attrib_def = await promote_wallet_public_did( - profile.context, did, connection_id + context=profile.context, + did=did, + connection_id=connection_id, ) except Exception as err: # log the error, but continue diff --git a/demo/features/0586-sign-transaction.feature b/demo/features/0586-sign-transaction.feature index 5069f2974e..08606ca08d 100644 --- a/demo/features/0586-sign-transaction.feature +++ b/demo/features/0586-sign-transaction.feature @@ -194,3 +194,72 @@ Feature: RFC 0586 Aries sign (endorse) transactions functions Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | | --endorser-role endorser --revocation --public-did | --endorser-role author --revocation | driverslicense | Data_DL_NormalizedValues | + +@T004-RFC0586 @GHA + Scenario Outline: Select different write ledgers, endorse a schema and cred def transaction, write to the selected ledger, issue and revoke a credential, with auto endorsing workflow + Given we have "3" agents + | name | role | capabilities | + | Acme1 | endorser connected with dev bcovrin | | + | Acme2 | endorser connected with test bcovrin | | + | Bob | author | | + And "Acme1" and "Bob" have an existing connection + When "Acme1" has a DID with role "ENDORSER" + And "Acme1" connection has job role "TRANSACTION_ENDORSER" + And "Bob" connection has job role "TRANSACTION_AUTHOR" + And "Bob" connection sets "CftsUq2Pmjz3MEmfu8RxUs" and "endorser_dev" as endorser info + And "Acme2" and "Bob" have an existing connection + When "Acme2" has a DID with role "ENDORSER" + And "Acme2" connection has job role "TRANSACTION_ENDORSER" + And "Bob" connection has job role "TRANSACTION_AUTHOR" + And "Bob" connection sets "8FWsRpoLKiuqBNDxik2trg" and "endorser_test" as endorser info + And "Bob" selects "bcovrinTest" write_ledger, create local wallet did, register on ledger and set as public + And "Bob" authors a schema transaction with + And "Bob" has written the schema to the ledger + And "Bob" authors a credential definition transaction with + And "Bob" has written the credential definition for to the ledger + And "Bob" has written the revocation registry definition to the ledger + And "Bob" has written the revocation registry entry transaction to the ledger + And "Acme2" has an issued credential from "Bob" + And "Bob" revokes the credential without publishing the entry + And "Bob" authors a revocation registry entry publishing transaction + Then "Acme2" can verify the credential from "Bob" was revoked + And "Bob" selects "bcovrinDev" write_ledger, create local wallet did, register on ledger and set as public + And "Bob" authors a schema transaction with + And "Bob" has written the schema to the ledger + And "Bob" authors a credential definition transaction with + And "Bob" has written the credential definition for to the ledger + And "Bob" has written the revocation registry definition to the ledger + And "Bob" has written the revocation registry entry transaction to the ledger + And "Acme1" has an issued credential from "Bob" + And "Bob" revokes the credential without publishing the entry + And "Bob" authors a revocation registry entry publishing transaction + Then "Acme1" can verify the credential from "Bob" was revoked + And "Bob" selects "bcovrinTest" write_ledger, create local wallet did, register on ledger and set as public + And "Bob" authors a schema transaction with + And "Bob" has written the schema to the ledger + And "Bob" authors a credential definition transaction with + And "Bob" has written the credential definition for to the ledger + And "Bob" has written the revocation registry definition to the ledger + And "Bob" has written the revocation registry entry transaction to the ledger + And "Acme2" has an issued credential from "Bob" + And "Bob" revokes the credential without publishing the entry + And "Bob" authors a revocation registry entry publishing transaction + Then "Acme2" can verify the credential from "Bob" was revoked + And "Bob" selects "bcovrinDev" write_ledger, create local wallet did, register on ledger and set as public + And "Bob" authors a schema transaction with + And "Bob" has written the schema to the ledger + And "Bob" authors a credential definition transaction with + And "Bob" has written the credential definition for to the ledger + And "Bob" has written the revocation registry definition to the ledger + And "Bob" has written the revocation registry entry transaction to the ledger + And "Acme1" has an issued credential from "Bob" + And "Bob" revokes the credential without publishing the entry + And "Bob" authors a revocation registry entry publishing transaction + Then "Acme1" can verify the credential from "Bob" was revoked + + Examples: + | Acme1_capabilities | Acme2_capabilities | Bob_capabilities | Schema_name | Credential_data | + | --endorser-role endorser --revocation --public-did --genesis-url http://dev.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000001 | --endorser-role endorser --revocation --public-did --genesis-url http://test.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000002 | --multitenant --multi-ledger --endorser-role author --revocation --seed bdd_seed_00000000000000000000003 | driverslicense | Data_DL_NormalizedValues | + | --endorser-role endorser --revocation --public-did --genesis-url http://dev.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000001 | --endorser-role endorser --revocation --public-did --genesis-url http://test.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000002 | --multi-ledger --endorser-role author --revocation --seed bdd_seed_00000000000000000000003 | driverslicense | Data_DL_NormalizedValues | + | --mediation --endorser-role endorser --revocation --public-did --genesis-url http://dev.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000001 | --mediation --endorser-role endorser --revocation --public-did --genesis-url http://test.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000002 | --multi-ledger --mediation --endorser-role author --revocation --seed bdd_seed_00000000000000000000003 | driverslicense | Data_DL_NormalizedValues | + | --mediation --endorser-role endorser --revocation --public-did --genesis-url http://dev.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000001 | --mediation --endorser-role endorser --revocation --public-did --genesis-url http://test.bcovrin.vonx.io/genesis --seed bdd_seed_00000000000000000000002 | --multitenant --mediation --multi-ledger --endorser-role author --revocation --seed bdd_seed_00000000000000000000003 | driverslicense | Data_DL_NormalizedValues | diff --git a/demo/features/steps/0586-sign-transaction.py b/demo/features/steps/0586-sign-transaction.py index 61702e84a8..3f023a4660 100644 --- a/demo/features/steps/0586-sign-transaction.py +++ b/demo/features/steps/0586-sign-transaction.py @@ -1,3 +1,4 @@ +import json import time from time import sleep @@ -18,6 +19,51 @@ # Given "Acme" and "Bob" have an existing connection +@when( + '"{agent_name}" selects "{write_ledger}" write_ledger, ' + "create local wallet did, register on ledger and set as public" +) +def step_impl(context, agent_name, write_ledger): + did_role = "AUTHOR" + agent = context.active_agents[agent_name] + + # assign different write ledger + write_ledger_dict = agent_container_PUT( + agent["agent"], + f"/ledger/{write_ledger}/set-write-ledger", + ) + assigned_write_ledger_id = write_ledger_dict.get("write_ledger") + assert write_ledger == assigned_write_ledger_id + + # create a new DID in the current wallet + created_did = agent_container_POST(agent["agent"], "/wallet/did/create") + # publish to the ledger with did_role + registered_did = agent_container_register_did( + agent["agent"], + created_did["result"]["did"], + created_did["result"]["verkey"], + "TRUST_ANCHOR", + ) + + # make the new did the wallet's public did + published_did = agent_container_POST( + agent["agent"], + "/wallet/did/public", + params={"did": created_did["result"]["did"]}, + ) + if "result" in published_did: + # published right away! + pass + elif "txn" in published_did: + # we are an author and need to go through the endorser process + # assume everything works! + async_sleep(3.0) + + if not "public_dids" in context: + context.public_dids = {} + context.public_dids[did_role] = created_did["result"]["did"] + + @when('"{agent_name}" has a DID with role "{did_role}"') def step_impl(context, agent_name, did_role): agent = context.active_agents[agent_name] @@ -91,6 +137,24 @@ def step_impl(context, agent_name): async_sleep(1.0) +@when('"{agent_name}" connection sets "{endorser_did}" and "{endorser_name}" as endorser info') +def step_impl(context, agent_name, endorser_did, endorser_name): + agent = context.active_agents[agent_name] + + # current connection_id for the selected agent + connection_id = agent["agent"].agent.connection_id + + updated_connection = agent_container_POST( + agent["agent"], + "/transactions/" + connection_id + "/set-endorser-info", + params={"endorser_did": endorser_did, "endorser_name": endorser_name}, + ) + + # assert goodness + assert updated_connection["endorser_did"] == endorser_did + async_sleep(1.0) + + @when('"{agent_name}" authors a schema transaction with {schema_name}') def step_impl(context, agent_name, schema_name): agent = context.active_agents[agent_name] diff --git a/demo/multi_ledger_config_bdd.yml b/demo/multi_ledger_config_bdd.yml index 883c2a52c6..3a6e9dcfad 100644 --- a/demo/multi_ledger_config_bdd.yml +++ b/demo/multi_ledger_config_bdd.yml @@ -1,11 +1,16 @@ -- id: local - is_production: true - is_write: true - genesis_url: 'http://$LEDGER_HOST:9000/genesis' - id: bcovrinTest is_production: true -# is_write: true + is_write: true genesis_url: 'http://test.bcovrin.vonx.io/genesis' + endorser_did: '8FWsRpoLKiuqBNDxik2trg' + endorser_alias: 'endorser_test' +- id: bcovrinDev + is_production: true + is_write: true + genesis_url: 'http://dev.bcovrin.vonx.io/genesis' + endorser_did: 'CftsUq2Pmjz3MEmfu8RxUs' + endorser_alias: 'endorser_dev' - id: greenlightTest + is_write: false is_production: true genesis_url: 'http://dev.greenlight.bcovrin.vonx.io/genesis' diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 5f52f8f74b..fa83c0fff3 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -711,6 +711,7 @@ def __init__( log_file: str = None, log_config: str = None, log_level: str = None, + ledger_url: str = None, ): # configuration parameters self.genesis_txns = genesis_txns @@ -750,6 +751,7 @@ def __init__( self.agent = None self.mediator_agent = None self.taa_accept = taa_accept + self.ledger_url = ledger_url async def initialize( self, @@ -795,7 +797,9 @@ async def initialize( # create public DID ... UNLESS we are an author ... if (not self.endorser_role) or (self.endorser_role == "endorser"): if self.public_did and self.cred_type != CRED_FORMAT_JSON_LD: - await self.agent.register_did(cred_type=self.cred_type) + await self.agent.register_did( + ledger_url=self.ledger_url, cred_type=self.cred_type + ) log_msg("Created public DID") # if we are endorsing, create the endorser agent first, then we can use the @@ -806,6 +810,7 @@ async def initialize( self.genesis_txns, self.genesis_txn_list, use_did_exchange=self.use_did_exchange, + ledger_url=self.ledger_url, ) if not self.endorser_agent: raise Exception("Endorser agent returns None :-(") @@ -872,6 +877,7 @@ async def initialize( new_did = await self.agent.admin_POST("/wallet/did/create") self.agent.did = new_did["result"]["did"] await self.agent.register_did( + ledger_url=self.ledger_url, did=new_did["result"]["did"], verkey=new_did["result"]["verkey"], ) @@ -1122,6 +1128,7 @@ async def detect_connection(self): async def register_did(self, did, verkey, role): return await self.agent.register_did( + ledger_url=self.ledger_url, did=did, verkey=verkey, role=role, @@ -1313,6 +1320,28 @@ def arg_parser(ident: str = None, port: int = 8020): "directly." ), ) + parser.add_argument( + "--genesis-url", + type=str, + metavar="", + help=( + "Specifies the url from which to download the genesis " + "transactions. For example, if you are using 'von-network', " + "the URL might be 'http://localhost:9000/genesis'. " + "Genesis transactions URLs are available for the " + "Sovrin test/main networks." + ), + ) + parser.add_argument( + "--seed", + type=str, + metavar="", + help=( + "Specifies the seed to use for the creation of a public " + "DID for the agent to use with a Hyperledger Indy ledger, or a local " + "('--wallet-local-did') DID. If public, the DID must already exist" + ), + ) if (not ident) or (ident != "alice"): parser.add_argument( "--reuse-connections", @@ -1403,10 +1432,16 @@ async def create_agent_with_args(args, ident: str = None): multi_ledger_config_path = None genesis = None + ledger_url = None if "multi_ledger" in args and args.multi_ledger: multi_ledger_config_path = "./demo/multi_ledger_config.yml" else: - genesis = await default_genesis_txns() + genesis_url = args.genesis_url + if genesis_url: + genesis = await default_genesis_txns(genesis_url) + ledger_url = genesis_url + else: + genesis = await default_genesis_txns() if not genesis and not multi_ledger_config_path: print("Error retrieving ledger genesis transactions") sys.exit(1) @@ -1437,6 +1472,12 @@ async def create_agent_with_args(args, ident: str = None): else: public_did = args.public_did if "public_did" in args else None + seed = None + if args.seed: + seed = args.seed + else: + seed = "random" if public_did else None + cred_type = args.cred_type if "cred_type" in args else None log_msg( f"Initializing demo agent {agent_ident} with AIP {aip} and credential type {cred_type}" @@ -1465,7 +1506,7 @@ async def create_agent_with_args(args, ident: str = None): use_did_exchange=(aip == 20) if ("aip" in args) else args.did_exchange, wallet_type=arg_file_dict.get("wallet-type") or args.wallet_type, public_did=public_did, - seed="random" if public_did else None, + seed=seed, arg_file=arg_file, aip=aip, endorser_role=args.endorser_role, @@ -1475,6 +1516,7 @@ async def create_agent_with_args(args, ident: str = None): log_file=log_file, log_config=log_config, log_level=log_level, + ledger_url=ledger_url, ) return agent diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index fea3bc06f4..c9ee59be7c 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -86,12 +86,12 @@ def __repr__(self) -> str: return json.dumps(self.val, indent=4) -async def default_genesis_txns(): +async def default_genesis_txns(genesis_url: str = None): genesis = None try: - if GENESIS_URL: + if genesis_url or GENESIS_URL: async with ClientSession() as session: - async with session.get(GENESIS_URL) as resp: + async with session.get(genesis_url or GENESIS_URL) as resp: genesis = await resp.text() elif RUN_MODE == "docker": async with ClientSession() as session: @@ -585,6 +585,12 @@ def get_agent_args(self): ("--endorser-alias", "endorser"), ] ) + if not self.genesis_txn_list: + result.extend( + [ + ("--endorser-alias", "endorser"), + ] + ) if self.endorser_did: result.extend( [ @@ -638,6 +644,8 @@ async def register_did( ledger_url = LEDGER_URL if not ledger_url: ledger_url = f"http://{self.external_host}:9000" + if "/genesis" in ledger_url: + ledger_url = ledger_url.replace("/genesis", "") data = {"alias": alias or self.ident} if self.endorser_role: if self.endorser_role == "endorser": @@ -1622,6 +1630,7 @@ async def start_endorser_agent( genesis: str = None, genesis_txn_list: str = None, use_did_exchange: bool = True, + ledger_url: str = None, ): # start mediator agent endorser_agent = EndorserAgent( @@ -1630,7 +1639,7 @@ async def start_endorser_agent( genesis_data=genesis, genesis_txn_list=genesis_txn_list, ) - await endorser_agent.register_did(cred_type=CRED_FORMAT_INDY) + await endorser_agent.register_did(ledger_url=ledger_url, cred_type=CRED_FORMAT_INDY) await endorser_agent.listen_webhooks(start_port + 2) await endorser_agent.start_process()