diff --git a/chia/pools/pool_wallet.py b/chia/pools/pool_wallet.py index 6fa700d5195b..f917de57a15c 100644 --- a/chia/pools/pool_wallet.py +++ b/chia/pools/pool_wallet.py @@ -463,7 +463,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 ) @@ -528,17 +527,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]]: @@ -621,6 +609,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), @@ -642,7 +631,6 @@ async def generate_travel_transactions( valid_times=ConditionValidTimes(), ) - await self.publish_transactions(tx_record, fee_tx) return tx_record, fee_tx @staticmethod @@ -925,7 +913,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: @@ -970,7 +957,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 24e5bd499e1f..c14e98ddc22e 100644 --- a/chia/rpc/util.py +++ b/chia/rpc/util.py @@ -104,6 +104,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 dc769372b205..599987bbfc0e 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -591,9 +591,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: @@ -672,6 +679,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 @@ -685,6 +693,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, @@ -798,7 +808,7 @@ async def create_new_wallet( else: raise ValueError("DAO rules must be specified for wallet creation") async with self.service.wallet_state_manager.lock: - dao_wallet = await DAOWallet.create_new_dao_and_wallet( + dao_wallet, txs = await DAOWallet.create_new_dao_and_wallet( wallet_state_manager, main_wallet, uint64(request.get("amount_of_cats", None)), @@ -809,6 +819,9 @@ async def create_new_wallet( uint64(request.get("fee", 0)), uint64(request.get("fee_for_cat", 0)), ) + if push: + for tx in txs: + await self.service.wallet_state_manager.add_pending_transaction(tx) elif mode == "existing": # async with self.service.wallet_state_manager.lock: dao_wallet = await DAOWallet.create_new_dao_wallet_for_existing_dao( @@ -901,11 +914,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(), } @@ -1096,6 +1112,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") @@ -1129,11 +1146,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, } @@ -1147,16 +1167,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( @@ -1164,6 +1188,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). @@ -1188,30 +1213,42 @@ 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 + ) + tx_list.extend(new_txs) + tx_config = dataclasses.replace( + tx_config, + excluded_coin_ids=[ + *tx_config.excluded_coin_ids, + *(c.name() for tx in new_txs for c in tx.removals), + ], ) 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( + tx_list.extend( 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 tx_list: + await self.service.wallet_state_manager.add_pending_transaction(tx) + 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: @@ -1451,6 +1488,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"]), @@ -1460,8 +1498,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: """ @@ -1646,6 +1687,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.") @@ -1710,8 +1752,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, @@ -1723,13 +1766,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, } @@ -1755,7 +1800,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) @@ -1921,6 +1970,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"] @@ -1962,7 +2012,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 @@ -2021,16 +2078,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( @@ -2038,6 +2100,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)) @@ -2048,6 +2111,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 @@ -2077,8 +2142,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 @@ -2086,7 +2153,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 @@ -2110,11 +2182,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: @@ -2125,12 +2197,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( @@ -2138,21 +2216,26 @@ 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) - spend_bundle = ( - await wallet.create_message_spend( - tx_config, - extra_conditions=( - *extra_conditions, - *(CreateCoinAnnouncement(hexstr_to_bytes(ca)) for ca in request.get("coin_announcements", [])), - *(CreatePuzzleAnnouncement(hexstr_to_bytes(pa)) for pa in request.get("puzzle_announcements", [])), - ), - ) - ).spend_bundle - return {"success": True, "spend_bundle": spend_bundle} + tx = await wallet.create_message_spend( + tx_config, + extra_conditions=( + *extra_conditions, + *(CreateCoinAnnouncement(hexstr_to_bytes(ca)) for ca in request.get("coin_announcements", [])), + *(CreatePuzzleAnnouncement(hexstr_to_bytes(pa)) for pa in request.get("puzzle_announcements", [])), + ), + ) + 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: @@ -2399,6 +2482,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) @@ -2409,13 +2493,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}"} @@ -2454,7 +2545,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: @@ -2479,15 +2572,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} @@ -2497,32 +2598,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} @@ -2575,6 +2680,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.") @@ -2582,18 +2688,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, } ########################################################################################## @@ -2634,7 +2744,12 @@ async def dao_add_funds_to_treasury( ) if push: await self.service.wallet_state_manager.add_pending_transaction(funding_tx) - return {"success": True, "tx_id": funding_tx.name, "tx": funding_tx} + return { + "success": True, + "tx_id": funding_tx.name, + "tx": funding_tx, + "transactions": [funding_tx.to_json_dict_convenience(self.service.config)], + } async def dao_get_treasury_balance(self, request: Dict[str, Any]) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) @@ -2692,6 +2807,7 @@ async def dao_send_to_lockup( "success": True, "tx_id": txs[0].name, "txs": txs, + "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], } async def dao_get_proposals(self, request: Dict[str, Any]) -> EndpointResult: @@ -2754,7 +2870,12 @@ async def dao_exit_lockup( ) if push: await self.service.wallet_state_manager.add_pending_transaction(exit_tx) - return {"success": True, "tx_id": exit_tx.name, "tx": exit_tx} + return { + "success": True, + "tx_id": exit_tx.name, + "tx": exit_tx, + "transactions": [exit_tx.to_json_dict_convenience(self.service.config)], + } @tx_endpoint async def dao_create_proposal( @@ -2849,6 +2970,7 @@ async def dao_create_proposal( "proposal_id": proposal_id, "tx_id": proposal_tx.name.hex(), "tx": proposal_tx, + "transactions": [proposal_tx.to_json_dict_convenience(self.service.config)], } @tx_endpoint @@ -2877,7 +2999,12 @@ async def dao_vote_on_proposal( assert vote_tx is not None if push: await self.service.wallet_state_manager.add_pending_transaction(vote_tx) - return {"success": True, "tx_id": vote_tx.name, "tx": vote_tx} + return { + "success": True, + "tx_id": vote_tx.name, + "tx": vote_tx, + "transactions": [vote_tx.to_json_dict_convenience(self.service.config)], + } async def dao_parse_proposal(self, request: Dict[str, Any]) -> EndpointResult: wallet_id = uint32(request["wallet_id"]) @@ -2914,8 +3041,14 @@ async def dao_close_proposal( extra_conditions=extra_conditions, ) assert tx is not None - await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"success": True, "tx_id": tx.name, "tx": tx} + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) + return { + "success": True, + "tx_id": tx.name, + "tx": tx, + "transactions": [tx.to_json_dict_convenience(self.service.config)], + } @tx_endpoint async def dao_free_coins_from_finished_proposals( @@ -2935,9 +3068,15 @@ async def dao_free_coins_from_finished_proposals( extra_conditions=extra_conditions, ) assert tx is not None - await self.service.wallet_state_manager.add_pending_transaction(tx) + if push: + await self.service.wallet_state_manager.add_pending_transaction(tx) - return {"success": True, "tx_id": tx.name, "tx": tx} + return { + "success": True, + "tx_id": tx.name, + "tx": tx, + "transactions": [tx.to_json_dict_convenience(self.service.config)], + } ########################################################################################## # NFT Wallet @@ -2948,6 +3087,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"]) @@ -2999,7 +3139,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, @@ -3010,11 +3150,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) @@ -3061,6 +3210,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) @@ -3074,14 +3224,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( @@ -3089,6 +3244,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. @@ -3163,8 +3319,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(): @@ -3174,6 +3331,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") @@ -3184,6 +3342,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. @@ -3254,8 +3413,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(): @@ -3265,6 +3425,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") @@ -3330,6 +3491,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"] @@ -3363,9 +3525,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)} @@ -3442,6 +3611,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. @@ -3457,10 +3627,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( @@ -3477,7 +3655,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"]) @@ -3690,6 +3871,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: @@ -3764,8 +3946,10 @@ async def _generate_signed_transaction() -> EndpointResult: ), ) 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) @@ -3800,8 +3984,11 @@ async def _generate_signed_transaction() -> EndpointResult: ), ) 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: @@ -3818,6 +4005,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"]) @@ -3843,7 +4031,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( @@ -3851,6 +4052,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 @@ -3864,7 +4066,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( @@ -3872,6 +4087,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: @@ -3885,7 +4101,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"]`""" @@ -3909,6 +4136,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: @@ -3928,8 +4156,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)} @@ -4004,6 +4233,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: @@ -4018,9 +4248,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( @@ -4028,6 +4262,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: @@ -4054,9 +4289,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""" @@ -4106,6 +4345,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: @@ -4121,8 +4361,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], @@ -4134,6 +4375,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: @@ -4149,8 +4391,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], @@ -4165,6 +4408,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 @@ -4192,8 +4436,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], @@ -4252,6 +4497,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 @@ -4283,8 +4529,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], @@ -4329,6 +4576,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 @@ -4352,8 +4600,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], @@ -4365,6 +4614,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 @@ -4391,8 +4641,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 c7093888cce4..5f31f3610538 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -209,6 +209,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: request = { "wallet_id": wallet_id, @@ -217,6 +218,7 @@ async def send_transaction( "fee": fee, "puzzle_decorator": puzzle_decorator_override, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -232,6 +234,7 @@ async def send_transaction_multi( tx_config: TXConfig, coins: Optional[List[Coin]] = None, fee: uint64 = uint64(0), + push: bool = True, ) -> TransactionRecord: # Converts bytes to hex for puzzle hashes additions_hex = [] @@ -239,7 +242,13 @@ async def send_transaction_multi( additions_hex.append({"amount": ad["amount"], "puzzle_hash": ad["puzzle_hash"].hex()}) if "memos" in ad: additions_hex[-1]["memos"] = ad["memos"] - request = {"wallet_id": wallet_id, "additions": additions_hex, "fee": fee, **tx_config.to_json_dict()} + request = { + "wallet_id": wallet_id, + "additions": additions_hex, + "fee": fee, + "push": push, + **tx_config.to_json_dict(), + } if coins is not None and len(coins) > 0: coins_json = [c.to_json_dict() for c in coins] request["coins"] = coins_json @@ -289,6 +298,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 = [] @@ -301,6 +311,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(), } @@ -322,6 +333,7 @@ async def create_signed_transaction( coins: Optional[List[Coin]] = None, fee: uint64 = uint64(0), wallet_id: Optional[int] = None, + push: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), ) -> TransactionRecord: @@ -331,6 +343,7 @@ async def create_signed_transaction( coins=coins, fee=fee, wallet_id=wallet_id, + push=push, extra_conditions=extra_conditions, timelock_info=timelock_info, ) @@ -421,12 +434,14 @@ async def update_did_recovery_list( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "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(), } @@ -444,10 +459,12 @@ async def did_message_spend( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = False, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -461,11 +478,13 @@ async def update_did_metadata( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "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(), } @@ -535,6 +554,7 @@ async def did_transfer_did( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -542,6 +562,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(), } @@ -685,12 +706,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 = { "wallet_id": wallet_id, "fee": fee, "memos": memos if memos is not None else [], "extra_conditions": conditions_to_json_dicts(extra_conditions), + "push": push, **tx_config.to_json_dict(), **timelock_info.to_json_dict(), } @@ -764,11 +787,13 @@ async def take_offer( fee: int = 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(), } @@ -825,6 +850,7 @@ async def cancel_offer( secure: bool = True, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> None: await self.fetch( "cancel_offer", @@ -833,6 +859,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(), }, @@ -848,6 +875,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", @@ -859,6 +887,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(), }, @@ -889,6 +918,7 @@ async def mint_nft( did_id: Optional[str] = None, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -906,6 +936,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(), } @@ -922,6 +953,7 @@ async def add_uri_to_nft( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -930,6 +962,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(), } @@ -966,6 +999,7 @@ async def transfer_nft( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -973,6 +1007,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(), } @@ -998,6 +1033,7 @@ async def set_nft_did( tx_config: TXConfig, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = True, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -1005,6 +1041,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(), } @@ -1035,6 +1072,7 @@ async def nft_mint_bulk( fee: Optional[int] = 0, extra_conditions: Tuple[Condition, ...] = tuple(), timelock_info: ConditionValidTimes = ConditionValidTimes(), + push: bool = False, ) -> Dict[str, Any]: request = { "wallet_id": wallet_id, @@ -1052,6 +1090,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(), } @@ -1459,6 +1498,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", @@ -1467,6 +1507,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(), }, @@ -1493,6 +1534,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", @@ -1505,6 +1547,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(), }, @@ -1525,6 +1568,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", @@ -1532,6 +1576,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(), }, @@ -1544,6 +1589,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", @@ -1551,6 +1597,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/dao_wallet/dao_wallet.py b/chia/wallet/dao_wallet/dao_wallet.py index f535a84a0fe8..707b2f3605fe 100644 --- a/chia/wallet/dao_wallet/dao_wallet.py +++ b/chia/wallet/dao_wallet/dao_wallet.py @@ -125,7 +125,7 @@ async def create_new_dao_and_wallet( name: Optional[str] = None, fee: uint64 = uint64(0), fee_for_cat: uint64 = uint64(0), - ) -> DAOWallet: + ) -> Tuple[DAOWallet, List[TransactionRecord]]: """ Create a brand new DAO wallet This must be called under the wallet state manager lock @@ -174,7 +174,7 @@ async def create_new_dao_and_wallet( std_wallet_id = self.standard_wallet.wallet_id try: - await self.generate_new_dao( + txs = await self.generate_new_dao( amount_of_cats, tx_config, fee=fee, @@ -199,7 +199,7 @@ async def create_new_dao_and_wallet( ) await self.save_info(dao_info) - return self + return self, txs @staticmethod async def create_new_dao_wallet_for_existing_dao( @@ -612,7 +612,7 @@ async def generate_new_dao( fee: uint64 = uint64(0), fee_for_cat: uint64 = uint64(0), extra_conditions: Tuple[Condition, ...] = tuple(), - ) -> Optional[SpendBundle]: + ) -> List[TransactionRecord]: """ Create a new DAO treasury using the dao_rules object. This does the first spend to create the launcher and eve coins. @@ -755,8 +755,7 @@ async def generate_new_dao( ) await self.add_parent(launcher_coin.name(), launcher_proof) - if tx_record is None or tx_record.spend_bundle is None: # pragma: no cover - return None + assert tx_record.spend_bundle is not None eve_coin = Coin(launcher_coin.name(), full_treasury_puzzle_hash, uint64(1)) dao_info = DAOInfo( @@ -797,8 +796,6 @@ async def generate_new_dao( valid_times=parse_timelock_info(extra_conditions), ) 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(treasury_record) funding_inner_puzhash = get_p2_singleton_puzhash(self.dao_info.treasury_id) await self.wallet_state_manager.add_interested_puzzle_hashes([funding_inner_puzhash], [self.id()]) @@ -806,7 +803,7 @@ async def generate_new_dao( await self.wallet_state_manager.add_interested_coin_ids([launcher_coin.name()], [self.wallet_id]) await self.wallet_state_manager.add_interested_coin_ids([eve_coin.name()], [self.wallet_id]) - return full_spend + return [treasury_record, regular_record] async def generate_treasury_eve_spend( self, inner_puz: Program, eve_coin: Coin, fee: uint64 = uint64(0) diff --git a/chia/wallet/did_wallet/did_wallet.py b/chia/wallet/did_wallet/did_wallet.py index c5caa5c19f44..24bc1a825402 100644 --- a/chia/wallet/did_wallet/did_wallet.py +++ b/chia/wallet/did_wallet/did_wallet.py @@ -139,14 +139,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 @@ -566,7 +566,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() @@ -641,7 +641,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())), @@ -661,9 +660,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, @@ -672,7 +674,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 @@ -739,7 +741,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())), @@ -759,8 +760,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( @@ -836,7 +839,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() @@ -887,8 +890,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 @@ -899,7 +901,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 TODO: @@ -974,8 +976,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 @@ -1027,7 +1028,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 @@ -1098,7 +1099,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( origin_coin=self.did_info.origin_coin, backup_ids=self.did_info.backup_ids, @@ -1112,7 +1112,7 @@ async def recovery_spend( metadata=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() @@ -1244,14 +1244,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 @@ -1277,9 +1275,6 @@ async def generate_new_decentralised_id( ), ) - if tx_record.spend_bundle is None: - return None - genesis_launcher_solution = Program.to([did_puzzle_hash, amount, bytes(0x80)]) launcher_cs = make_spend(launcher_coin, genesis_launcher_puz, genesis_launcher_solution) @@ -1313,6 +1308,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 @@ -1337,9 +1333,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 e2de097f7db7..05951c09720c 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -337,9 +337,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 """ @@ -412,8 +411,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] @@ -443,10 +441,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: @@ -494,7 +489,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() @@ -513,11 +508,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) @@ -1202,7 +1195,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 @@ -1225,15 +1218,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 05e37ab4104e..52a20acb1314 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -239,7 +239,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] = [] @@ -345,8 +345,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 @@ -837,9 +837,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 7992ca92d349..47857aa8d2a6 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 @@ -883,6 +884,7 @@ async def auto_claim_coins(self) -> None: stop=tx_config.coin_selection_config.max_coin_amount, ), ) + all_txs: List[TransactionRecord] = [] for coin in unspent_coins.records: try: metadata: MetadataTypes = coin.parsed_metadata() @@ -892,12 +894,23 @@ 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) + all_txs.extend(txs) + tx_config = dataclasses.replace( + tx_config, + excluded_coin_ids=[ + *tx_config.excluded_coin_ids, + *(c.name() for tx in txs for c in tx.removals), + ], + ) 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) + all_txs.extend(await self.spend_clawback_coins(clawback_coins, tx_fee, tx_config)) + + for tx in all_txs: + await self.add_pending_transaction(tx) async def spend_clawback_coins( self, @@ -906,7 +919,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()])) @@ -958,6 +971,7 @@ 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, @@ -968,6 +982,7 @@ async def spend_clawback_coins( ) 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), @@ -988,8 +1003,8 @@ 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) - return [tx_record.name] + tx_list.append(tx_record) + 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 f63db760a68f..62bce2554d7f 100644 --- a/tests/wallet/cat_wallet/test_trades.py +++ b/tests/wallet/cat_wallet/test_trades.py @@ -465,6 +465,8 @@ async def test_cat_trades( wallet_environments.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 @@ -667,6 +669,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): wallet_environments.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 @@ -788,6 +792,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, wallet_environments.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 @@ -976,6 +982,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, wallet_environments.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 @@ -1215,6 +1223,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, wallet_environments.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 @@ -1340,6 +1350,8 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): peer, wallet_environments.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 @@ -1577,6 +1589,8 @@ async def test_trade_cancellation(wallets_prefarm): # 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 @@ -1593,6 +1607,8 @@ async def test_trade_cancellation(wallets_prefarm): 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) @@ -1633,6 +1649,8 @@ async def test_trade_cancellation(wallets_prefarm): 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) @@ -1684,6 +1702,8 @@ async def test_trade_cancellation_balance_check(wallets_prefarm): 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) @@ -1738,12 +1758,16 @@ async def test_trade_conflict(three_wallets_prefarm): 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) @@ -1797,6 +1821,8 @@ async def test_trade_bad_spend(wallets_prefarm): 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 def check_wallet_cache_empty() -> bool: @@ -1855,6 +1881,8 @@ async def test_trade_high_fee(wallets_prefarm): 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(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) @@ -1922,6 +1950,8 @@ async def test_aggregated_trade_state(wallets_prefarm): assert trade_take is not None assert tx_records is not None + for tx in tx_records: + await trade_manager_taker.wallet_state_manager.add_pending_transaction(tx) await full_node.process_transaction_records(records=tx_records) await full_node.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=60) diff --git a/tests/wallet/dao_wallet/test_dao_wallets.py b/tests/wallet/dao_wallet/test_dao_wallets.py index 61ee6aba0d7b..ba8eab903453 100644 --- a/tests/wallet/dao_wallet/test_dao_wallets.py +++ b/tests/wallet/dao_wallet/test_dao_wallets.py @@ -131,7 +131,7 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA # Try to create a DAO with more CATs than xch balance with pytest.raises(ValueError) as e_info: - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(funds + 1), @@ -142,7 +142,7 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA ) assert e_info.value.args[0] == f"Your balance of {funds} mojos is not enough to create {funds + 1} CATs" - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt * 2), @@ -153,8 +153,9 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA ) assert dao_wallet_0 is not None - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -314,7 +315,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators proposal_minimum_amount=uint64(1), ) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), @@ -326,8 +327,9 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators treasury_id = dao_wallet_0.dao_info.treasury_id # Get the full node sim to process the wallet creation spend - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -501,7 +503,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Create the DAO. # This takes two steps: create the treasury singleton, wait for oracle_spend_delay and # then complete the eve spend - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), @@ -510,8 +512,9 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato ) assert dao_wallet_0 is not None - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -944,7 +947,7 @@ async def test_dao_proposal_partial_vote( proposal_minimum_amount=uint64(1), ) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), @@ -954,8 +957,9 @@ async def test_dao_proposal_partial_vote( assert dao_wallet_0 is not None # Get the full node sim to process the wallet creation spend - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2492,7 +2496,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula proposal_minimum_amount=uint64(101), ) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), @@ -2502,8 +2506,9 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula assert dao_wallet_0 is not None # Get the full node sim to process the wallet creation spend - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -2899,7 +2904,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd proposal_minimum_amount=uint64(101), ) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt), @@ -2909,8 +2914,9 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd assert dao_wallet_0 is not None # Get the full node sim to process the wallet creation spend - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3163,7 +3169,7 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn proposal_minimum_amount=proposal_min_amt, ) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), @@ -3172,8 +3178,9 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn ) assert dao_wallet_0 is not None - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_node_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) @@ -3380,7 +3387,7 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd fee = uint64(10) fee_for_cat = uint64(20) - dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + dao_wallet_0, tx_queue = await DAOWallet.create_new_dao_and_wallet( wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_amt * 2), @@ -3391,8 +3398,9 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd ) assert dao_wallet_0 is not None - txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() - await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + for tx in tx_queue: + await wallet_0.wallet_state_manager.add_pending_transaction(tx) + await full_node_api.wait_transaction_records_entered_mempool(records=tx_queue, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) diff --git a/tests/wallet/db_wallet/test_dl_offers.py b/tests/wallet/db_wallet/test_dl_offers.py index 8764f752ee16..326f38bb6f06 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 996b3a3d55d8..486c3aa2c86b 100644 --- a/tests/wallet/did_wallet/test_did.py +++ b/tests/wallet/did_wallet/test_did.py @@ -23,7 +23,6 @@ from chia.wallet.util.address_type import AddressType from chia.wallet.util.tx_config import DEFAULT_COIN_SELECTION_CONFIG, DEFAULT_TX_CONFIG from chia.wallet.util.wallet_types import WalletType -from chia.wallet.wallet_state_manager import WalletStateManager from tests.environments.wallet import WalletStateTransition, WalletTestFramework from tests.util.setup_nodes import OldSimulatorsAndWallets from tests.util.time_out_assert import time_out_assert, time_out_assert_not_none @@ -207,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() ) @@ -225,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) @@ -244,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() @@ -366,16 +373,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() ) @@ -393,7 +404,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() ) @@ -457,10 +469,8 @@ async def test_did_recovery_with_empty_set(self, self_hostname, two_wallet_nodes coin = await did_wallet.get_coin() info = Program.to([]) pubkey = (await did_wallet.wallet_state_manager.get_unused_derivation_record(did_wallet.wallet_info.id)).pubkey - with pytest.raises(Exception): - spend_bundle = await did_wallet.recovery_spend( - coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([])) - ) + with pytest.raises(Exception): # We expect a CLVM 80 error for this test + await did_wallet.recovery_spend(coin, ph, info, pubkey, SpendBundle([], AugSchemeMPL.aggregate([]))) @pytest.mark.parametrize( "trusted", @@ -525,7 +535,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()) @@ -605,7 +617,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()) @@ -632,7 +646,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 @@ -643,7 +661,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() ) @@ -670,7 +689,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() ) @@ -683,7 +706,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() @@ -758,7 +782,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() ) @@ -834,7 +860,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) @@ -1030,7 +1058,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() ) @@ -1301,7 +1331,9 @@ async def test_did_resync(self, self_hostname, two_wallet_nodes, trusted) -> Non 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, True, tx_config=DEFAULT_TX_CONFIG) + txs = await did_wallet_1.transfer_did(new_puzhash, fee, True, tx_config=DEFAULT_TX_CONFIG) + for tx in txs: + await did_wallet_1.wallet_state_manager.add_pending_transaction(tx) spend_bundle_list = await wallet_node_1.wallet_state_manager.tx_store.get_unconfirmed_for_wallet( did_wallet_1.id() ) @@ -1378,30 +1410,24 @@ async def test_did_coin_records(wallet_environments: WalletTestFramework, monkey ] ) - # When transfer_did doesn't push the transactions automatically, this monkeypatching is no longer necessary - with monkeypatch.context() as m: - - async def nothing(*args) -> None: - pass - - m.setattr(WalletStateManager, "add_pending_transaction", nothing) - for _ in range(0, 2): - tx = await did_wallet.transfer_did( - await wallet.get_puzzle_hash(new=False), uint64(0), True, wallet_environments.tx_config - ) - assert tx.spend_bundle is not None - await client.push_tx(tx.spend_bundle) - await wallet_environments.process_pending_states( - [ - WalletStateTransition( - pre_block_balance_updates={}, - post_block_balance_updates={ - 1: {"set_remainder": True}, - 2: {"set_remainder": True}, - }, - ), - WalletStateTransition(), - ] - ) + for _ in range(0, 2): + txs = await did_wallet.transfer_did( + await wallet.get_puzzle_hash(new=False), uint64(0), True, wallet_environments.tx_config + ) + spend_bundles = [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] + assert len(spend_bundles) > 0 + await client.push_tx(SpendBundle.aggregate(spend_bundles)) + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + pre_block_balance_updates={}, + post_block_balance_updates={ + 1: {"set_remainder": True}, + 2: {"set_remainder": True}, + }, + ), + WalletStateTransition(), + ] + ) assert len(await wallet.wallet_state_manager.get_spendable_coins_for_wallet(did_wallet.id())) == 1 diff --git a/tests/wallet/nft_wallet/test_nft_1_offers.py b/tests/wallet/nft_wallet/test_nft_1_offers.py index 84f2e2d3efe5..856635abdc72 100644 --- a/tests/wallet/nft_wallet/test_nft_1_offers.py +++ b/tests/wallet/nft_wallet/test_nft_1_offers.py @@ -127,7 +127,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, @@ -135,10 +135,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) @@ -180,6 +183,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) @@ -263,7 +268,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, @@ -271,10 +276,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) @@ -318,6 +326,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 @@ -398,7 +408,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, @@ -406,10 +416,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) @@ -468,6 +481,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 @@ -554,7 +569,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, @@ -562,10 +577,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) @@ -650,6 +668,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 @@ -733,7 +753,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, @@ -741,10 +761,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) @@ -838,6 +861,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 @@ -924,7 +949,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, @@ -932,10 +957,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) @@ -963,6 +991,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) @@ -1040,7 +1070,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, @@ -1048,10 +1078,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)) @@ -1080,6 +1113,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) @@ -1236,7 +1271,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, @@ -1244,8 +1279,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, @@ -1253,10 +1294,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 @@ -1270,7 +1313,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, @@ -1278,8 +1321,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 @@ -1348,6 +1395,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) @@ -1449,6 +1498,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 990541da3ae9..782ccc72a657 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 @@ -949,9 +984,13 @@ async def test_nft_offer_nft0_and_xch_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) @@ -1047,6 +1086,13 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert trade_take is not None assert tx_records is not None + for tx in tx_records: + 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.process_transaction_records(records=tx_records) await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -1127,6 +1173,13 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert trade_take is not None assert tx_records is not None + for tx in tx_records: + 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.process_transaction_records(records=tx_records) # check balances: taker wallet down an NFT, up cats await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index b27b53e33717..2b5fe229ad83 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -25,6 +25,7 @@ from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.nft_wallet.nft_info import NFTInfo 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 @@ -138,9 +139,14 @@ async def test_nft_wallet_creation_automatically( metadata = Program.to( [("u", ["https://www.chia.net/img/branding/chia-logo.svg"]), ("h", "0xD4584AD463139FA8C0D9F68F4B59F185")] ) - sb = await nft_wallet_0.generate_new_nft(metadata, DEFAULT_TX_CONFIG) - assert sb is not None - 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( + 30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for _ in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) @@ -238,11 +244,15 @@ async def ensure_wallet_sync() -> None: 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 is not None - # ensure hints are generated - assert len(compute_memos(sb)) > 0 - 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: + # ensure hints are generated + assert len(compute_memos(tx.spend_bundle)) > 0 + await time_out_assert_not_none( + 30, full_node_api.full_node.mempool_manager.get_spendbundle, tx.spend_bundle.name() + ) for _ in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) @@ -270,11 +280,15 @@ async def ensure_wallet_sync() -> None: 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 is not None - # ensure hints are generated - assert len(compute_memos(sb)) > 0 - 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: + # ensure hints are generated + assert len(compute_memos(tx.spend_bundle)) > 0 + 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 _ in range(1, num_blocks * 2): @@ -996,10 +1010,13 @@ async def test_nft_transfer_nft_with_did( 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 _ 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) @@ -1045,6 +1062,9 @@ async def test_nft_transfer_nft_with_did( 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( @@ -1613,6 +1633,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: OldSimulatorsAn 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 @@ -1646,7 +1669,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: OldSimulatorsAn 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 @@ -1671,6 +1696,9 @@ async def test_nft_set_did(self_hostname: str, two_wallet_nodes: OldSimulatorsAn 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 cb2502763465..5f08b5716ab1 100644 --- a/tests/wallet/rpc/test_wallet_rpc.py +++ b/tests/wallet/rpc/test_wallet_rpc.py @@ -354,6 +354,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 @@ -522,6 +526,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) @@ -529,8 +534,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) @@ -771,12 +774,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 @@ -1485,10 +1488,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) @@ -1498,12 +1500,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( - (await wallet_2_rpc.did_message_spend(did_wallet_2.id(), DEFAULT_TX_CONFIG.override(reuse_puzhash=True)))[ - "spend_bundle" - ] + SpendBundle.from_json_dict( + ( + await wallet_2_rpc.did_message_spend( + did_wallet_2.id(), DEFAULT_TX_CONFIG.override(reuse_puzhash=True), 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) diff --git a/tests/wallet/sync/test_wallet_sync.py b/tests/wallet/sync/test_wallet_sync.py index 469eb7a1577a..ae53d198aa11 100644 --- a/tests/wallet/sync/test_wallet_sync.py +++ b/tests/wallet/sync/test_wallet_sync.py @@ -1071,14 +1071,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 len(compute_memos(farm_sb)) > 0 + 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 len(compute_memos(tx.spend_bundle)) > 0 + 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 8415b64ec182..af52da2464bd 100644 --- a/tests/wallet/vc_wallet/test_vc_wallet.py +++ b/tests/wallet/vc_wallet/test_vc_wallet.py @@ -670,7 +670,9 @@ async def test_self_revoke(wallet_environments: WalletTestFramework) -> None: ) # Send the DID to oblivion - await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, wallet_environments.tx_config) + txs = await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, wallet_environments.tx_config) + for tx in txs: + await did_wallet.wallet_state_manager.add_pending_transaction(tx) await wallet_environments.process_pending_states( [ WalletStateTransition(