Optimize NFT coin_added (#16227)

* Optimize NFT coin_added

* Resolve comments

* Resolve comment

* Resolve comments
This commit is contained in:
Kronus91 2023-09-06 15:28:32 -07:00 committed by GitHub
parent 3f09054548
commit 3c81911b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 46 deletions

View File

@ -31,7 +31,7 @@ from chia.wallet.lineage_proof import LineageProof
from chia.wallet.nft_wallet import nft_puzzles
from chia.wallet.nft_wallet.nft_info import NFTCoinInfo, NFTWalletInfo
from chia.wallet.nft_wallet.nft_puzzles import NFT_METADATA_UPDATER, create_ownership_layer_puzzle, get_metadata_and_phs
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.nft_wallet.uncurry_nft import NFTCoinData, UncurriedNFT
from chia.wallet.outer_puzzles import AssetType, construct_puzzle, match_puzzle, solve_puzzle
from chia.wallet.payment import Payment
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
@ -46,7 +46,6 @@ from chia.wallet.uncurried_puzzle import uncurry_puzzle
from chia.wallet.util.compute_memos import compute_memos
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig
from chia.wallet.util.wallet_sync_utils import fetch_coin_spend
from chia.wallet.util.wallet_types import WalletType
from chia.wallet.wallet import CHIP_0002_SIGN_MESSAGE_PREFIX, Wallet
from chia.wallet.wallet_coin_record import WalletCoinRecord
@ -59,7 +58,7 @@ _T_NFTWallet = TypeVar("_T_NFTWallet", bound="NFTWallet")
class NFTWallet:
if TYPE_CHECKING:
_protocol_check: ClassVar[WalletProtocol[UncurriedNFT]] = cast("NFTWallet", None)
_protocol_check: ClassVar[WalletProtocol[NFTCoinData]] = cast("NFTWallet", None)
wallet_state_manager: Any
log: logging.Logger
@ -158,44 +157,31 @@ class NFTWallet:
return nft_coin
async def coin_added(
self, coin: Coin, height: uint32, peer: WSChiaConnection, coin_data: Optional[UncurriedNFT]
self, coin: Coin, height: uint32, peer: WSChiaConnection, parent_coin_data: Optional[NFTCoinData]
) -> None:
"""Notification from wallet state manager that wallet has been received."""
# TODO Use coin_data instead of calling peer API
self.log.info(f"NFT wallet %s has been notified that {coin} was added", self.get_name())
if await self.nft_store.exists(coin.name()):
# already added
return
wallet_node = self.wallet_state_manager.wallet_node
cs: Optional[CoinSpend] = None
coin_states: Optional[List[CoinState]] = await wallet_node.get_coin_state([coin.parent_coin_info], peer=peer)
if not coin_states:
# farm coin
return
assert coin_states
parent_coin = coin_states[0].coin
cs = await fetch_coin_spend(height, parent_coin, peer)
assert cs is not None
await self.puzzle_solution_received(cs, peer)
assert isinstance(parent_coin_data, NFTCoinData), f"Invalid NFT coin data: {parent_coin_data}"
await self.puzzle_solution_received(coin, parent_coin_data, peer)
async def puzzle_solution_received(self, coin_spend: CoinSpend, peer: WSChiaConnection) -> None:
async def puzzle_solution_received(self, coin: Coin, data: NFTCoinData, peer: WSChiaConnection) -> None:
self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info)
coin_name = coin_spend.coin.name()
puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal))
# At this point, the puzzle must be a NFT puzzle.
# This method will be called only when the wallet state manager uncurried this coin as a NFT puzzle.
uncurried_nft = UncurriedNFT.uncurry(*puzzle.uncurry())
assert uncurried_nft is not None
uncurried_nft: UncurriedNFT = data.uncurried_nft
self.log.debug(
"found the info for NFT coin %s %s %s",
coin_name.hex(),
coin.name().hex(),
uncurried_nft.inner_puzzle,
uncurried_nft.singleton_struct,
)
singleton_id = uncurried_nft.singleton_launcher_id
parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash()
metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution)
metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, data.parent_coin_spend.solution)
self.log.debug("Got back puzhash from solution: %s", p2_puzzle_hash)
self.log.debug("Got back updated metadata: %s", metadata)
derivation_record: Optional[
@ -217,7 +203,9 @@ class NFTWallet:
mint_height: uint32 = uint32(launcher_coin_states[0].spent_height)
minter_did = None
if uncurried_nft.supports_did:
inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, coin_spend.solution.to_program(), p2_puzzle)
inner_puzzle = nft_puzzles.recurry_nft_puzzle(
uncurried_nft, data.parent_coin_spend.solution.to_program(), p2_puzzle
)
minter_did = await self.wallet_state_manager.get_minter_did(launcher_coin_states[0].coin, peer)
else:
inner_puzzle = p2_puzzle
@ -232,7 +220,7 @@ class NFTWallet:
nft_puzzles.create_full_puzzle_with_nft_puzzle(singleton_id, uncurried_nft.inner_puzzle),
)
child_puzzle_hash = child_puzzle.get_tree_hash()
for new_coin in compute_additions(coin_spend):
for new_coin in compute_additions(data.parent_coin_spend):
self.log.debug(
"Comparing addition: %s with %s, amount: %s ",
new_coin.puzzle_hash,
@ -247,17 +235,12 @@ class NFTWallet:
self.log.info("Adding a new NFT to wallet: %s", child_coin)
# all is well, lets add NFT to our local db
parent_coin = None
confirmed_height = None
coin_states: Optional[List[CoinState]] = await self.wallet_state_manager.wallet_node.get_coin_state(
[coin_name], peer=peer
parent_coin = data.parent_coin_state.coin
confirmed_height = (
None if data.parent_coin_state.spent_height is None else uint32(data.parent_coin_state.spent_height)
)
if coin_states is not None:
parent_coin = coin_states[0].coin
confirmed_height = None if coin_states[0].spent_height is None else uint32(coin_states[0].spent_height)
if parent_coin is None or confirmed_height is None:
if confirmed_height is None:
raise ValueError("Error finding parent")
await self.add_coin(

View File

@ -4,8 +4,10 @@ import logging
from dataclasses import dataclass
from typing import Optional, Type, TypeVar
from chia.protocols.wallet_protocol import CoinState
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.util.ints import uint16
from chia.util.streamable import Streamable, streamable
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
@ -203,3 +205,11 @@ class UncurriedNFT(Streamable):
return state_layer_inner_solution.first() # type: ignore
else:
return state_layer_inner_solution
@streamable
@dataclass(frozen=True)
class NFTCoinData(Streamable):
uncurried_nft: UncurriedNFT
parent_coin_state: CoinState
parent_coin_spend: CoinSpend

View File

@ -68,7 +68,7 @@ from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, match_di
from chia.wallet.key_val_store import KeyValStore
from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs, get_new_owner_did
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT
from chia.wallet.nft_wallet.uncurry_nft import NFTCoinData, UncurriedNFT
from chia.wallet.notification_manager import NotificationManager
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.payment import Payment
@ -743,7 +743,8 @@ class WalletStateManager:
# First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT
uncurried_nft = UncurriedNFT.uncurry(uncurried.mod, uncurried.args)
if uncurried_nft is not None and coin_state.coin.amount % 2 == 1:
return await self.handle_nft(coin_spend, uncurried_nft, parent_coin_state, coin_state), uncurried_nft
nft_data = NFTCoinData(uncurried_nft, parent_coin_state, coin_spend)
return await self.handle_nft(nft_data), nft_data
# Check if the coin is a DID
did_curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
@ -1175,14 +1176,12 @@ class WalletStateManager:
return minter_did
async def handle_nft(
self, coin_spend: CoinSpend, uncurried_nft: UncurriedNFT, parent_coin_state: CoinState, coin_state: CoinState
self,
nft_data: NFTCoinData,
) -> Optional[WalletIdentifier]:
"""
Handle the new coin when it is a NFT
:param coin_spend: New coin spend
:param uncurried_nft: Uncurried NFT
:param parent_coin_state: Parent coin state
:param coin_state: Current coin state
:param nft_data: all necessary data to process a NFT coin
:return: Wallet ID & Wallet Type
"""
wallet_identifier = None
@ -1190,13 +1189,14 @@ class WalletStateManager:
new_did_id = None
old_did_id = None
# P2 puzzle hash determines if we should ignore the NFT
uncurried_nft: UncurriedNFT = nft_data.uncurried_nft
old_p2_puzhash = uncurried_nft.p2_puzzle.get_tree_hash()
metadata, new_p2_puzhash = get_metadata_and_phs(
uncurried_nft,
coin_spend.solution,
nft_data.parent_coin_spend.solution,
)
if uncurried_nft.supports_did:
new_did_id = get_new_owner_did(uncurried_nft, coin_spend.solution.to_program())
new_did_id = get_new_owner_did(uncurried_nft, nft_data.parent_coin_spend.solution.to_program())
old_did_id = uncurried_nft.owner_did
if new_did_id is None:
new_did_id = old_did_id
@ -1204,7 +1204,7 @@ class WalletStateManager:
new_did_id = None
self.log.debug(
"Handling NFT: %s old DID:%s, new DID:%s, old P2:%s, new P2:%s",
coin_spend,
nft_data.parent_coin_spend,
old_did_id,
new_did_id,
old_p2_puzhash,
@ -1231,8 +1231,10 @@ class WalletStateManager:
uncurried_nft.singleton_launcher_id.hex(),
old_did_id,
)
if parent_coin_state.spent_height is not None:
await nft_wallet.remove_coin(coin_spend.coin, uint32(parent_coin_state.spent_height))
if nft_data.parent_coin_state.spent_height is not None:
await nft_wallet.remove_coin(
nft_data.parent_coin_spend.coin, uint32(nft_data.parent_coin_state.spent_height)
)
is_empty = await nft_wallet.is_empty()
has_did = False
for did_wallet in self.wallets.values():