Repository: hyperledger/aries-cloudagent-python

Issue:
Minor Bug - 0.7.0: DID wallet posture remains "wallet_only" after assigning public sov DID for agent



Discussion:

Resolving patch:
diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py
--- a/aries_cloudagent/wallet/askar.py
+++ b/aries_cloudagent/wallet/askar.py
@@ -79,7 +79,7 @@ async def create_signing_key(
 
         Raises:
             WalletDuplicateError: If the resulting verkey already exists in the wallet
-            WalletError: If there is an aries_askar error
+            WalletError: If there is another backend error
 
         """
 
@@ -112,7 +112,7 @@ async def get_signing_key(self, verkey: str) -> KeyInfo:
 
         Raises:
             WalletNotFoundError: If no keypair is associated with the verification key
-            WalletError: If there is a aries_askar error
+            WalletError: If there is another backend error
 
         """
 
@@ -173,7 +173,7 @@ async def create_local_did(
 
         Raises:
             WalletDuplicateError: If the DID already exists in the wallet
-            WalletError: If there is a aries_askar error
+            WalletError: If there is another backend error
 
         """
 
@@ -250,8 +250,6 @@ async def create_local_did(
             did=did, verkey=verkey, metadata=metadata, method=method, key_type=key_type
         )
 
-    # FIXME implement get_public_did more efficiently (store lookup record)
-
     async def get_local_dids(self) -> Sequence[DIDInfo]:
         """
         Get list of defined local DIDs.
@@ -278,7 +276,7 @@ async def get_local_did(self, did: str) -> DIDInfo:
 
         Raises:
             WalletNotFoundError: If the DID is not found
-            WalletError: If there is an aries_askar error
+            WalletError: If there is another backend error
 
         """
 
@@ -350,9 +348,12 @@ async def get_public_did(self) -> DIDInfo:
         """
         public_did = None
         public_info = None
+        public_item = None
         storage = AskarStorage(self._session)
         try:
-            public = await storage.get_record(CATEGORY_CONFIG, RECORD_NAME_PUBLIC_DID)
+            public_item = await storage.get_record(
+                CATEGORY_CONFIG, RECORD_NAME_PUBLIC_DID
+            )
         except StorageNotFoundError:
             # populate public DID record
             # this should only happen once, for an upgraded wallet
@@ -375,9 +376,11 @@ async def get_public_did(self) -> DIDInfo:
                 )
             except StorageDuplicateError:
                 # another process stored the record first
-                pass
-        else:
-            public_did = json.loads(public.value)["did"]
+                public_item = await storage.get_record(
+                    CATEGORY_CONFIG, RECORD_NAME_PUBLIC_DID
+                )
+        if public_item:
+            public_did = json.loads(public_item.value)["did"]
             if public_did:
                 try:
                     public_info = await self.get_local_did(public_did)
@@ -396,10 +399,18 @@ async def set_public_did(self, did: Union[str, DIDInfo]) -> DIDInfo:
         """
 
         if isinstance(did, str):
-            # will raise an exception if not found
-            info = await self.get_local_did(did)
+            try:
+                item = await self._session.handle.fetch(
+                    CATEGORY_DID, did, for_update=True
+                )
+            except AskarError as err:
+                raise WalletError("Error when fetching local DID") from err
+            if not item:
+                raise WalletNotFoundError("Unknown DID: {}".format(did))
+            info = _load_did_entry(item)
         else:
             info = did
+            item = None
 
         if info.method != DIDMethod.SOV:
             raise WalletError("Setting public DID is only allowed for did:sov DIDs")
@@ -407,6 +418,19 @@ async def set_public_did(self, did: Union[str, DIDInfo]) -> DIDInfo:
         public = await self.get_public_did()
         if not public or public.did != info.did:
             storage = AskarStorage(self._session)
+            if not info.metadata.get("posted"):
+                metadata = {**info.metadata, "posted": True}
+                if item:
+                    entry_val = item.value_json
+                    entry_val["metadata"] = metadata
+                    await self._session.handle.replace(
+                        CATEGORY_DID, did, value_json=entry_val, tags=item.tags
+                    )
+                else:
+                    await self.replace_local_did_metadata(info.did, metadata)
+                info = info._replace(
+                    metadata=metadata,
+                )
             await storage.update_record(
                 StorageRecord(
                     type=CATEGORY_CONFIG,
@@ -558,7 +582,7 @@ async def sign_message(
         Raises:
             WalletError: If the message is not provided
             WalletError: If the verkey is not provided
-            WalletError: If an aries_askar error occurs
+            WalletError: If another backend error occurs
 
         """
         if not message:
@@ -606,7 +630,7 @@ async def verify_message(
             WalletError: If the verkey is not provided
             WalletError: If the signature is not provided
             WalletError: If the message is not provided
-            WalletError: If an aries_askar error occurs
+            WalletError: If another backend error occurs
 
         """
         if not from_verkey:
@@ -649,7 +673,7 @@ async def pack_message(
 
         Raises:
             WalletError: If no message is provided
-            WalletError: If an aries_askar error occurs
+            WalletError: If another backend error occurs
 
         """
         if message is None:
@@ -680,7 +704,7 @@ async def unpack_message(self, enc_message: bytes) -> Tuple[str, str, str]:
 
         Raises:
             WalletError: If the message is not provided
-            WalletError: If an aries_askar error occurs
+            WalletError: If another backend error occurs
 
         """
         if not enc_message:
diff --git a/aries_cloudagent/wallet/indy.py b/aries_cloudagent/wallet/indy.py
--- a/aries_cloudagent/wallet/indy.py
+++ b/aries_cloudagent/wallet/indy.py
@@ -620,9 +620,10 @@ async def get_public_did(self) -> DIDInfo:
 
         public_did = None
         public_info = None
+        public_item = None
         storage = IndySdkStorage(self.opened)
         try:
-            public = await storage.get_record(
+            public_item = await storage.get_record(
                 RECORD_TYPE_CONFIG, RECORD_NAME_PUBLIC_DID
             )
         except StorageNotFoundError:
@@ -647,9 +648,11 @@ async def get_public_did(self) -> DIDInfo:
                 )
             except StorageDuplicateError:
                 # another process stored the record first
-                pass
-        else:
-            public_did = json.loads(public.value)["did"]
+                public_item = await storage.get_record(
+                    RECORD_TYPE_CONFIG, RECORD_NAME_PUBLIC_DID
+                )
+        if public_item:
+            public_did = json.loads(public_item.value)["did"]
             if public_did:
                 try:
                     public_info = await self.get_local_did(public_did)
@@ -678,6 +681,10 @@ async def set_public_did(self, did: Union[str, DIDInfo]) -> DIDInfo:
 
         public = await self.get_public_did()
         if not public or public.did != info.did:
+            if not info.metadata.get("posted"):
+                metadata = {**info.metadata, "posted": True}
+                await self.replace_local_did_metadata(info.did, metadata)
+                info = info._replace(metadata=metadata)
             storage = IndySdkStorage(self.opened)
             await storage.update_record(
                 StorageRecord(


Test patch:
diff --git a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py
--- a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py
+++ b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py
@@ -403,6 +403,7 @@ async def test_set_public_did(self, wallet: InMemoryWallet):
             self.test_sov_did,
             self.test_metadata,
         )
+        assert not info.metadata.get("posted")
 
         with pytest.raises(WalletNotFoundError):
             await wallet.set_public_did("55GkHamhTU1ZbTbV2ab9DF")
@@ -410,17 +411,20 @@ async def test_set_public_did(self, wallet: InMemoryWallet):
         # test assign
         info_same = await wallet.set_public_did(info.did)
         assert info_same.did == info.did
+        assert info_same.metadata.get("posted")
 
         info_new = await wallet.create_local_did(DIDMethod.SOV, KeyType.ED25519)
         assert info_new.did != info_same.did
 
         loc = await wallet.get_local_did(self.test_sov_did)
-        pub = await wallet.set_public_did(loc.did)
+        pub = await wallet.set_public_did(loc)
         assert pub.did == loc.did
+        assert pub.metadata.get("posted")
 
         # test replace
         info_final = await wallet.set_public_did(info_new.did)
         assert info_final.did == info_new.did
+        assert info_final.metadata.get("posted")
 
     @pytest.mark.asyncio
     async def test_set_public_did_x_not_sov(self, wallet: InMemoryWallet):