Add /cat_get_unacknowledged API for accessing unknown CATs (#10382)

* Add /cat_get_unacknowledged API for accessing unknown CATs

* Reformat & fix cast issue

* Integration tested & add unit test

* Handle optional uint32

* Reformat

* Reformat

* Reformat

* Merge PR 10308

* Reformat

* Fix concurrent issue

* Add state change notification

* rename API

* Fix failing tests

* Updated state_change name

Co-authored-by: Jeff Cruikshank <jeff@chia.net>
This commit is contained in:
Kronus91 2022-04-07 09:22:59 -07:00 committed by GitHub
parent c8468bea07
commit ffd3b19315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 2 deletions

View File

@ -91,6 +91,7 @@ class WalletRpcApi:
"/cat_set_name": self.cat_set_name, "/cat_set_name": self.cat_set_name,
"/cat_asset_id_to_name": self.cat_asset_id_to_name, "/cat_asset_id_to_name": self.cat_asset_id_to_name,
"/cat_get_name": self.cat_get_name, "/cat_get_name": self.cat_get_name,
"/get_stray_cats": self.get_stray_cats,
"/cat_spend": self.cat_spend, "/cat_spend": self.cat_spend,
"/cat_get_asset_id": self.cat_get_asset_id, "/cat_get_asset_id": self.cat_get_asset_id,
"/create_offer_for_ids": self.create_offer_for_ids, "/create_offer_for_ids": self.create_offer_for_ids,
@ -857,6 +858,16 @@ class WalletRpcApi:
name: str = await wallet.get_name() name: str = await wallet.get_name()
return {"wallet_id": wallet_id, "name": name} return {"wallet_id": wallet_id, "name": name}
async def get_stray_cats(self, request):
"""
Get a list of all unacknowledged CATs
:param request: RPC request
:return: A list of unacknowledged CATs
"""
assert self.service.wallet_state_manager is not None
cats = await self.service.wallet_state_manager.interested_store.get_unacknowledged_tokens()
return {"stray_cats": cats}
async def cat_spend(self, request): async def cat_spend(self, request):
assert self.service.wallet_state_manager is not None assert self.service.wallet_state_manager is not None

View File

@ -363,6 +363,10 @@ class WalletRpcClient(RpcClient):
} }
return bytes.fromhex((await self.fetch("cat_get_asset_id", request))["asset_id"]) return bytes.fromhex((await self.fetch("cat_get_asset_id", request))["asset_id"])
async def get_stray_cats(self) -> Dict:
response = await self.fetch("get_stray_cats", {})
return response["stray_cats"]
async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[Tuple[Optional[uint32], str]]: async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[Tuple[Optional[uint32], str]]:
request: Dict[str, Any] = { request: Dict[str, Any] = {
"asset_id": asset_id.hex(), "asset_id": asset_id.hex(),

View File

@ -4,6 +4,7 @@ import aiosqlite
from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.db_wrapper import DBWrapper from chia.util.db_wrapper import DBWrapper
from chia.util.ints import uint32
class WalletInterestedStore: class WalletInterestedStore:
@ -26,14 +27,21 @@ class WalletInterestedStore:
await self.db_connection.execute( await self.db_connection.execute(
"CREATE TABLE IF NOT EXISTS interested_puzzle_hashes(puzzle_hash text PRIMARY KEY, wallet_id integer)" "CREATE TABLE IF NOT EXISTS interested_puzzle_hashes(puzzle_hash text PRIMARY KEY, wallet_id integer)"
) )
# Table for unknown CATs
fields = "asset_id text PRIMARY KEY, name text, first_seen_height integer, sender_puzzle_hash text"
await self.db_connection.execute(f"CREATE TABLE IF NOT EXISTS unacknowledged_asset_tokens({fields})")
await self.db_connection.commit() await self.db_connection.commit()
return self return self
async def _clear_database(self): async def _clear_database(self):
cursor = await self.db_connection.execute("DELETE FROM puzzle_hashes") cursor = await self.db_connection.execute("DELETE FROM interested_puzzle_hashes")
await cursor.close() await cursor.close()
cursor = await self.db_connection.execute("DELETE FROM interested_coins") cursor = await self.db_connection.execute("DELETE FROM interested_coins")
await cursor.close() await cursor.close()
cursor = await self.db_connection.execute("DELETE FROM unacknowledged_asset_tokens")
await cursor.close()
await self.db_connection.commit() await self.db_connection.commit()
async def get_interested_coin_ids(self) -> List[bytes32]: async def get_interested_coin_ids(self) -> List[bytes32]:
@ -97,3 +105,52 @@ class WalletInterestedStore:
if not in_transaction: if not in_transaction:
await self.db_connection.commit() await self.db_connection.commit()
self.db_wrapper.lock.release() self.db_wrapper.lock.release()
async def add_unacknowledged_token(
self,
asset_id: bytes32,
name: str,
first_seen_height: Optional[uint32],
sender_puzzle_hash: bytes32,
in_transaction: bool = True,
) -> None:
"""
Add an unacknowledged CAT to the database. It will only be inserted once at the first time.
:param asset_id: CAT asset ID
:param name: Name of the CAT, for now it will be unknown until we integrate the CAT name service
:param first_seen_height: The block height of the wallet received this CAT in the first time
:param sender_puzzle_hash: The puzzle hash of the sender
:param in_transaction: In transaction or not
:return: None
"""
if not in_transaction:
await self.db_wrapper.lock.acquire()
try:
cursor = await self.db_connection.execute(
"INSERT OR IGNORE INTO unacknowledged_asset_tokens VALUES (?, ?, ?, ?)",
(
asset_id.hex(),
name,
first_seen_height if first_seen_height is not None else 0,
sender_puzzle_hash.hex(),
),
)
await cursor.close()
finally:
if not in_transaction:
await self.db_connection.commit()
self.db_wrapper.lock.release()
async def get_unacknowledged_tokens(self) -> List:
"""
Get a list of all unacknowledged CATs
:return: A json style list of unacknowledged CATs
"""
cursor = await self.db_connection.execute(
"SELECT asset_id, name, first_seen_height, sender_puzzle_hash FROM unacknowledged_asset_tokens"
)
cats = await cursor.fetchall()
return [
{"asset_id": cat[0], "name": cat[1], "first_seen_height": cat[2], "sender_puzzle_hash": cat[3]}
for cat in cats
]

View File

@ -572,7 +572,8 @@ class WalletStateManager:
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}") self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
else: else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey)) our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(bytes(derivation_record.pubkey))
cat_puzzle = construct_cat_puzzle(CAT_MOD, bytes32(bytes(tail_hash)[1:]), our_inner_puzzle) asset_id: bytes32 = bytes32(bytes(tail_hash)[1:])
cat_puzzle = construct_cat_puzzle(CAT_MOD, asset_id, our_inner_puzzle)
if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash: if cat_puzzle.get_tree_hash() != coin_state.coin.puzzle_hash:
return None, None return None, None
if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get( if bytes(tail_hash).hex()[2:] in self.default_cats or self.config.get(
@ -584,6 +585,15 @@ class WalletStateManager:
wallet_id = cat_wallet.id() wallet_id = cat_wallet.id()
wallet_type = WalletType(cat_wallet.type()) wallet_type = WalletType(cat_wallet.type())
self.state_changed("wallet_created") self.state_changed("wallet_created")
else:
# Found unacknowledged CAT, save it in the database.
await self.interested_store.add_unacknowledged_token(
asset_id,
CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()),
parent_coin_state.spent_height,
parent_coin_state.coin.puzzle_hash,
)
self.state_changed("added_stray_cat")
return wallet_id, wallet_type return wallet_id, wallet_type

View File

@ -477,6 +477,12 @@ class TestWalletRpc:
for i in range(0, 5): for i in range(0, 5):
await client.farm_block(encode_puzzle_hash(ph_2, "txch")) await client.farm_block(encode_puzzle_hash(ph_2, "txch"))
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
# Test unacknowledged CAT
await wallet_node.wallet_state_manager.interested_store.add_unacknowledged_token(
asset_id, "Unknown", uint32(10000), bytes.fromhex("ABCD")
)
cats = await client.get_stray_cats()
assert len(cats) == 1
await time_out_assert(10, eventual_balance_det, 16, client, cat_0_id) await time_out_assert(10, eventual_balance_det, 16, client, cat_0_id)
await time_out_assert(10, eventual_balance_det, 4, client_2, cat_1_id) await time_out_assert(10, eventual_balance_det, 4, client_2, cat_1_id)