From 75555f50e812a6ac9c3d8a98efc80f65fad7afca Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 11 Oct 2023 06:44:04 -0700 Subject: [PATCH] Don't autopush transactions by default anywhere --- chia/pools/pool_wallet.py | 22 +- chia/rpc/util.py | 12 +- chia/rpc/wallet_rpc_api.py | 425 ++++++++++++++----- chia/rpc/wallet_rpc_client.py | 43 ++ chia/wallet/did_wallet/did_wallet.py | 54 ++- chia/wallet/nft_wallet/nft_wallet.py | 32 +- chia/wallet/trade_manager.py | 9 +- chia/wallet/wallet_state_manager.py | 17 +- tests/wallet/cat_wallet/test_trades.py | 28 ++ tests/wallet/db_wallet/test_dl_offers.py | 6 + tests/wallet/did_wallet/test_did.py | 69 ++- tests/wallet/nft_wallet/test_nft_1_offers.py | 141 ++++-- tests/wallet/nft_wallet/test_nft_offers.py | 89 ++-- tests/wallet/nft_wallet/test_nft_wallet.py | 61 ++- tests/wallet/rpc/test_wallet_rpc.py | 31 +- tests/wallet/sync/test_wallet_sync.py | 14 +- tests/wallet/vc_wallet/test_vc_wallet.py | 4 +- 17 files changed, 739 insertions(+), 318 deletions(-) diff --git a/chia/pools/pool_wallet.py b/chia/pools/pool_wallet.py index 00201d58a6de..83cb38047ffc 100644 --- a/chia/pools/pool_wallet.py +++ b/chia/pools/pool_wallet.py @@ -462,7 +462,6 @@ async def create_new_pool_wallet_transaction( name=spend_bundle.name(), valid_times=parse_timelock_info(extra_conditions), ) - await standard_wallet.push_transaction(standard_wallet_record) p2_singleton_puzzle_hash: bytes32 = launcher_id_to_p2_puzzle_hash( launcher_coin_id, p2_singleton_delay_time, p2_singleton_delayed_ph ) @@ -521,17 +520,6 @@ async def generate_fee_transaction( ) return fee_tx - async def publish_transactions(self, travel_tx: TransactionRecord, fee_tx: Optional[TransactionRecord]) -> None: - # We create two transaction records, one for the pool wallet to keep track of the travel TX, and another - # for the standard wallet to keep track of the fee. However, we will only submit the first one to the - # blockchain, and this one has the fee inside it as well. - # The fee tx, if present, will be added to the DB with no spend_bundle set, which has the effect that it - # will not be sent to full nodes. - - await self.wallet_state_manager.add_pending_transaction(travel_tx) - if fee_tx is not None: - await self.wallet_state_manager.add_pending_transaction(dataclasses.replace(fee_tx, spend_bundle=None)) - async def generate_travel_transactions( self, fee: uint64, tx_config: TXConfig ) -> Tuple[TransactionRecord, Optional[TransactionRecord]]: @@ -615,6 +603,7 @@ async def generate_travel_transactions( fee_tx = await self.generate_fee_transaction(fee, tx_config) assert fee_tx.spend_bundle is not None signed_spend_bundle = SpendBundle.aggregate([signed_spend_bundle, fee_tx.spend_bundle]) + fee_tx = dataclasses.replace(fee_tx, spend_bundle=None) tx_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -636,7 +625,6 @@ async def generate_travel_transactions( valid_times=ConditionValidTimes(), ) - await self.publish_transactions(tx_record, fee_tx) return tx_record, fee_tx @staticmethod @@ -918,7 +906,6 @@ async def claim_pool_rewards( valid_times=ConditionValidTimes(), ) - await self.publish_transactions(absorb_transaction, fee_tx) return absorb_transaction, fee_tx async def new_peak(self, peak_height: uint32) -> None: @@ -963,7 +950,12 @@ async def new_peak(self, peak_height: uint32) -> None: assert self.target_state.relative_lock_height >= self.MINIMUM_RELATIVE_LOCK_HEIGHT assert self.target_state.pool_url is not None - await self.generate_travel_transactions(self.next_transaction_fee, self.next_tx_config) + travel_tx, fee_tx = await self.generate_travel_transactions( + self.next_transaction_fee, self.next_tx_config + ) + await self.wallet_state_manager.add_pending_transaction(travel_tx) + if fee_tx is not None: + await self.wallet_state_manager.add_pending_transaction(fee_tx) async def have_unconfirmed_transaction(self) -> bool: unconfirmed: List[TransactionRecord] = await self.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( diff --git a/chia/rpc/util.py b/chia/rpc/util.py index edee869ba40e..0b71ca25c5fe 100644 --- a/chia/rpc/util.py +++ b/chia/rpc/util.py @@ -79,6 +79,16 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s ): raise ValueError("Relative timelocks are not currently supported in the RPC") - return await func(self, request, *args, tx_config=tx_config, extra_conditions=extra_conditions, **kwargs) + push: Optional[bool] = request.get("push") + + return await func( + self, + request, + *args, + tx_config=tx_config, + extra_conditions=extra_conditions, + **({"push": push} if push is not None else {}), + **kwargs, + ) return rpc_endpoint diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 1b39fbcb907d..6b2a8b5da9f2 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -554,9 +554,16 @@ async def push_transactions(self, request: Dict[str, Any]) -> EndpointResult: wallet = self.service.wallet_state_manager.main_wallet txs: List[TransactionRecord] = [] - for transaction_hexstr in request["transactions"]: - tx = TransactionRecord.from_bytes(hexstr_to_bytes(transaction_hexstr)) - txs.append(tx) + for transaction_hexstr_or_json in request["transactions"]: + if isinstance(transaction_hexstr_or_json, str): + tx = TransactionRecord.from_bytes(hexstr_to_bytes(transaction_hexstr_or_json)) + txs.append(tx) + else: + try: + tx = TransactionRecord.from_json_dict_convenience(transaction_hexstr_or_json) + except AttributeError: + tx = TransactionRecord.from_json_dict(transaction_hexstr_or_json) + txs.append(tx) async with self.service.wallet_state_manager.lock: for tx in txs: @@ -635,6 +642,7 @@ async def create_new_wallet( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_state_manager = self.service.wallet_state_manager @@ -648,6 +656,8 @@ async def create_new_wallet( name = request.get("name", None) if request["mode"] == "new": if request.get("test", False): + if not push: + raise ValueError("Test CAT minting must be pushed automatically") # pragma: no cover async with self.service.wallet_state_manager.lock: cat_wallet: CATWallet = await CATWallet.create_new_cat_wallet( wallet_state_manager, @@ -826,11 +836,14 @@ async def create_new_wallet( delayed_address, extra_conditions=extra_conditions, ) + if push: + await self.service.wallet_state_manager.add_pending_transaction(tr) except Exception as e: raise ValueError(str(e)) return { "total_fee": fee * 2, "transaction": tr, + "transactions": [tr], "launcher_id": launcher_id.hex(), "p2_singleton_puzzle_hash": p2_singleton_puzzle_hash.hex(), } @@ -1018,6 +1031,7 @@ async def send_transaction( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: if await self.service.wallet_state_manager.synced() is False: raise ValueError("Wallet needs to be fully synced before sending transactions") @@ -1051,11 +1065,14 @@ async def send_transaction( puzzle_decorator_override=request.get("puzzle_decorator", None), extra_conditions=extra_conditions, ) - await wallet.push_transaction(tx) + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) # Transaction may not have been included in the mempool yet. Use get_transaction to check. + json_tx = tx.to_json_dict_convenience(self.service.config) return { - "transaction": tx.to_json_dict_convenience(self.service.config), + "transaction": json_tx, + "transactions": [json_tx], "transaction_id": tx.name, } @@ -1069,16 +1086,20 @@ async def send_transaction_multi(self, request: Dict[str, Any]) -> EndpointResul async with self.service.wallet_state_manager.lock: if wallet.type() in {WalletType.CAT, WalletType.CRCAT}: assert isinstance(wallet, CATWallet) - transaction = (await self.cat_spend(request, hold_lock=False))["transaction"] + response = await self.cat_spend(request, hold_lock=False) + transaction = response["transaction"] + transactions = response["transactions"] else: - transaction = (await self.create_signed_transaction(request, hold_lock=False))["signed_tx"] - tr = TransactionRecord.from_json_dict_convenience(transaction) - if wallet.type() not in {WalletType.CAT, WalletType.CRCAT}: - assert isinstance(wallet, Wallet) - await wallet.push_transaction(tr) + response = await self.create_signed_transaction(request, hold_lock=False) + transaction = response["signed_tx"] + transactions = response["transactions"] # Transaction may not have been included in the mempool yet. Use get_transaction to check. - return {"transaction": transaction, "transaction_id": tr.name} + return { + "transaction": transaction, + "transaction_id": TransactionRecord.from_json_dict_convenience(transaction).name, + "transactions": transactions, + } @tx_endpoint async def spend_clawback_coins( @@ -1086,6 +1107,7 @@ async def spend_clawback_coins( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Spend clawback coins that were sent (to claw them back) or received (to claim them). @@ -1110,34 +1132,36 @@ async def spend_clawback_coins( batch_size = request.get( "batch_size", self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50) ) - tx_id_list: List[bytes] = [] + tx_list: List[TransactionRecord] = [] for coin_id, coin_record in coin_records.coin_id_to_record.items(): try: metadata = coin_record.parsed_metadata() assert isinstance(metadata, ClawbackMetadata) coins[coin_record.coin] = metadata if len(coins) >= batch_size: - tx_id_list.extend( - ( - await self.service.wallet_state_manager.spend_clawback_coins( - coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions - ) - ) + new_txs = await self.service.wallet_state_manager.spend_clawback_coins( + coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions ) + if push: + for tx in new_txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + tx_list.extend(new_txs) coins = {} except Exception as e: log.error(f"Failed to spend clawback coin {coin_id.hex()}: %s", e) if len(coins) > 0: - tx_id_list.extend( - ( - await self.service.wallet_state_manager.spend_clawback_coins( - coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions - ) - ) + new_txs = await self.service.wallet_state_manager.spend_clawback_coins( + coins, tx_fee, tx_config, request.get("force", False), extra_conditions=extra_conditions ) + if push: + for tx in new_txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + tx_list.extend(new_txs) + return { "success": True, - "transaction_ids": [tx.hex() for tx in tx_id_list], + "transaction_ids": [tx.name.hex() for tx in tx_list if tx.type == TransactionType.OUTGOING_CLAWBACK.value], + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_list], } async def delete_unconfirmed_transactions(self, request: Dict[str, Any]) -> EndpointResult: @@ -1389,6 +1413,7 @@ async def send_notification( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: tx: TransactionRecord = await self.service.wallet_state_manager.notification_manager.send_new_notification( bytes32.from_hexstr(request["target"]), @@ -1398,8 +1423,11 @@ async def send_notification( request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) - await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"tx": tx.to_json_dict_convenience(self.service.config)} + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) + + json_tx = tx.to_json_dict_convenience(self.service.config) + return {"tx": json_tx, "transactions": [json_tx]} async def verify_signature(self, request: Dict[str, Any]) -> EndpointResult: """ @@ -1578,6 +1606,7 @@ async def cat_spend( tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), hold_lock: bool = True, + push: bool = True, ) -> EndpointResult: if await self.service.wallet_state_manager.synced() is False: raise ValueError("Wallet needs to be fully synced.") @@ -1642,8 +1671,9 @@ async def cat_spend( memos=memos if memos else None, extra_conditions=extra_conditions, ) - for tx in txs: - await wallet.standard_wallet.push_transaction(tx) + if push: + for tx in txs: + await wallet.standard_wallet.push_transaction(tx) else: txs = await wallet.generate_signed_transaction( amounts, @@ -1655,13 +1685,15 @@ async def cat_spend( memos=memos if memos else None, extra_conditions=extra_conditions, ) - for tx in txs: - await wallet.standard_wallet.push_transaction(tx) + if push: + for tx in txs: + await wallet.standard_wallet.push_transaction(tx) # Return the first transaction, which is expected to be the CAT spend. If a fee is # included, it is currently ordered after the CAT spend. return { "transaction": txs[0].to_json_dict_convenience(self.service.config), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], "transaction_id": txs[0].name, } @@ -1687,7 +1719,11 @@ async def create_offer_for_ids( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = False, ) -> EndpointResult: + if push: + raise ValueError("Cannot push an incomplete spend") # pragma: no cover + offer: Dict[str, int] = request["offer"] fee: uint64 = uint64(request.get("fee", 0)) validate_only: bool = request.get("validate_only", False) @@ -1851,6 +1887,7 @@ async def take_offer( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: offer_hex: str = request["offer"] @@ -1892,7 +1929,14 @@ async def take_offer( solver=solver, extra_conditions=extra_conditions, ) - return {"trade_record": trade_record.to_json_dict_convenience()} + if push: + for tx in tx_records: + await self.service.wallet_state_manager.add_pending_transaction(tx) + + return { + "trade_record": trade_record.to_json_dict_convenience(), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_records], + } async def get_offer(self, request: Dict[str, Any]) -> EndpointResult: trade_mgr = self.service.wallet_state_manager.trade_manager @@ -1951,16 +1995,21 @@ async def cancel_offer( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wsm = self.service.wallet_state_manager secure = request["secure"] trade_id = bytes32.from_hexstr(request["trade_id"]) fee: uint64 = uint64(request.get("fee", 0)) async with self.service.wallet_state_manager.lock: - await wsm.trade_manager.cancel_pending_offers( + txs = await wsm.trade_manager.cancel_pending_offers( [bytes32(trade_id)], tx_config, fee=fee, secure=secure, extra_conditions=extra_conditions ) - return {} + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + + return {"transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs]} @tx_endpoint async def cancel_offers( @@ -1968,6 +2017,7 @@ async def cancel_offers( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: secure = request["secure"] batch_fee: uint64 = uint64(request.get("batch_fee", 0)) @@ -1978,6 +2028,8 @@ async def cancel_offers( else: asset_id = request.get("asset_id", "xch") + all_txs: List[TransactionRecord] = [] + start: int = 0 end: int = start + batch_size trade_mgr = self.service.wallet_state_manager.trade_manager @@ -2007,8 +2059,10 @@ async def cancel_offers( continue async with self.service.wallet_state_manager.lock: - await trade_mgr.cancel_pending_offers( - list(records.keys()), tx_config, batch_fee, secure, records, extra_conditions=extra_conditions + all_txs.extend( + await trade_mgr.cancel_pending_offers( + list(records.keys()), tx_config, batch_fee, secure, records, extra_conditions=extra_conditions + ) ) log.info(f"Cancelled offers {start} to {end} ...") # If fewer records were returned than requested, we're done @@ -2016,7 +2070,12 @@ async def cancel_offers( break start = end end += batch_size - return {"success": True} + + if push: + for tx in all_txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + + return {"success": True, "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in all_txs]} ########################################################################################## # Distributed Identities @@ -2040,11 +2099,11 @@ async def did_update_recovery_ids( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) recovery_list = [] - success: bool = False for _ in request["new_list"]: recovery_list.append(decode_puzzle_hash(_)) if "num_verifications_required" in request: @@ -2055,12 +2114,18 @@ async def did_update_recovery_ids( update_success = await wallet.update_recovery_list(recovery_list, new_amount_verifications_required) # Update coin with new ID info if update_success: - spend_bundle = await wallet.create_update_spend( + txs = await wallet.create_update_spend( tx_config, fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions ) - if spend_bundle is not None: - success = True - return {"success": success} + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "success": True, + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } + else: + return {"success": False, "transactions": []} # pragma: no cover @tx_endpoint async def did_message_spend( @@ -2068,6 +2133,7 @@ async def did_message_spend( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = False, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) @@ -2078,12 +2144,16 @@ async def did_message_spend( for pa in request.get("puzzle_announcements", []): puzzle_announcements.add(bytes.fromhex(pa)) - spend_bundle = ( - await wallet.create_message_spend( - tx_config, coin_announcements, puzzle_announcements, extra_conditions=extra_conditions - ) - ).spend_bundle - return {"success": True, "spend_bundle": spend_bundle} + tx = await wallet.create_message_spend( + tx_config, coin_announcements, puzzle_announcements, extra_conditions=extra_conditions + ) + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "success": True, + "spend_bundle": tx.spend_bundle, + "transactions": [tx.to_json_dict_convenience(self.service.config)], + } async def did_get_info(self, request: Dict[str, Any]) -> EndpointResult: if "coin_id" not in request: @@ -2329,6 +2399,7 @@ async def did_update_metadata( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) @@ -2339,13 +2410,20 @@ async def did_update_metadata( update_success = await wallet.update_metadata(metadata) # Update coin with new ID info if update_success: - spend_bundle = await wallet.create_update_spend( + txs = await wallet.create_update_spend( tx_config, uint64(request.get("fee", 0)), extra_conditions=extra_conditions ) - if spend_bundle is not None: - return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} - else: - return {"success": False, "error": "Couldn't create an update spend bundle."} + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "wallet_id": wallet_id, + "success": True, + "spend_bundle": SpendBundle.aggregate( + [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] + ), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } else: return {"success": False, "error": f"Couldn't update metadata with input: {metadata}"} @@ -2384,7 +2462,9 @@ async def did_get_metadata(self, request: Dict[str, Any]) -> EndpointResult: "metadata": metadata, } - async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: + # TODO: this needs a test + # Don't need full @tx_endpoint decorator here, but "push" is still a valid option + async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: # pragma: no cover wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) if len(request["attest_data"]) < wallet.did_info.num_of_backup_ids_needed: @@ -2409,15 +2489,23 @@ async def did_recovery_spend(self, request: Dict[str, Any]) -> EndpointResult: puzhash = wallet.did_info.temp_puzhash assert wallet.did_info.temp_coin is not None - spend_bundle = await wallet.recovery_spend( - wallet.did_info.temp_coin, - puzhash, - info_list, - pubkey, - message_spend_bundle, - ) + tx = ( + await wallet.recovery_spend( + wallet.did_info.temp_coin, + puzhash, + info_list, + pubkey, + message_spend_bundle, + ) + )[0] + if request.get("push", True): + await self.service.wallet_state_manager.add_pending_transaction(tx) if spend_bundle: - return {"success": True, "spend_bundle": spend_bundle} + return { + "success": True, + "spend_bundle": tx.spend_bundle, + "transactions": [tx.to_json_dict_convenience(self.service.config)], + } else: return {"success": False} @@ -2427,32 +2515,36 @@ async def did_get_pubkey(self, request: Dict[str, Any]) -> EndpointResult: pubkey = bytes((await wallet.wallet_state_manager.get_unused_derivation_record(wallet_id)).pubkey).hex() return {"success": True, "pubkey": pubkey} + # TODO: this needs a test @tx_endpoint async def did_create_attest( self, request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> EndpointResult: + push: bool = True, + ) -> EndpointResult: # pragma: no cover wallet_id = uint32(request["wallet_id"]) wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) async with self.service.wallet_state_manager.lock: info = await wallet.get_info_for_recovery() coin = bytes32.from_hexstr(request["coin_name"]) pubkey = G1Element.from_bytes(hexstr_to_bytes(request["pubkey"])) - spend_bundle, attest_data = await wallet.create_attestment( + tx, message_spend_bundle, attest_data = await wallet.create_attestment( coin, bytes32.from_hexstr(request["puzhash"]), pubkey, tx_config, extra_conditions=extra_conditions, ) - if info is not None and spend_bundle is not None: + if info is not None: + assert tx.spend_bundle is not None return { "success": True, - "message_spend_bundle": bytes(spend_bundle).hex(), + "message_spend_bundle": bytes(message_spend_bundle).hex(), "info": [info[0].hex(), info[1].hex(), info[2]], "attest_data": attest_data, + "transactions": [tx.to_json_dict_convenience(self.service.config)], } else: return {"success": False} @@ -2505,6 +2597,7 @@ async def did_transfer_did( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: if await self.service.wallet_state_manager.synced() is False: raise ValueError("Wallet needs to be fully synced.") @@ -2512,18 +2605,22 @@ async def did_transfer_did( did_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=DIDWallet) puzzle_hash: bytes32 = decode_puzzle_hash(request["inner_address"]) async with self.service.wallet_state_manager.lock: - txs: TransactionRecord = await did_wallet.transfer_did( + txs: List[TransactionRecord] = await did_wallet.transfer_did( puzzle_hash, uint64(request.get("fee", 0)), request.get("with_recovery_info", True), tx_config, extra_conditions=extra_conditions, ) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "success": True, - "transaction": txs.to_json_dict_convenience(self.service.config), - "transaction_id": txs.name, + "transaction": txs[0].to_json_dict_convenience(self.service.config), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + "transaction_id": txs[0].name, } ########################################################################################## @@ -2535,6 +2632,7 @@ async def nft_mint_nft( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: log.debug("Got minting RPC request: %s", request) wallet_id = uint32(request["wallet_id"]) @@ -2586,7 +2684,7 @@ async def nft_mint_nft( else: did_id = decode_puzzle_hash(did_id) - spend_bundle = await nft_wallet.generate_new_nft( + txs = await nft_wallet.generate_new_nft( metadata, tx_config, target_puzhash, @@ -2597,11 +2695,20 @@ async def nft_mint_nft( extra_conditions=extra_conditions, ) nft_id = None - assert spend_bundle is not None + spend_bundle = SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]) for cs in spend_bundle.coin_spends: if cs.coin.puzzle_hash == nft_puzzles.LAUNCHER_PUZZLE_HASH: nft_id = encode_puzzle_hash(cs.coin.name(), AddressType.NFT.hrp(self.service.config)) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle, "nft_id": nft_id} + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "wallet_id": wallet_id, + "success": True, + "spend_bundle": spend_bundle, + "nft_id": nft_id, + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } async def nft_count_nfts(self, request: Dict[str, Any]) -> EndpointResult: wallet_id = request.get("wallet_id", None) @@ -2652,6 +2759,7 @@ async def nft_set_nft_did( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) nft_wallet = self.service.wallet_state_manager.get_wallet(id=wallet_id, required_type=NFTWallet) @@ -2665,14 +2773,19 @@ async def nft_set_nft_did( return {"success": False, "error": "The NFT doesn't support setting a DID."} fee = uint64(request.get("fee", 0)) - spend_bundle = await nft_wallet.set_nft_did( + txs = await nft_wallet.set_nft_did( nft_coin_info, did_id, tx_config, fee=fee, extra_conditions=extra_conditions, ) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} + return { + "wallet_id": wallet_id, + "success": True, + "spend_bundle": SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } @tx_endpoint async def nft_set_did_bulk( @@ -2680,6 +2793,7 @@ async def nft_set_did_bulk( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Bulk set DID for NFTs across different wallets. @@ -2754,8 +2868,9 @@ async def nft_set_did_bulk( # Add all spend bundles to the first tx refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - for tx in refined_tx_list: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in refined_tx_list: + await self.service.wallet_state_manager.add_pending_transaction(tx) for id in coin_ids: await nft_wallet.update_coin_status(id, True) for wallet_id in nft_dict.keys(): @@ -2765,6 +2880,7 @@ async def nft_set_did_bulk( "success": True, "spend_bundle": spend_bundle, "tx_num": len(refined_tx_list), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in refined_tx_list], } else: raise ValueError("Couldn't set DID on given NFT") @@ -2775,6 +2891,7 @@ async def nft_transfer_bulk( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Bulk transfer NFTs to an address. @@ -2845,8 +2962,9 @@ async def nft_transfer_bulk( spend_bundle = SpendBundle.aggregate(spend_bundles) # Add all spend bundles to the first tx refined_tx_list[0] = dataclasses.replace(refined_tx_list[0], spend_bundle=spend_bundle) - for tx in refined_tx_list: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in refined_tx_list: + await self.service.wallet_state_manager.add_pending_transaction(tx) for id in coin_ids: await nft_wallet.update_coin_status(id, True) for wallet_id in nft_dict.keys(): @@ -2856,6 +2974,7 @@ async def nft_transfer_bulk( "success": True, "spend_bundle": spend_bundle, "tx_num": len(refined_tx_list), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in refined_tx_list], } else: raise ValueError("Couldn't transfer given NFTs") @@ -2921,6 +3040,7 @@ async def nft_transfer_nft( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) address = request["target_address"] @@ -2954,9 +3074,16 @@ async def nft_transfer_nft( for tx in txs: if tx.spend_bundle is not None: spend_bundle = tx.spend_bundle - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} + return { + "wallet_id": wallet_id, + "success": True, + "spend_bundle": spend_bundle, + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } except Exception as e: log.exception(f"Failed to transfer NFT: {e}") return {"success": False, "error": str(e)} @@ -3034,6 +3161,7 @@ async def nft_add_uri( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) # Note metadata updater can only add one uri for one field per spend. @@ -3049,10 +3177,18 @@ async def nft_add_uri( nft_coin_info = await nft_wallet.get_nft_coin_by_id(nft_coin_id) fee = uint64(request.get("fee", 0)) - spend_bundle = await nft_wallet.update_metadata( + txs = await nft_wallet.update_metadata( nft_coin_info, key, uri, tx_config, fee=fee, extra_conditions=extra_conditions ) - return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "wallet_id": wallet_id, + "success": True, + "spend_bundle": SpendBundle.aggregate([tx.spend_bundle for tx in txs if tx.spend_bundle is not None]), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], + } async def nft_calculate_royalties(self, request: Dict[str, Any]) -> EndpointResult: return NFTWallet.royalty_calculation( @@ -3069,7 +3205,10 @@ async def nft_mint_bulk( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = False, ) -> EndpointResult: + if push: + raise ValueError("Automatic pushing of nft minting transactions not yet available") # pragma: no cover if await self.service.wallet_state_manager.synced() is False: raise ValueError("Wallet needs to be fully synced.") wallet_id = uint32(request["wallet_id"]) @@ -3280,6 +3419,7 @@ async def create_signed_transaction( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = False, hold_lock: bool = True, ) -> EndpointResult: if "wallet_id" in request: @@ -3371,8 +3511,10 @@ async def _generate_signed_transaction() -> EndpointResult: extra_conditions=extra_conditions, ) signed_tx = tx.to_json_dict_convenience(self.service.config) + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"signed_txs": [signed_tx], "signed_tx": signed_tx} + return {"signed_txs": [signed_tx], "signed_tx": signed_tx, "transactions": [signed_tx]} else: assert isinstance(wallet, CATWallet) @@ -3390,8 +3532,11 @@ async def _generate_signed_transaction() -> EndpointResult: extra_conditions=extra_conditions, ) signed_txs = [tx.to_json_dict_convenience(self.service.config) for tx in txs] + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"signed_txs": signed_txs, "signed_tx": signed_txs[0]} + return {"signed_txs": signed_txs, "signed_tx": signed_txs[0], "transactions": signed_txs} if hold_lock: async with self.service.wallet_state_manager.lock: @@ -3408,6 +3553,7 @@ async def pw_join_pool( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: fee = uint64(request.get("fee", 0)) wallet_id = uint32(request["wallet_id"]) @@ -3433,7 +3579,20 @@ async def pw_join_pool( async with self.service.wallet_state_manager.lock: total_fee, tx, fee_tx = await wallet.join_pool(new_target_state, fee, tx_config) - return {"total_fee": total_fee, "transaction": tx, "fee_transaction": fee_tx} + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) + if fee_tx is not None: + await self.service.wallet_state_manager.add_pending_transaction(fee_tx) + return { + "total_fee": total_fee, + "transaction": tx, + "fee_transaction": fee_tx, + "transactions": [ + transaction.to_json_dict_convenience(self.service.config) + for transaction in (tx, fee_tx) + if transaction is not None + ], + } @tx_endpoint async def pw_self_pool( @@ -3441,6 +3600,7 @@ async def pw_self_pool( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: # Leaving a pool requires two state transitions. # First we transition to PoolSingletonState.LEAVING_POOL @@ -3454,7 +3614,20 @@ async def pw_self_pool( async with self.service.wallet_state_manager.lock: total_fee, tx, fee_tx = await wallet.self_pool(fee, tx_config) - return {"total_fee": total_fee, "transaction": tx, "fee_transaction": fee_tx} + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) + if fee_tx is not None: + await self.service.wallet_state_manager.add_pending_transaction(fee_tx) + return { + "total_fee": total_fee, + "transaction": tx, + "fee_transaction": fee_tx, + "transactions": [ + transaction.to_json_dict_convenience(self.service.config) + for transaction in (tx, fee_tx) + if transaction is not None + ], + } @tx_endpoint async def pw_absorb_rewards( @@ -3462,6 +3635,7 @@ async def pw_absorb_rewards( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Perform a sweep of the p2_singleton rewards controlled by the pool wallet singleton""" if await self.service.wallet_state_manager.synced() is False: @@ -3475,7 +3649,18 @@ async def pw_absorb_rewards( async with self.service.wallet_state_manager.lock: transaction, fee_tx = await wallet.claim_pool_rewards(fee, max_spends_in_tx, tx_config) state: PoolWalletInfo = await wallet.get_current_state() - return {"state": state.to_json_dict(), "transaction": transaction, "fee_transaction": fee_tx} + if push: + await self.service.wallet_state_manager.add_pending_transaction(transaction) + if fee_tx is not None: + await self.service.wallet_state_manager.add_pending_transaction(fee_tx) + return { + "state": state.to_json_dict(), + "transaction": transaction, + "fee_transaction": fee_tx, + "transactions": [ + tx.to_json_dict_convenience(self.service.config) for tx in (transaction, fee_tx) if tx is not None + ], + } async def pw_status(self, request: Dict[str, Any]) -> EndpointResult: """Return the complete state of the Pool wallet with id `request["wallet_id"]`""" @@ -3499,6 +3684,7 @@ async def create_new_dl( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Initialize the DataLayer Wallet (only one can exist)""" if self.service.wallet_state_manager is None: @@ -3518,8 +3704,9 @@ async def create_new_dl( fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) - await self.service.wallet_state_manager.add_pending_transaction(dl_tx) - await self.service.wallet_state_manager.add_pending_transaction(std_tx) + if push: + await self.service.wallet_state_manager.add_pending_transaction(dl_tx) + await self.service.wallet_state_manager.add_pending_transaction(std_tx) except ValueError as e: log.error(f"Error while generating new reporter {e}") return {"success": False, "error": str(e)} @@ -3594,6 +3781,7 @@ async def dl_update_root( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Get the singleton record for the latest singleton of a launcher ID""" if self.service.wallet_state_manager is None: @@ -3608,9 +3796,13 @@ async def dl_update_root( fee=uint64(request.get("fee", 0)), extra_conditions=extra_conditions, ) - for record in records: - await self.service.wallet_state_manager.add_pending_transaction(record) - return {"tx_record": records[0].to_json_dict_convenience(self.service.config)} + if push: + for record in records: + await self.service.wallet_state_manager.add_pending_transaction(record) + return { + "tx_record": records[0].to_json_dict_convenience(self.service.config), + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in records], + } @tx_endpoint async def dl_update_multiple( @@ -3618,6 +3810,7 @@ async def dl_update_multiple( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Update multiple singletons with new merkle roots""" if self.service.wallet_state_manager is None: @@ -3644,9 +3837,13 @@ async def dl_update_multiple( aggregate_spend = SpendBundle.aggregate([aggregate_spend, tx.spend_bundle]) modified_txs.append(dataclasses.replace(tx, spend_bundle=None)) modified_txs[0] = dataclasses.replace(modified_txs[0], spend_bundle=aggregate_spend) - for tx in modified_txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"tx_records": [rec.to_json_dict_convenience(self.service.config) for rec in modified_txs]} + if push: + for tx in modified_txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "tx_records": [rec.to_json_dict_convenience(self.service.config) for rec in modified_txs], + "transactions": [rec.to_json_dict_convenience(self.service.config) for rec in modified_txs], + } async def dl_history(self, request: Dict[str, Any]) -> EndpointResult: """Get the singleton record for the latest singleton of a launcher ID""" @@ -3696,6 +3893,7 @@ async def dl_new_mirror( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Add a new on chain message for a specific singleton""" if self.service.wallet_state_manager is None: @@ -3711,8 +3909,9 @@ async def dl_new_mirror( fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) - for tx in txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], @@ -3724,6 +3923,7 @@ async def dl_delete_mirror( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """Remove an existing mirror for a specific singleton""" if self.service.wallet_state_manager is None: @@ -3739,8 +3939,9 @@ async def dl_delete_mirror( fee=request.get("fee", uint64(0)), extra_conditions=extra_conditions, ) - for tx in txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], @@ -3755,6 +3956,7 @@ async def vc_mint( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Mint a verified credential using the assigned DID @@ -3782,8 +3984,9 @@ class VCMint(Streamable): vc_record, tx_list = await vc_wallet.launch_new_vc( did_id, tx_config, puzhash, parsed_request.fee, extra_conditions=extra_conditions ) - for tx in tx_list: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in tx_list: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "vc_record": vc_record.to_json_dict(), "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in tx_list], @@ -3842,6 +4045,7 @@ async def vc_spend( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Spend a verified credential @@ -3873,8 +4077,9 @@ class VCSpend(Streamable): provider_inner_puzhash=parsed_request.provider_inner_puzhash, extra_conditions=extra_conditions, ) - for tx in txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], @@ -3919,6 +4124,7 @@ async def vc_revoke( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Revoke an on chain VC provided the correct DID is available @@ -3942,8 +4148,9 @@ class VCRevoke(Streamable): parsed_request.fee, extra_conditions=extra_conditions, ) - for tx in txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], @@ -3955,6 +4162,7 @@ async def crcat_approve_pending( request: Dict[str, Any], tx_config: TXConfig = DEFAULT_TX_CONFIG, extra_conditions: Tuple[Condition, ...] = tuple(), + push: bool = True, ) -> EndpointResult: """ Moving any "pending approval" CR-CATs into the spendable balance of the wallet @@ -3981,8 +4189,9 @@ class CRCATApprovePending(Streamable): fee=parsed_request.fee, extra_conditions=extra_conditions, ) - for tx in txs: - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index c14a26d414cc..546c77c597f8 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -192,6 +192,7 @@ async def send_transaction( puzzle_decorator_override: Optional[List[Dict[str, Union[str, int, bool]]]] = None, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> TransactionRecord: if memos is None: send_dict: Dict = { @@ -201,6 +202,7 @@ async def send_transaction( "fee": fee, "puzzle_decorator": puzzle_decorator_override, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **timelock_info.to_json_dict(), } else: @@ -212,6 +214,7 @@ async def send_transaction( "memos": memos, "puzzle_decorator": puzzle_decorator_override, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **timelock_info.to_json_dict(), } send_dict.update(tx_config.to_json_dict()) @@ -225,6 +228,7 @@ async def send_transaction_multi( tx_config: TXConfig, coins: List[Coin] = None, fee: uint64 = uint64(0), + push: bool = True, ) -> TransactionRecord: # Converts bytes to hex for puzzle hashes additions_hex = [] @@ -241,6 +245,7 @@ async def send_transaction_multi( "additions": additions_hex, "coins": coins_json, "fee": fee, + "push": push, **tx_config.to_json_dict(), }, ) @@ -299,6 +304,7 @@ async def create_signed_transactions( wallet_id: Optional[int] = None, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = False, ) -> List[TransactionRecord]: # Converts bytes to hex for puzzle hashes additions_hex = [] @@ -311,6 +317,7 @@ async def create_signed_transactions( "additions": additions_hex, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -354,6 +361,7 @@ async def create_signed_transaction( coin_announcements: Optional[List[Announcement]] = None, puzzle_announcements: Optional[List[Announcement]] = None, wallet_id: Optional[int] = None, + push: bool = False, ) -> TransactionRecord: txs: List[TransactionRecord] = await self.create_signed_transactions( additions=additions, @@ -363,6 +371,7 @@ async def create_signed_transaction( coin_announcements=coin_announcements, puzzle_announcements=puzzle_announcements, wallet_id=wallet_id, + push=push, ) if len(txs) == 0: raise ValueError("`create_signed_transaction` returned empty list!") @@ -473,12 +482,14 @@ async def update_did_recovery_list( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict: request: Dict[str, Any] = { "wallet_id": wallet_id, "new_list": recovery_list, "num_verifications_required": num_verification, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -500,12 +511,14 @@ async def did_message_spend( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = False, ) -> Dict: request: Dict[str, Any] = { "wallet_id": wallet_id, "coin_announcements": coin_announcements, "puzzle_announcements": puzzle_announcements, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -519,11 +532,13 @@ async def update_did_metadata( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict: request: Dict[str, Any] = { "wallet_id": wallet_id, "metadata": metadata, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -600,6 +615,7 @@ async def did_transfer_did( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict: request: Dict[str, Any] = { "wallet_id": wallet_id, @@ -607,6 +623,7 @@ async def did_transfer_did( "fee": fee, "with_recovery_info": with_recovery, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -762,12 +779,14 @@ async def cat_spend( cat_discrepancy: Optional[Tuple[int, Program, Program]] = None, # (extra_delta, tail_reveal, tail_solution) extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> TransactionRecord: send_dict: Dict[str, Any] = { "wallet_id": wallet_id, "fee": fee, "memos": memos if memos else [], "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -841,11 +860,13 @@ async def take_offer( fee=uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> TradeRecord: req = { "offer": offer.to_bech32(), "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -902,6 +923,7 @@ async def cancel_offer( secure: bool = True, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ): await self.fetch( "cancel_offer", @@ -910,6 +932,7 @@ async def cancel_offer( "secure": secure, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), }, @@ -925,6 +948,7 @@ async def cancel_offers( asset_id: Optional[bytes32] = None, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> None: await self.fetch( "cancel_offers", @@ -936,6 +960,7 @@ async def cancel_offers( "cancel_all": cancel_all, "asset_id": None if asset_id is None else asset_id.hex(), "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), }, @@ -970,6 +995,7 @@ async def mint_nft( did_id=None, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ): request: Dict[str, Any] = { "wallet_id": wallet_id, @@ -987,6 +1013,7 @@ async def mint_nft( "did_id": did_id, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -1003,6 +1030,7 @@ async def add_uri_to_nft( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ): request: Dict[str, Any] = { "wallet_id": wallet_id, @@ -1011,6 +1039,7 @@ async def add_uri_to_nft( "key": key, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -1047,6 +1076,7 @@ async def transfer_nft( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ): request: Dict[str, Any] = { "wallet_id": wallet_id, @@ -1054,6 +1084,7 @@ async def transfer_nft( "target_address": target_address, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -1079,6 +1110,7 @@ async def set_nft_did( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ): request: Dict[str, Any] = { "wallet_id": wallet_id, @@ -1086,6 +1118,7 @@ async def set_nft_did( "nft_coin_id": nft_coin_id, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -1116,6 +1149,7 @@ async def nft_mint_bulk( fee: Optional[int] = 0, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = False, ) -> Dict: request = { "wallet_id": wallet_id, @@ -1133,6 +1167,7 @@ async def nft_mint_bulk( "mint_from_did": mint_from_did, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -1351,6 +1386,7 @@ async def vc_mint( fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Tuple[VCRecord, List[TransactionRecord]]: response = await self.fetch( "vc_mint", @@ -1359,6 +1395,7 @@ async def vc_mint( "target_address": encode_puzzle_hash(target_address, "rpc") if target_address is not None else None, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), }, @@ -1385,6 +1422,7 @@ async def vc_spend( fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> List[TransactionRecord]: response = await self.fetch( "vc_spend", @@ -1397,6 +1435,7 @@ async def vc_spend( else provider_inner_puzhash, "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), }, @@ -1417,6 +1456,7 @@ async def vc_revoke( fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> List[TransactionRecord]: response = await self.fetch( "vc_revoke", @@ -1424,6 +1464,7 @@ async def vc_revoke( "vc_parent_id": vc_parent_id.hex(), "fee": fee, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), }, @@ -1436,6 +1477,7 @@ async def crcat_approve_pending( min_amount_to_claim: uint64, tx_config: TXConfig, fee: uint64 = uint64(0), + push: bool = True, ) -> List[TransactionRecord]: response = await self.fetch( "crcat_approve_pending", @@ -1443,6 +1485,7 @@ async def crcat_approve_pending( "wallet_id": wallet_id, "min_amount_to_claim": min_amount_to_claim, "fee": fee, + "push": push, **tx_config.to_json_dict(), }, ) diff --git a/chia/wallet/did_wallet/did_wallet.py b/chia/wallet/did_wallet/did_wallet.py index 5bcbc1469018..6aa8680fb816 100644 --- a/chia/wallet/did_wallet/did_wallet.py +++ b/chia/wallet/did_wallet/did_wallet.py @@ -124,14 +124,14 @@ async def create_new_did_wallet( raise ValueError("Not enough balance") try: - spend_bundle = await self.generate_new_decentralised_id(amount, DEFAULT_TX_CONFIG, fee) + txs = await self.generate_new_decentralised_id(amount, DEFAULT_TX_CONFIG, fee) except Exception: await wallet_state_manager.user_store.delete_wallet(self.id()) raise - if spend_bundle is None: - await wallet_state_manager.user_store.delete_wallet(self.id()) - raise ValueError("Failed to create spend.") + for tx in txs: + await self.wallet_state_manager.add_pending_transaction(tx) + await self.wallet_state_manager.add_new_wallet(self) return self @@ -534,7 +534,7 @@ def get_name(self): async def create_update_spend( self, tx_config: TXConfig, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple() - ): + ) -> List[TransactionRecord]: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coin = await self.get_coin() @@ -600,7 +600,6 @@ async def create_update_spend( if chia_tx is not None and chia_tx.spend_bundle is not None: spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) - await self.wallet_state_manager.add_pending_transaction(chia_tx) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -620,9 +619,12 @@ async def create_update_spend( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) - await self.wallet_state_manager.add_pending_transaction(did_record) - return spend_bundle + txs = [did_record] + if chia_tx is not None: + txs.append(chia_tx) + + return txs async def transfer_did( self, @@ -631,7 +633,7 @@ async def transfer_did( with_recovery: bool, tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> TransactionRecord: + ) -> List[TransactionRecord]: """ Transfer the current DID to another owner :param new_puzhash: New owner's p2_puzzle @@ -697,7 +699,6 @@ async def transfer_did( if chia_tx is not None and chia_tx.spend_bundle is not None: spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) chia_tx = dataclasses.replace(chia_tx, spend_bundle=None) - await self.wallet_state_manager.add_pending_transaction(chia_tx) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -717,8 +718,10 @@ async def transfer_did( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) - await self.wallet_state_manager.add_pending_transaction(did_record) - return did_record + txs = [did_record] + if chia_tx is not None: + txs.append(chia_tx) + return txs # The message spend can tests\wallet\rpc\test_wallet_rpc.py send messages and also change your innerpuz async def create_message_spend( @@ -806,7 +809,7 @@ async def create_message_spend( ) # This is used to cash out, or update the id_list - async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig): + async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig) -> List[TransactionRecord]: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coin = await self.get_coin() @@ -857,8 +860,7 @@ async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig): memos=list(compute_memos(spend_bundle).items()), valid_times=ConditionValidTimes(), ) - await self.wallet_state_manager.add_pending_transaction(did_record) - return spend_bundle + return [did_record] # Pushes a SpendBundle to create a message coin on the blockchain # Returns a SpendBundle for the recoverer to spend the message coin @@ -869,7 +871,7 @@ async def create_attestment( pubkey: G1Element, tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Tuple[SpendBundle, str]: + ) -> Tuple[TransactionRecord, SpendBundle, str]: """ Create an attestment :param recovering_coin_name: Coin ID of the DID @@ -941,8 +943,7 @@ async def create_attestment( ) attest_str: str = f"{self.get_my_DID()}:{bytes(message_spend_bundle).hex()}:{coin.parent_coin_info.hex()}:" attest_str += f"{self.did_info.current_inner.get_tree_hash().hex()}:{coin.amount}" - await self.wallet_state_manager.add_pending_transaction(did_record) - return message_spend_bundle, attest_str + return did_record, message_spend_bundle, attest_str async def get_info_for_recovery(self) -> Optional[Tuple[bytes32, bytes32, uint64]]: assert self.did_info.current_inner is not None @@ -997,7 +998,7 @@ async def recovery_spend( parent_innerpuzhash_amounts_for_recovery_ids: List[Tuple[bytes, bytes, int]], pubkey: G1Element, spend_bundle: SpendBundle, - ) -> SpendBundle: + ) -> List[TransactionRecord]: assert self.did_info.origin_coin is not None # innersol is mode new_amount_or_p2_solution new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey recovery_list_reveal my_id) # noqa @@ -1068,7 +1069,6 @@ async def recovery_spend( memos=list(compute_memos(spend_bundle).items()), valid_times=ConditionValidTimes(), ) - await self.wallet_state_manager.add_pending_transaction(did_record) new_did_info = DIDInfo( self.did_info.origin_coin, self.did_info.backup_ids, @@ -1082,7 +1082,7 @@ async def recovery_spend( self.did_info.metadata, ) await self.save_info(new_did_info) - return spend_bundle + return [did_record] async def get_new_p2_inner_hash(self) -> bytes32: puzzle = await self.get_new_p2_inner_puzzle() @@ -1221,14 +1221,12 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: async def generate_new_decentralised_id( self, amount: uint64, tx_config: TXConfig, fee: uint64 = uint64(0) - ) -> Optional[SpendBundle]: + ) -> List[TransactionRecord]: """ This must be called under the wallet state manager lock """ coins = await self.standard_wallet.select_coins(uint64(amount + fee), tx_config.coin_selection_config) - if coins is None: - return None origin = coins.copy().pop() genesis_launcher_puz = SINGLETON_LAUNCHER_PUZZLE @@ -1273,9 +1271,6 @@ async def generate_new_decentralised_id( await self.add_parent(eve_coin.parent_coin_info, eve_parent) await self.add_parent(eve_coin.name(), future_parent) - if tx_record.spend_bundle is None: - return None - # Only want to save this information if the transaction is valid did_info: DIDInfo = DIDInfo( launcher_coin, @@ -1291,6 +1286,7 @@ async def generate_new_decentralised_id( ) await self.save_info(did_info) eve_spend = await self.generate_eve_spend(eve_coin, did_full_puz, did_inner) + assert tx_record.spend_bundle is not None full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend, launcher_sb]) assert self.did_info.origin_coin is not None assert self.did_info.current_inner is not None @@ -1315,9 +1311,7 @@ async def generate_new_decentralised_id( valid_times=ConditionValidTimes(), ) regular_record = dataclasses.replace(tx_record, spend_bundle=None) - await self.wallet_state_manager.add_pending_transaction(regular_record) - await self.wallet_state_manager.add_pending_transaction(did_record) - return full_spend + return [did_record, regular_record] async def generate_eve_spend( self, diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index d211c5c5e7a8..48d3c9ff01dd 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -328,9 +328,8 @@ async def generate_new_nft( percentage: uint16 = uint16(0), did_id: Optional[bytes] = None, fee: uint64 = uint64(0), - push_tx: bool = True, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Optional[SpendBundle]: + ) -> List[TransactionRecord]: """ This must be called under the wallet state manager lock """ @@ -404,8 +403,7 @@ async def generate_new_nft( eve_coin = Coin(launcher_coin.name(), eve_fullpuz_hash, uint64(amount)) if tx_record.spend_bundle is None: - self.log.error("Couldn't produce a launcher spend") - return None + raise ValueError("Couldn't produce a launcher spend") # pragma: no cover bundles_to_agg = [tx_record.spend_bundle, launcher_sb] @@ -435,10 +433,7 @@ async def generate_new_nft( memos=[[target_puzzle_hash]], ) txs.append(dataclasses.replace(tx_record, spend_bundle=None)) - if push_tx: - for tx in txs: - await self.wallet_state_manager.add_pending_transaction(tx) - return SpendBundle.aggregate([x.spend_bundle for x in txs if x.spend_bundle is not None]) + return txs async def sign(self, spend_bundle: SpendBundle, puzzle_hashes: Optional[List[bytes32]] = None) -> SpendBundle: if puzzle_hashes is None: @@ -486,7 +481,7 @@ async def update_metadata( tx_config: TXConfig, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Optional[SpendBundle]: + ) -> List[TransactionRecord]: uncurried_nft = UncurriedNFT.uncurry(*nft_coin_info.full_puzzle.uncurry()) assert uncurried_nft is not None puzzle_hash = uncurried_nft.p2_puzzle.get_tree_hash() @@ -505,11 +500,9 @@ async def update_metadata( metadata_update=(key, uri), extra_conditions=extra_conditions, ) - for tx in txs: - await self.wallet_state_manager.add_pending_transaction(tx) await self.update_coin_status(nft_coin_info.coin.name(), True) self.wallet_state_manager.state_changed("nft_coin_updated", self.wallet_info.id) - return SpendBundle.aggregate([x.spend_bundle for x in txs if x.spend_bundle is not None]) + return txs async def get_current_nfts(self, start_index: int = 0, count: int = 50) -> List[NFTCoinInfo]: return await self.nft_store.get_nft_list(wallet_id=self.id(), start_index=start_index, count=count) @@ -1211,7 +1204,7 @@ async def set_nft_did( tx_config: TXConfig, fee: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> SpendBundle: + ) -> List[TransactionRecord]: self.log.debug("Setting NFT DID with parameters: nft=%s did=%s", nft_coin_info, did_id) unft = UncurriedNFT.uncurry(*nft_coin_info.full_puzzle.uncurry()) assert unft is not None @@ -1234,15 +1227,10 @@ async def set_nft_did( additional_bundles=additional_bundles, extra_conditions=extra_conditions, ) - spend_bundle = SpendBundle.aggregate([x.spend_bundle for x in nft_tx_record if x.spend_bundle is not None]) - if spend_bundle: - for tx in nft_tx_record: - await self.wallet_state_manager.add_pending_transaction(tx) - await self.update_coin_status(nft_coin_info.coin.name(), True) - self.wallet_state_manager.state_changed("nft_coin_did_set", self.wallet_info.id) - return spend_bundle - else: - raise ValueError("Couldn't set DID on given NFT") + + await self.update_coin_status(nft_coin_info.coin.name(), True) + self.wallet_state_manager.state_changed("nft_coin_did_set", self.wallet_info.id) + return nft_tx_record async def mint_from_did( self, diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 5af9a4b3c820..f3272fb3ba74 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -230,7 +230,7 @@ async def cancel_pending_offers( secure: bool = True, # Cancel with a transaction on chain trade_cache: Dict[bytes32, TradeRecord] = {}, # Optional pre-fetched trade records for optimization extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Optional[List[TransactionRecord]]: + ) -> List[TransactionRecord]: """This will create a transaction that includes coins that were offered""" all_txs: List[TransactionRecord] = [] @@ -338,8 +338,8 @@ async def cancel_pending_offers( # Aggregate spend bundles to the first tx if len(all_txs) > 0: all_txs[0] = dataclasses.replace(all_txs[0], spend_bundle=SpendBundle.aggregate(bundles)) - for tx in all_txs: - await self.wallet_state_manager.add_pending_transaction(tx_record=dataclasses.replace(tx, fee_amount=fee)) + + all_txs = [dataclasses.replace(tx, fee_amount=fee) for tx in all_txs] return all_txs @@ -814,9 +814,6 @@ async def respond_to_offer( memos=[], valid_times=ConditionValidTimes(), ) - await self.wallet_state_manager.add_pending_transaction(push_tx) - for tx in tx_records: - await self.wallet_state_manager.add_transaction(tx) return trade_record, [push_tx, *tx_records] diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index aee0e1eaff66..dc95c4d47c58 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import dataclasses import logging import multiprocessing.context import time @@ -808,12 +809,16 @@ async def auto_claim_coins(self) -> None: if current_timestamp - coin_timestamp >= metadata.time_lock: clawback_coins[coin.coin] = metadata if len(clawback_coins) >= self.config.get("auto_claim", {}).get("batch_size", 50): - await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config) + txs = await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config) + for tx in txs: + await self.add_pending_transaction(tx) clawback_coins = {} except Exception as e: self.log.error(f"Failed to claim clawback coin {coin.coin.name().hex()}: %s", e) if len(clawback_coins) > 0: - await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config) + txs = await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config) + for tx in txs: + await self.add_pending_transaction(tx) async def spend_clawback_coins( self, @@ -822,7 +827,7 @@ async def spend_clawback_coins( tx_config: TXConfig, force: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> List[bytes32]: + ) -> List[TransactionRecord]: assert len(clawback_coins) > 0 coin_spends: List[CoinSpend] = [] message: bytes32 = std_hash(b"".join([c.name() for c in clawback_coins.keys()])) @@ -871,12 +876,14 @@ async def spend_clawback_coins( if len(coin_spends) == 0: return [] spend_bundle: SpendBundle = await self.sign_transaction(coin_spends) + tx_list: List[TransactionRecord] = [] if fee > 0: chia_tx = await self.main_wallet.create_tandem_xch_tx( fee, tx_config, Announcement(coin_spends[0].coin.name(), message) ) assert chia_tx.spend_bundle is not None spend_bundle = SpendBundle.aggregate([spend_bundle, chia_tx.spend_bundle]) + tx_list.append(dataclasses.replace(chia_tx, spend_bundle=None)) assert derivation_record is not None tx_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -897,11 +904,11 @@ async def spend_clawback_coins( memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) - await self.add_pending_transaction(tx_record) + tx_list.append(tx_record) # Update incoming tx to prevent double spend and mark it is pending for coin_spend in coin_spends: await self.tx_store.increment_sent(coin_spend.coin.name(), "", MempoolInclusionStatus.PENDING, None) - return [tx_record.name] + return tx_list async def filter_spam(self, new_coin_state: List[CoinState]) -> List[CoinState]: xch_spam_amount = self.config.get("xch_spam_amount", 1000000) diff --git a/tests/wallet/cat_wallet/test_trades.py b/tests/wallet/cat_wallet/test_trades.py index fb7621738197..78f5057e0e6c 100644 --- a/tests/wallet/cat_wallet/test_trades.py +++ b/tests/wallet/cat_wallet/test_trades.py @@ -352,6 +352,8 @@ async def test_cat_trades( tx_config, fee=uint64(1), ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -446,6 +448,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, tx_config, ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -497,6 +501,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, tx_config, ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) assert trade_take is not None assert tx_records is not None @@ -577,6 +583,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, tx_config, ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) assert trade_take is not None assert tx_records is not None @@ -638,6 +646,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, tx_config, ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) assert trade_take is not None assert tx_records is not None @@ -683,6 +693,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, tx_config, ) + for tx in tx_records: + await wallet_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) assert trade_take is not None assert tx_records is not None @@ -803,6 +815,8 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: # trade_take, tx_records = await trade_manager_taker.respond_to_offer( # Offer.from_bytes(trade_make.offer), # ) + # for tx in tx_records: + # await wallet_taker.wallet_state_manager.add_pending_transaction(tx) # await time_out_assert(15, full_node.txs_in_mempool, True, tx_records) # assert trade_take is not None # assert tx_records is not None @@ -819,6 +833,8 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=fee, secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) await full_node.process_transaction_records(records=txs) @@ -859,6 +875,8 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) await full_node.process_transaction_records(records=txs) @@ -912,6 +930,8 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) await full_node.process_transaction_records(records=txs) @@ -970,12 +990,16 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: peer = wallet_node_taker.get_full_node_peer() offer = Offer.from_bytes(trade_make.offer) tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) + for tx in txs1: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) # we shouldn't be able to respond to a duplicate offer with pytest.raises(ValueError): await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CONFIRM, trade_manager_taker, tr1) # pushing into mempool while already in it should fail tr2, txs2 = await trade_manager_trader.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) + for tx in txs2: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert await trade_manager_trader.get_coins_of_interest() offer_tx_records: List[TransactionRecord] = await wallet_node_maker.wallet_state_manager.tx_store.get_not_sent() await full_node.process_transaction_records(records=offer_tx_records) @@ -1033,6 +1057,8 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: bundle = dataclasses.replace(offer._bundle, aggregated_signature=G2Element()) offer = dataclasses.replace(offer, _bundle=bundle) tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) + for tx in txs1: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) wallet_node_taker.wallet_tx_resend_timeout_secs = 0 # don't wait for resend for _ in range(10): print(await wallet_node_taker._resend_queue()) @@ -1092,5 +1118,7 @@ async def get_trade_and_status(trade_manager, trade) -> TradeStatus: tr1, txs1 = await trade_manager_taker.respond_to_offer( offer, peer, DEFAULT_TX_CONFIG, fee=uint64(1000000000000) ) + for tx in txs1: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await full_node.process_transaction_records(records=txs1) await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, tr1) diff --git a/tests/wallet/db_wallet/test_dl_offers.py b/tests/wallet/db_wallet/test_dl_offers.py index fc4d0d99e6e8..6397ffe5312c 100644 --- a/tests/wallet/db_wallet/test_dl_offers.py +++ b/tests/wallet/db_wallet/test_dl_offers.py @@ -174,6 +174,8 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: ), fee=fee, ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert offer_taker is not None assert tx_records is not None @@ -295,6 +297,8 @@ async def test_dl_offer_cancellation(wallets_prefarm: Any, trusted: bool) -> Non cancellation_txs = await trade_manager.cancel_pending_offers( [offer.trade_id], DEFAULT_TX_CONFIG, fee=uint64(2_000_000_000_000), secure=True ) + for tx in cancellation_txs: + await trade_manager.wallet_state_manager.add_pending_transaction(tx) assert len(cancellation_txs) == 2 await time_out_assert(15, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager, offer) await full_node_api.process_transaction_records(records=cancellation_txs) @@ -472,6 +476,8 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: ), fee=fee, ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert offer_taker is not None assert tx_records is not None diff --git a/tests/wallet/did_wallet/test_did.py b/tests/wallet/did_wallet/test_did.py index a5dbfae115ac..6c2b53eb94e1 100644 --- a/tests/wallet/did_wallet/test_did.py +++ b/tests/wallet/did_wallet/test_did.py @@ -206,9 +206,11 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes pubkey = bytes( (await did_wallet_2.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id)).pubkey ) - message_spend_bundle, attest_data = await did_wallet_0.create_attestment( + message_tx, message_spend_bundle, attest_data = await did_wallet_0.create_attestment( did_wallet_2.did_info.temp_coin.name(), newpuzhash, pubkey, DEFAULT_TX_CONFIG ) + await did_wallet_0.wallet_state_manager.add_pending_transaction(message_tx) + assert message_spend_bundle is not None spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_0.id() ) @@ -224,15 +226,19 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes ) = await did_wallet_2.load_attest_files_for_recovery_spend([attest_data]) assert message_spend_bundle == test_message_spend_bundle - spend_bundle = await did_wallet_2.recovery_spend( + txs = await did_wallet_2.recovery_spend( did_wallet_2.did_info.temp_coin, newpuzhash, test_info_list, pubkey, test_message_spend_bundle, ) + assert txs[0].spend_bundle is not None + await did_wallet_2.wallet_state_manager.add_pending_transaction(txs[0]) - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) + await time_out_assert_not_none( + 5, full_node_api.full_node.mempool_manager.get_spendbundle, txs[0].spend_bundle.name() + ) await full_node_api.farm_blocks_to_wallet(1, wallet_0) @@ -243,7 +249,9 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes assert wallet.wallet_state_manager.wallets[wallet.id()] == wallet some_ph = 32 * b"\2" - await did_wallet_2.create_exit_spend(some_ph, DEFAULT_TX_CONFIG) + txs = await did_wallet_2.create_exit_spend(some_ph, DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet_2.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_2.id() @@ -367,16 +375,20 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_2.wallet_info.id) ).pubkey new_ph = did_wallet_4.did_info.temp_puzhash - message_spend_bundle, attest1 = await did_wallet.create_attestment( + message_tx, message_spend_bundle, attest1 = await did_wallet.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) + await did_wallet.wallet_state_manager.add_pending_transaction(message_tx) + assert message_spend_bundle is not None spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) spend_bundle = spend_bundle_list[0].spend_bundle await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) - message_spend_bundle2, attest2 = await did_wallet_2.create_attestment( + message_tx2, message_spend_bundle2, attest2 = await did_wallet_2.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) + await did_wallet_2.wallet_state_manager.add_pending_transaction(message_tx2) + assert message_spend_bundle2 is not None spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_2.id() ) @@ -394,7 +406,8 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w await full_node_api.farm_blocks_to_wallet(1, wallet) await time_out_assert(15, did_wallet_4.get_confirmed_balance, 0) await time_out_assert(15, did_wallet_4.get_unconfirmed_balance, 0) - await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle) + txs = await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, message_spend_bundle) + await did_wallet_4.wallet_state_manager.add_pending_transaction(txs[0]) spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_4.id() ) @@ -459,9 +472,7 @@ async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes info = Program.to([]) pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record(did_wallet.wallet_info.id)).pubkey try: - spend_bundle = await did_wallet.recovery_spend( - coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([])) - ) + await did_wallet.recovery_spend(coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))) except Exception: # We expect a CLVM 80 error for this test pass @@ -531,7 +542,9 @@ async def test_did_find_lost_did(self, self_hostname, two_wallet_nodes, trusted) recovery_list = [bytes32.fromhex(did_wallet.get_my_DID())] await did_wallet.update_recovery_list(recovery_list, uint64(1)) assert did_wallet.did_info.backup_ids == recovery_list - await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) + txs = await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) spend_bundle = spend_bundle_list[0].spend_bundle await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) @@ -611,7 +624,9 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, recovery_list = [bytes.fromhex(did_wallet_2.get_my_DID())] await did_wallet.update_recovery_list(recovery_list, uint64(1)) assert did_wallet.did_info.backup_ids == recovery_list - await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) + txs = await did_wallet.create_update_spend(DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) @@ -638,7 +653,11 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, await did_wallet_3.wallet_state_manager.get_unused_derivation_record(did_wallet_3.wallet_info.id) ).pubkey await time_out_assert(15, did_wallet.get_confirmed_balance, 101) - attest_data = (await did_wallet.create_attestment(coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG))[1] + message_tx, message_spend_bundle, attest_data = await did_wallet.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG + ) + await did_wallet.wallet_state_manager.add_pending_transaction(message_tx) + assert message_spend_bundle is not None spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) spend_bundle = spend_bundle_list[0].spend_bundle @@ -649,7 +668,8 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, info, message_spend_bundle, ) = await did_wallet_3.load_attest_files_for_recovery_spend([attest_data]) - await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle) + txs = await did_wallet_3.recovery_spend(coin, new_ph, info, pubkey, message_spend_bundle) + await did_wallet_3.wallet_state_manager.add_pending_transaction(txs[0]) spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_3.id() ) @@ -676,7 +696,11 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, pubkey = ( await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id) ).pubkey - attest1 = (await did_wallet_3.create_attestment(coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG))[1] + message_tx, message_spend_bundle, attest1 = await did_wallet_3.create_attestment( + coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG + ) + await did_wallet_3.wallet_state_manager.add_pending_transaction(message_tx) + assert message_spend_bundle is not None spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_3.id() ) @@ -689,7 +713,8 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, test_info_list, test_message_spend_bundle, ) = await did_wallet_4.load_attest_files_for_recovery_spend([attest1]) - await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, test_message_spend_bundle) + txs = await did_wallet_4.recovery_spend(coin, new_ph, test_info_list, pubkey, test_message_spend_bundle) + await did_wallet_2.wallet_state_manager.add_pending_transaction(txs[0]) spend_bundle_list = await wallet_node_2.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_4.id() @@ -764,7 +789,9 @@ async def test_did_transfer(self, self_hostname, two_wallet_nodes, with_recovery await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) # Transfer DID new_puzhash = await wallet2.get_new_puzzlehash() - await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery, DEFAULT_TX_CONFIG) + txs = await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery, DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet_1.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_1.id() ) @@ -840,7 +867,9 @@ async def test_update_recovery_list(self, self_hostname, two_wallet_nodes, trust await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) await did_wallet_1.update_recovery_list([bytes(ph)], 1) - await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG) + txs = await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet_1.wallet_state_manager.add_pending_transaction(tx) await full_node_api.farm_blocks_to_wallet(1, wallet) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) @@ -1046,7 +1075,9 @@ async def test_update_metadata(self, self_hostname, two_wallet_nodes, trusted): metadata = {} metadata["Twitter"] = "http://www.twitter.com" await did_wallet_1.update_metadata(metadata) - await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG, fee) + txs = await did_wallet_1.create_update_spend(DEFAULT_TX_CONFIG, fee) + for tx in txs: + await did_wallet_1.wallet_state_manager.add_pending_transaction(tx) transaction_records = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_1.id() ) diff --git a/tests/wallet/nft_wallet/test_nft_1_offers.py b/tests/wallet/nft_wallet/test_nft_1_offers.py index e132dd53d86e..401bdce138d6 100644 --- a/tests/wallet/nft_wallet/test_nft_1_offers.py +++ b/tests/wallet/nft_wallet/test_nft_1_offers.py @@ -129,7 +129,7 @@ async def test_nft_offer_sell_nft( ] ) - sb = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -137,10 +137,13 @@ async def test_nft_offer_sell_nft( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -182,6 +185,8 @@ async def test_nft_offer_sell_nft( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, mempool_not_empty, True, full_node_api) @@ -283,7 +288,7 @@ async def test_nft_offer_request_nft( await time_out_assert(20, wallet_taker.get_unconfirmed_balance, funds - 1) await time_out_assert(20, wallet_taker.get_confirmed_balance, funds - 1) - sb = await nft_wallet_taker.generate_new_nft( + txs = await nft_wallet_taker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -291,10 +296,13 @@ async def test_nft_offer_request_nft( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_taker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -338,6 +346,8 @@ async def test_nft_offer_request_nft( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None @@ -436,7 +446,7 @@ async def test_nft_offer_sell_did_to_did( await time_out_assert(20, wallet_maker.get_unconfirmed_balance, funds - 1) await time_out_assert(20, wallet_maker.get_confirmed_balance, funds - 1) - sb = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -444,10 +454,13 @@ async def test_nft_offer_sell_did_to_did( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(bytes32([0] * 32))) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -506,6 +519,8 @@ async def test_nft_offer_sell_did_to_did( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -610,7 +625,7 @@ async def test_nft_offer_sell_nft_for_cat( ] ) - sb = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -618,10 +633,13 @@ async def test_nft_offer_sell_nft_for_cat( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -706,6 +724,8 @@ async def test_nft_offer_sell_nft_for_cat( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -807,7 +827,7 @@ async def test_nft_offer_request_nft_for_cat( ] ) - sb = await nft_wallet_taker.generate_new_nft( + txs = await nft_wallet_taker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -815,10 +835,13 @@ async def test_nft_offer_request_nft_for_cat( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_taker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=20) @@ -912,6 +935,8 @@ async def test_nft_offer_request_nft_for_cat( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -1005,7 +1030,7 @@ async def test_nft_offer_sell_cancel( ] ) - sb = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -1013,10 +1038,13 @@ async def test_nft_offer_sell_cancel( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker], timeout=20) @@ -1044,6 +1072,8 @@ async def test_nft_offer_sell_cancel( txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=FEE, secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) async def get_trade_and_status(trade_manager: Any, trade: Any) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) @@ -1127,7 +1157,7 @@ async def test_nft_offer_sell_cancel_in_batch( ] ) - sb = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash, @@ -1135,10 +1165,13 @@ async def test_nft_offer_sell_cancel_in_batch( royalty_basis_pts, did_id, ) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) @@ -1167,6 +1200,8 @@ async def test_nft_offer_sell_cancel_in_batch( txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=FEE, secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) async def get_trade_and_status(trade_manager: Any, trade: Any) -> TradeStatus: trade_rec = await trade_manager.get_trade_by_id(trade.trade_id) @@ -1346,7 +1381,7 @@ async def test_complex_nft_offer( ) return else: - sb_maker = await nft_wallet_maker.generate_new_nft( + txs = await nft_wallet_maker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash_maker, @@ -1354,8 +1389,14 @@ async def test_complex_nft_offer( uint16(royalty_basis_pts_maker), did_id_maker, ) - - sb_taker_1 = await nft_wallet_taker.generate_new_nft( + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) + + txs = await nft_wallet_taker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash_taker, @@ -1363,10 +1404,12 @@ async def test_complex_nft_offer( royalty_basis_pts_taker_1, did_id_taker, ) - assert sb_maker is not None - assert sb_taker_1 is not None - await time_out_assert_not_none(10, full_node_api.full_node.mempool_manager.get_spendbundle, sb_maker.name()) - await time_out_assert_not_none(10, full_node_api.full_node.mempool_manager.get_spendbundle, sb_taker_1.name()) + for tx in txs: + await nft_wallet_taker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) funds_maker -= 1 @@ -1380,7 +1423,7 @@ async def test_complex_nft_offer( await time_out_assert(30, get_nft_count, 1, nft_wallet_taker) # MAke one more NFT for the taker - sb_taker_2 = await nft_wallet_taker.generate_new_nft( + txs = await nft_wallet_taker.generate_new_nft( metadata, DEFAULT_TX_CONFIG, target_puzhash_taker, @@ -1388,8 +1431,12 @@ async def test_complex_nft_offer( royalty_basis_pts_taker_2, did_id_taker, ) - assert sb_taker_2 is not None - await time_out_assert_not_none(10, full_node_api.full_node.mempool_manager.get_spendbundle, sb_taker_2.name()) + for tx in txs: + await nft_wallet_taker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_token)) funds_taker -= 1 @@ -1458,6 +1505,8 @@ async def test_complex_nft_offer( DEFAULT_TX_CONFIG, fee=FEE, ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None await full_node_api.process_transaction_records(records=tx_records) @@ -1559,6 +1608,8 @@ async def get_cat_wallet_and_check_balance(asset_id: str, wsm: Any) -> uint128: DEFAULT_TX_CONFIG, fee=uint64(0), ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None await time_out_assert(20, mempool_not_empty, True, full_node_api) diff --git a/tests/wallet/nft_wallet/test_nft_offers.py b/tests/wallet/nft_wallet/test_nft_offers.py index a636d89ae994..98bfd00f891f 100644 --- a/tests/wallet/nft_wallet/test_nft_offers.py +++ b/tests/wallet/nft_wallet/test_nft_offers.py @@ -102,9 +102,13 @@ async def test_nft_offer_with_fee( ] ) - sb = await nft_wallet_maker.generate_new_nft(metadata, tx_config) - assert sb - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_maker.generate_new_nft(metadata, tx_config) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -147,6 +151,8 @@ async def test_nft_offer_with_fee( tx_config, fee=taker_fee, ) + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -214,7 +220,8 @@ async def test_nft_offer_with_fee( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, tx_config, fee=taker_fee ) - + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -294,9 +301,13 @@ async def test_nft_offer_cancellations( ] ) - sb = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -331,6 +342,8 @@ async def test_nft_offer_cancellations( txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=cancel_fee, secure=True ) + for tx in txs: + await trade_manager_maker.wallet_state_manager.add_pending_transaction(tx) await time_out_assert(20, get_trade_and_status, TradeStatus.PENDING_CANCEL, trade_manager_maker, trade_make) await full_node_api.process_transaction_records(records=txs) @@ -411,9 +424,13 @@ async def test_nft_offer_with_metadata_update( ] ) - sb = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -426,11 +443,12 @@ async def test_nft_offer_with_metadata_update( url_to_add = "https://new_url.com" key = "mu" fee_for_update = uint64(10) - update_sb = await nft_wallet_maker.update_metadata( - nft_to_update, key, url_to_add, DEFAULT_TX_CONFIG, fee=fee_for_update - ) + txs = await nft_wallet_maker.update_metadata(nft_to_update, key, url_to_add, DEFAULT_TX_CONFIG, fee=fee_for_update) mempool_mgr = full_node_api.full_node.mempool_manager - await time_out_assert_not_none(20, mempool_mgr.get_spendbundle, update_sb.name()) # type: ignore + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none(20, mempool_mgr.get_spendbundle, tx.spend_bundle.name()) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -467,7 +485,8 @@ async def test_nft_offer_with_metadata_update( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=taker_fee ) - + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -553,9 +572,13 @@ async def test_nft_offer_nft_for_cat( ] ) - sb = await nft_wallet_maker.generate_new_nft(metadata, tx_config) - assert sb - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_maker.generate_new_nft(metadata, tx_config) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -641,7 +664,8 @@ async def test_nft_offer_nft_for_cat( tx_config, fee=taker_fee, ) - + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -721,7 +745,8 @@ async def test_nft_offer_nft_for_cat( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, tx_config, fee=taker_fee ) - + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None @@ -809,9 +834,13 @@ async def test_nft_offer_nft_for_nft( ] ) - sb = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_maker.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) metadata_2 = Program.to( [ @@ -819,9 +848,14 @@ async def test_nft_offer_nft_for_nft( ("h", "0xD4584AD463139FA8C0D9F68F4B59F183"), ] ) - sb_2 = await nft_wallet_taker.generate_new_nft(metadata_2, DEFAULT_TX_CONFIG) - assert sb_2 - await time_out_assert_not_none(20, full_node_api.full_node.mempool_manager.get_spendbundle, sb_2.name()) + + txs = await nft_wallet_taker.generate_new_nft(metadata_2, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_maker.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(token_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -863,7 +897,8 @@ async def test_nft_offer_nft_for_nft( trade_take, tx_records = await trade_manager_taker.respond_to_offer( Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=taker_fee ) - + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) assert trade_take is not None assert tx_records is not None diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index 390308157315..c3773aa47368 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -23,6 +23,7 @@ from chia.util.ints import uint16, uint32, uint64 from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.nft_wallet.nft_wallet import NFTWallet +from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.address_type import AddressType from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG @@ -139,9 +140,13 @@ async def test_nft_wallet_creation_automatically(self_hostname: str, two_wallet_ ] ) - sb = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_0.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) @@ -234,11 +239,14 @@ async def test_nft_wallet_creation_and_transfer(self_hostname: str, two_wallet_n await time_out_assert(30, wallet_0.get_unconfirmed_balance, 2000000000000) await time_out_assert(30, wallet_0.get_confirmed_balance, 2000000000000) - sb = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_0.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) @@ -268,11 +276,14 @@ async def test_nft_wallet_creation_and_transfer(self_hostname: str, two_wallet_n await time_out_assert(10, wallet_0.get_unconfirmed_balance, 4000000000000 - 1) await time_out_assert(10, wallet_0.get_confirmed_balance, 4000000000000) - sb = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb - # ensure hints are generated - assert compute_memos(sb) - await time_out_assert_not_none(10, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + txs = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await nft_wallet_0.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await time_out_assert(30, wallet_node_0.wallet_state_manager.lock.locked, False) for i in range(1, num_blocks * 2): @@ -1001,10 +1012,13 @@ async def test_nft_transfer_nft_with_did(self_hostname: str, two_wallet_nodes: A await full_node_api.wait_for_wallet_synced(wallet_node_0, 20) await full_node_api.wait_for_wallet_synced(wallet_node_1, 20) # transfer DID to the other wallet - tx = await did_wallet.transfer_did(ph1, uint64(0), True, DEFAULT_TX_CONFIG) - assert tx - assert tx.spend_bundle is not None - await time_out_assert_not_none(30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name()) + txs = await did_wallet.transfer_did(ph1, uint64(0), True, DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) await full_node_api.wait_for_wallet_synced(wallet_node_0, 20) @@ -1050,6 +1064,9 @@ async def test_nft_transfer_nft_with_did(self_hostname: str, two_wallet_nodes: A resp = await api_1.nft_set_nft_did( dict(wallet_id=nft_wallet_id_1, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex(), fee=fee) ) + txs = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) await make_new_block_with(resp, full_node_api, ph) coins_response = await wait_rpc_state_condition( @@ -1619,6 +1636,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: Any, trusted: A resp = await api_0.nft_set_nft_did( dict(wallet_id=nft_wallet_0_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) ) + txs = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + for tx in txs: + await did_wallet1.wallet_state_manager.add_pending_transaction(tx) await make_new_block_with(resp, full_node_api, ph) coins_response = await wait_rpc_state_condition( 30, api_0.nft_get_by_did, [dict(did_id=hmr_did_id)], lambda x: x.get("wallet_id", 0) > 0 @@ -1649,7 +1669,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: Any, trusted: A resp = await api_0.nft_set_nft_did( dict(wallet_id=nft_wallet_1_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) ) - + txs = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + for tx in txs: + await did_wallet1.wallet_state_manager.add_pending_transaction(tx) await make_new_block_with(resp, full_node_api, ph) coins_response = await wait_rpc_state_condition( 30, api_0.nft_get_by_did, [dict(did_id=hmr_did_id)], lambda x: x.get("wallet_id") is not None @@ -1673,6 +1695,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: Any, trusted: A assert coins[0] == resp["nft_info"] # Test set DID2 -> None resp = await api_0.nft_set_nft_did(dict(wallet_id=nft_wallet_2_id, nft_coin_id=nft_coin_id.hex())) + txs = [TransactionRecord.from_json_dict_convenience(tx) for tx in resp["transactions"]] + for tx in txs: + await did_wallet1.wallet_state_manager.add_pending_transaction(tx) await make_new_block_with(resp, full_node_api, ph) # Check NFT DID diff --git a/tests/wallet/rpc/test_wallet_rpc.py b/tests/wallet/rpc/test_wallet_rpc.py index 8e2f1e055527..dcdea34320c6 100644 --- a/tests/wallet/rpc/test_wallet_rpc.py +++ b/tests/wallet/rpc/test_wallet_rpc.py @@ -355,6 +355,10 @@ async def test_push_transactions(wallet_rpc_environment: WalletRpcTestEnvironmen ) await client.push_transactions([tx]) + resp = await client.fetch("push_transactions", {"transactions": [tx.to_json_dict_convenience(wallet_node.config)]}) + assert resp["success"] + resp = await client.fetch("push_transactions", {"transactions": [tx.to_json_dict()]}) + assert resp["success"] spend_bundle = tx.spend_bundle assert spend_bundle is not None @@ -523,6 +527,7 @@ async def test_create_signed_transaction( tx_config=DEFAULT_TX_CONFIG.override( excluded_coin_amounts=[uint64(selected_coin[0].amount)] if selected_coin is not None else [], ), + push=True, ) change_expected = not selected_coin or selected_coin[0].amount - amount_total > 0 assert_tx_amounts(tx, outputs, amount_fee=amount_fee, change_expected=change_expected, is_cat=is_cat) @@ -530,8 +535,6 @@ async def test_create_signed_transaction( # Farm the transaction and make sure the wallet balance reflects it correct spend_bundle = tx.spend_bundle assert spend_bundle is not None - push_res = await wallet_1_rpc.push_transactions([tx]) - assert push_res["success"] await farm_transaction(full_node_api, wallet_1_node, spend_bundle) await time_out_assert(20, get_confirmed_balance, generated_funds - amount_total, wallet_1_rpc, wallet_id) @@ -758,12 +761,12 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron resp = await wallet_2_rpc.spend_clawback_coins([clawback_coin_id_1, clawback_coin_id_2], 100) assert resp["success"] assert len(resp["transaction_ids"]) == 2 - await time_out_assert_not_none( - 10, full_node_api.full_node.mempool_manager.get_spendbundle, bytes32.from_hexstr(resp["transaction_ids"][0]) - ) - await time_out_assert_not_none( - 10, full_node_api.full_node.mempool_manager.get_spendbundle, bytes32.from_hexstr(resp["transaction_ids"][1]) - ) + for _tx in resp["transactions"]: + tx = TransactionRecord.from_json_dict_convenience(_tx) + if tx.spend_bundle is not None: + await time_out_assert_not_none( + 10, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) await farm_transaction_block(full_node_api, wallet_2_node) await time_out_assert(20, get_confirmed_balance, generated_funds + 300, wallet_2_rpc, 1) # Test spent coin @@ -1471,10 +1474,9 @@ async def num_wallets() -> int: assert metadata["Twitter"] == "Https://test" last_did_coin = await did_wallet_2.get_coin() - bundle = SpendBundle.from_json_dict( - (await wallet_2_rpc.did_message_spend(did_wallet_2.id(), [], [], DEFAULT_TX_CONFIG))["spend_bundle"] + SpendBundle.from_json_dict( + (await wallet_2_rpc.did_message_spend(did_wallet_2.id(), [], [], DEFAULT_TX_CONFIG, push=True))["spend_bundle"] ) - await env.full_node.rpc_client.push_tx(bundle) await wallet_2_node.wallet_state_manager.add_interested_coin_ids([last_did_coin.name()]) await time_out_assert(5, check_mempool_spend_count, True, full_node_api, 1) @@ -1484,14 +1486,13 @@ async def num_wallets() -> int: assert next_did_coin.parent_coin_info == last_did_coin.name() last_did_coin = next_did_coin - bundle = SpendBundle.from_json_dict( + SpendBundle.from_json_dict( ( await wallet_2_rpc.did_message_spend( - did_wallet_2.id(), [], [], DEFAULT_TX_CONFIG.override(reuse_puzhash=True) + did_wallet_2.id(), [], [], DEFAULT_TX_CONFIG.override(reuse_puzhash=True), push=True ) - )["spend_bundle"] + )["spend_bundle"], ) - await env.full_node.rpc_client.push_tx(bundle) await wallet_2_node.wallet_state_manager.add_interested_coin_ids([last_did_coin.name()]) await time_out_assert(5, check_mempool_spend_count, True, full_node_api, 1) diff --git a/tests/wallet/sync/test_wallet_sync.py b/tests/wallet/sync/test_wallet_sync.py index 2342f2793d91..fc343a2707e4 100644 --- a/tests/wallet/sync/test_wallet_sync.py +++ b/tests/wallet/sync/test_wallet_sync.py @@ -1125,14 +1125,16 @@ async def test_dusted_wallet( ("h", "0xD4584AD463139FA8C0D9F68F4B59F185"), ] ) - farm_sb = await farm_nft_wallet.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert farm_sb - - # ensure hints are generated - assert compute_memos(farm_sb) + txs = await farm_nft_wallet.generate_new_nft(metadata, DEFAULT_TX_CONFIG) + for tx in txs: + await farm_nft_wallet.wallet_state_manager.add_pending_transaction(tx) + if tx.spend_bundle is not None: + assert compute_memos(tx.spend_bundle) + await time_out_assert_not_none( + 20, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) # Farm a new block - await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, farm_sb.name()) await full_node_api.wait_for_wallets_synced(wallet_nodes=[farm_wallet_node, dust_wallet_node], timeout=20) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(farm_ph)) await full_node_api.wait_for_wallets_synced(wallet_nodes=[farm_wallet_node, dust_wallet_node], timeout=20) diff --git a/tests/wallet/vc_wallet/test_vc_wallet.py b/tests/wallet/vc_wallet/test_vc_wallet.py index b8d9e3323970..8a79aff98092 100644 --- a/tests/wallet/vc_wallet/test_vc_wallet.py +++ b/tests/wallet/vc_wallet/test_vc_wallet.py @@ -451,7 +451,9 @@ async def test_self_revoke( new_vc_record.vc.launcher_id, DEFAULT_TX_CONFIG, new_proof_hash=bytes32([0] * 32), self_revoke=True ) - await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, DEFAULT_TX_CONFIG) + txs = await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(did_wallet.id()) spend_bundle = spend_bundle_list[0].spend_bundle await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())