async def generate_signed_transaction( self, amount, to_puzzle_hash, fee: uint64 = uint64(0), memo: Optional[List[bytes]] = None) -> TransactionRecord: self.rl_coin_record = await self._get_rl_coin_record() if not self.rl_coin_record: raise ValueError("No unspent coin (zero balance)") if amount > self.rl_coin_record.coin.amount: raise ValueError( f"Coin value not sufficient: {amount} > {self.rl_coin_record.coin.amount}" ) transaction = await self.rl_generate_unsigned_transaction( to_puzzle_hash, amount, fee) spend_bundle = await self.rl_sign_transaction(transaction) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=to_puzzle_hash, amount=uint64(amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), )
async def aggregate_this_coin(self, coin: Coin): spend_bundle = await self.rl_generate_signed_aggregation_transaction( self.rl_info, coin, await self._get_rl_parent(), await self._get_rl_coin()) rl_coin = await self._get_rl_coin() puzzle_hash = rl_coin.puzzle_hash if rl_coin is not None else None assert puzzle_hash is not None tx_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzzle_hash, amount=uint64(0), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), ) asyncio.create_task(self.push_transaction(tx_record))
async def clawback_rl_coin_transaction(self, fee) -> TransactionRecord: to_puzzle_hash = await self.main_wallet.get_new_puzzlehash() spend_bundle = await self.clawback_rl_coin(to_puzzle_hash, fee) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=to_puzzle_hash, amount=uint64(0), fee_amount=fee, confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), )
async def test_wallet_prevent_fee_theft(self, two_wallet_nodes, trusted): num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_1 = full_nodes[0] wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() if trusted: wallet_node.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } wallet_node_2.config["trusted_peers"] = { full_node_1.full_node.server.node_id.hex(): full_node_1.full_node.server.node_id.hex() } else: wallet_node.config["trusted_peers"] = {} wallet_node_2.config["trusted_peers"] = {} await server_2.start_client( PeerInfo(self_hostname, uint16(full_node_1.full_node.server._port)), None) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(ph)) funds = sum([ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks) ]) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds) assert await wallet.get_confirmed_balance() == funds assert await wallet.get_unconfirmed_balance() == funds tx_amount = 3200000000000 tx_fee = 300000000000 tx = await wallet.generate_signed_transaction( tx_amount, await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash( ), tx_fee, ) # extract coin_spend from generated spend_bundle for cs in tx.spend_bundle.coin_spends: if cs.additions() == []: stolen_cs = cs # get a legit signature stolen_sb = await wallet.sign_transaction([stolen_cs]) now = uint64(int(time.time())) add_list = list(stolen_sb.additions()) rem_list = list(stolen_sb.removals()) name = stolen_sb.name() stolen_tx = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=32 * b"0", amount=0, fee_amount=stolen_cs.coin.amount, confirmed=False, sent=uint32(0), spend_bundle=stolen_sb, additions=add_list, removals=rem_list, wallet_id=wallet.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=name, memos=list(compute_memos(stolen_sb).items()), ) await wallet.push_transaction(stolen_tx) await time_out_assert(5, wallet.get_confirmed_balance, funds) await time_out_assert(5, wallet.get_unconfirmed_balance, funds - stolen_cs.coin.amount) for i in range(0, num_blocks): await full_node_1.farm_new_transaction_block( FarmNewBlockProtocol(32 * b"0")) # Funds have not decreased because stolen_tx was rejected outstanding_coinbase_rewards = 2000000000000 await time_out_assert(20, wallet.get_confirmed_balance, funds + outstanding_coinbase_rewards)
async def test_wallet_rpc(self, two_wallet_nodes, trusted): test_rpc_port = uint16(21529) test_rpc_port_2 = uint16(21536) test_rpc_port_node = uint16(21530) num_blocks = 5 full_nodes, wallets = two_wallet_nodes full_node_api = full_nodes[0] full_node_server = full_node_api.full_node.server wallet_node, server_2 = wallets[0] wallet_node_2, server_3 = wallets[1] wallet = wallet_node.wallet_state_manager.main_wallet wallet_2 = wallet_node_2.wallet_state_manager.main_wallet ph = await wallet.get_new_puzzlehash() ph_2 = await wallet_2.get_new_puzzlehash() await server_2.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) await server_3.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) if trusted: wallet_node.config["trusted_peers"] = {full_node_server.node_id.hex(): full_node_server.node_id.hex()} wallet_node_2.config["trusted_peers"] = {full_node_server.node_id.hex(): full_node_server.node_id.hex()} else: wallet_node.config["trusted_peers"] = {} wallet_node_2.config["trusted_peers"] = {} for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) initial_funds = sum( [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks)] ) initial_funds_eventually = sum( [ calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks + 1) ] ) wallet_rpc_api = WalletRpcApi(wallet_node) wallet_rpc_api_2 = WalletRpcApi(wallet_node_2) config = bt.config hostname = config["self_hostname"] daemon_port = config["daemon_port"] def stop_node_cb(): pass full_node_rpc_api = FullNodeRpcApi(full_node_api.full_node) rpc_cleanup_node = await start_rpc_server( full_node_rpc_api, hostname, daemon_port, test_rpc_port_node, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) rpc_cleanup = await start_rpc_server( wallet_rpc_api, hostname, daemon_port, test_rpc_port, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) rpc_cleanup_2 = await start_rpc_server( wallet_rpc_api_2, hostname, daemon_port, test_rpc_port_2, stop_node_cb, bt.root_path, config, connect_to_daemon=False, ) await time_out_assert(5, wallet.get_confirmed_balance, initial_funds) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds) client = await WalletRpcClient.create(self_hostname, test_rpc_port, bt.root_path, config) client_2 = await WalletRpcClient.create(self_hostname, test_rpc_port_2, bt.root_path, config) client_node = await FullNodeRpcClient.create(self_hostname, test_rpc_port_node, bt.root_path, config) try: await time_out_assert(5, client.get_synced) addr = encode_puzzle_hash(await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash(), "xch") tx_amount = 15600000 try: await client.send_transaction("1", 100000000000000001, addr) raise Exception("Should not create high value tx") except ValueError: pass # Tests sending a basic transaction tx = await client.send_transaction("1", tx_amount, addr, memos=["this is a basic tx"]) transaction_id = tx.name async def tx_in_mempool(): tx = await client.get_transaction("1", transaction_id) return tx.is_in_mempool() await time_out_assert(5, tx_in_mempool, True) await time_out_assert(5, wallet.get_unconfirmed_balance, initial_funds - tx_amount) assert (await client.get_wallet_balance("1"))["unconfirmed_wallet_balance"] == initial_funds - tx_amount assert (await client.get_wallet_balance("1"))["confirmed_wallet_balance"] == initial_funds for i in range(0, 5): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_2)) async def eventual_balance(): return (await client.get_wallet_balance("1"))["confirmed_wallet_balance"] async def eventual_balance_det(c, wallet_id: str): return (await c.get_wallet_balance(wallet_id))["confirmed_wallet_balance"] # Checks that the memo can be retrieved tx_confirmed = await client.get_transaction("1", transaction_id) assert tx_confirmed.confirmed assert len(tx_confirmed.get_memos()) == 1 assert [b"this is a basic tx"] in tx_confirmed.get_memos().values() assert list(tx_confirmed.get_memos().keys())[0] in [a.name() for a in tx.spend_bundle.additions()] await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount) # Tests offline signing ph_3 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() ph_4 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() ph_5 = await wallet_node_2.wallet_state_manager.main_wallet.get_new_puzzlehash() # Test basic transaction to one output and coin announcement signed_tx_amount = 888000 tx_coin_announcements = [ Announcement( std_hash(b"coin_id_1"), std_hash(b"message"), b"\xca", ), Announcement( std_hash(b"coin_id_2"), bytes(Program.to("a string")), ), ] tx_res: TransactionRecord = await client.create_signed_transaction( [{"amount": signed_tx_amount, "puzzle_hash": ph_3}], coin_announcements=tx_coin_announcements ) assert tx_res.fee_amount == 0 assert tx_res.amount == signed_tx_amount assert len(tx_res.additions) == 2 # The output and the change assert any([addition.amount == signed_tx_amount for addition in tx_res.additions]) # check error for a ASSERT_ANNOUNCE_CONSUMED_FAILED and if the error is not there throw a value error try: push_res = await client_node.push_tx(tx_res.spend_bundle) except ValueError as error: error_string = error.args[0]["error"] # noqa: # pylint: disable=E1126 if error_string.find("ASSERT_ANNOUNCE_CONSUMED_FAILED") == -1: raise ValueError from error # # Test basic transaction to one output and puzzle announcement signed_tx_amount = 888000 tx_puzzle_announcements = [ Announcement( std_hash(b"puzzle_hash_1"), b"message", b"\xca", ), Announcement( std_hash(b"puzzle_hash_2"), bytes(Program.to("a string")), ), ] tx_res: TransactionRecord = await client.create_signed_transaction( [{"amount": signed_tx_amount, "puzzle_hash": ph_3}], puzzle_announcements=tx_puzzle_announcements ) assert tx_res.fee_amount == 0 assert tx_res.amount == signed_tx_amount assert len(tx_res.additions) == 2 # The output and the change assert any([addition.amount == signed_tx_amount for addition in tx_res.additions]) # check error for a ASSERT_ANNOUNCE_CONSUMED_FAILED and if the error is not there throw a value error try: push_res = await client_node.push_tx(tx_res.spend_bundle) except ValueError as error: error_string = error.args[0]["error"] # noqa: # pylint: disable=E1126 if error_string.find("ASSERT_ANNOUNCE_CONSUMED_FAILED") == -1: raise ValueError from error # Test basic transaction to one output signed_tx_amount = 888000 tx_res: TransactionRecord = await client.create_signed_transaction( [{"amount": signed_tx_amount, "puzzle_hash": ph_3, "memos": ["My memo"]}] ) assert tx_res.fee_amount == 0 assert tx_res.amount == signed_tx_amount assert len(tx_res.additions) == 2 # The output and the change assert any([addition.amount == signed_tx_amount for addition in tx_res.additions]) push_res = await client.push_tx(tx_res.spend_bundle) assert push_res["success"] assert (await client.get_wallet_balance("1"))[ "confirmed_wallet_balance" ] == initial_funds_eventually - tx_amount for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert(5, eventual_balance, initial_funds_eventually - tx_amount - signed_tx_amount) # Test transaction to two outputs, from a specified coin, with a fee coin_to_spend = None for addition in tx_res.additions: if addition.amount != signed_tx_amount: coin_to_spend = addition assert coin_to_spend is not None tx_res = await client.create_signed_transaction( [{"amount": 444, "puzzle_hash": ph_4, "memos": ["hhh"]}, {"amount": 999, "puzzle_hash": ph_5}], coins=[coin_to_spend], fee=100, ) assert tx_res.fee_amount == 100 assert tx_res.amount == 444 + 999 assert len(tx_res.additions) == 3 # The outputs and the change assert any([addition.amount == 444 for addition in tx_res.additions]) assert any([addition.amount == 999 for addition in tx_res.additions]) assert sum([rem.amount for rem in tx_res.removals]) - sum([ad.amount for ad in tx_res.additions]) == 100 push_res = await client_node.push_tx(tx_res.spend_bundle) assert push_res["success"] for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) found: bool = False for addition in tx_res.spend_bundle.additions(): if addition.amount == 444: cr: Optional[CoinRecord] = await client_node.get_coin_record_by_name(addition.name()) assert cr is not None spend: CoinSpend = await client_node.get_puzzle_and_solution( addition.parent_coin_info, cr.confirmed_block_index ) sb: SpendBundle = SpendBundle([spend], G2Element()) assert compute_memos(sb) == {addition.name(): [b"hhh"]} found = True assert found new_balance = initial_funds_eventually - tx_amount - signed_tx_amount - 444 - 999 - 100 await time_out_assert(5, eventual_balance, new_balance) send_tx_res: TransactionRecord = await client.send_transaction_multi( "1", [ {"amount": 555, "puzzle_hash": ph_4, "memos": ["FiMemo"]}, {"amount": 666, "puzzle_hash": ph_5, "memos": ["SeMemo"]}, ], fee=200, ) assert send_tx_res is not None assert send_tx_res.fee_amount == 200 assert send_tx_res.amount == 555 + 666 assert len(send_tx_res.additions) == 3 # The outputs and the change assert any([addition.amount == 555 for addition in send_tx_res.additions]) assert any([addition.amount == 666 for addition in send_tx_res.additions]) assert ( sum([rem.amount for rem in send_tx_res.removals]) - sum([ad.amount for ad in send_tx_res.additions]) == 200 ) await asyncio.sleep(3) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) new_balance = new_balance - 555 - 666 - 200 await time_out_assert(5, eventual_balance, new_balance) address = await client.get_next_address("1", True) assert len(address) > 10 transactions = await client.get_transactions("1") assert len(transactions) > 1 all_transactions = await client.get_transactions("1") # Test transaction pagination some_transactions = await client.get_transactions("1", 0, 5) some_transactions_2 = await client.get_transactions("1", 5, 10) assert some_transactions == all_transactions[0:5] assert some_transactions_2 == all_transactions[5:10] # Testing sorts # Test the default sort (CONFIRMED_AT_HEIGHT) assert all_transactions == sorted(all_transactions, key=attrgetter("confirmed_at_height")) all_transactions = await client.get_transactions("1", reverse=True) assert all_transactions == sorted(all_transactions, key=attrgetter("confirmed_at_height"), reverse=True) # Test RELEVANCE await client.send_transaction("1", 1, encode_puzzle_hash(ph_2, "xch")) # Create a pending tx all_transactions = await client.get_transactions("1", sort_key=SortKey.RELEVANCE) sorted_transactions = sorted(all_transactions, key=attrgetter("created_at_time"), reverse=True) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed_at_height"), reverse=True) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed")) assert all_transactions == sorted_transactions all_transactions = await client.get_transactions("1", sort_key=SortKey.RELEVANCE, reverse=True) sorted_transactions = sorted(all_transactions, key=attrgetter("created_at_time")) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed_at_height")) sorted_transactions = sorted(sorted_transactions, key=attrgetter("confirmed"), reverse=True) assert all_transactions == sorted_transactions # Checks that the memo can be retrieved tx_confirmed = await client.get_transaction("1", send_tx_res.name) assert tx_confirmed.confirmed if isinstance(tx_confirmed, SpendBundle): memos = compute_memos(tx_confirmed) else: memos = tx_confirmed.get_memos() assert len(memos) == 2 print(memos) assert [b"FiMemo"] in memos.values() assert [b"SeMemo"] in memos.values() assert list(memos.keys())[0] in [a.name() for a in send_tx_res.spend_bundle.additions()] assert list(memos.keys())[1] in [a.name() for a in send_tx_res.spend_bundle.additions()] ############## # CATS # ############## # Creates a wallet and a CAT with 20 mojos res = await client.create_new_cat_and_wallet(20) assert res["success"] cat_0_id = res["wallet_id"] asset_id = bytes.fromhex(res["asset_id"]) assert len(asset_id) > 0 bal_0 = await client.get_wallet_balance(cat_0_id) assert bal_0["confirmed_wallet_balance"] == 0 assert bal_0["pending_coin_removal_count"] == 1 col = await client.get_cat_asset_id(cat_0_id) assert col == asset_id assert (await client.get_cat_name(cat_0_id)) == "CAT Wallet" await client.set_cat_name(cat_0_id, "My cat") assert (await client.get_cat_name(cat_0_id)) == "My cat" wid, name = await client.cat_asset_id_to_name(col) assert wid == cat_0_id assert name == "My cat" should_be_none = await client.cat_asset_id_to_name(bytes([0] * 32)) assert should_be_none is None verified_asset_id = next(iter(DEFAULT_CATS.items()))[1]["asset_id"] should_be_none, name = await client.cat_asset_id_to_name(bytes.fromhex(verified_asset_id)) assert should_be_none is None assert name == next(iter(DEFAULT_CATS.items()))[1]["name"] await asyncio.sleep(1) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert(10, eventual_balance_det, 20, client, cat_0_id) bal_0 = await client.get_wallet_balance(cat_0_id) assert bal_0["pending_coin_removal_count"] == 0 assert bal_0["unspent_coin_count"] == 1 # Creates a second wallet with the same CAT res = await client_2.create_wallet_for_existing_cat(asset_id) assert res["success"] cat_1_id = res["wallet_id"] colour_1 = bytes.fromhex(res["asset_id"]) assert colour_1 == asset_id await asyncio.sleep(1) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) bal_1 = await client_2.get_wallet_balance(cat_1_id) assert bal_1["confirmed_wallet_balance"] == 0 addr_0 = await client.get_next_address(cat_0_id, False) addr_1 = await client_2.get_next_address(cat_1_id, False) assert addr_0 != addr_1 await client.cat_spend(cat_0_id, 4, addr_1, 0, ["the cat memo"]) await asyncio.sleep(1) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) await time_out_assert(10, eventual_balance_det, 16, client, cat_0_id) await time_out_assert(10, eventual_balance_det, 4, client_2, cat_1_id) ########## # Offers # ########## # Create an offer of 5 chia for one CAT offer, trade_record = await client.create_offer_for_ids({uint32(1): -5, cat_0_id: 1}, validate_only=True) all_offers = await client.get_all_offers() assert len(all_offers) == 0 assert offer is None offer, trade_record = await client.create_offer_for_ids({uint32(1): -5, cat_0_id: 1}, fee=uint64(1)) summary = await client.get_offer_summary(offer) assert summary == {"offered": {"xch": 5}, "requested": {col.hex(): 1}} assert await client.check_offer_validity(offer) all_offers = await client.get_all_offers(file_contents=True) assert len(all_offers) == 1 assert TradeStatus(all_offers[0].status) == TradeStatus.PENDING_ACCEPT assert all_offers[0].offer == bytes(offer) trade_record = await client_2.take_offer(offer, fee=uint64(1)) assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CONFIRM await client.cancel_offer(offer.name(), secure=False) trade_record = await client.get_offer(offer.name(), file_contents=True) assert trade_record.offer == bytes(offer) assert TradeStatus(trade_record.status) == TradeStatus.CANCELLED await client.cancel_offer(offer.name(), fee=uint64(1), secure=True) trade_record = await client.get_offer(offer.name()) assert TradeStatus(trade_record.status) == TradeStatus.PENDING_CANCEL new_offer, new_trade_record = await client.create_offer_for_ids({uint32(1): -5, cat_0_id: 1}, fee=uint64(1)) all_offers = await client.get_all_offers() assert len(all_offers) == 2 await asyncio.sleep(1) for i in range(0, 5): await client.farm_block(encode_puzzle_hash(ph_2, "xch")) await asyncio.sleep(0.5) async def is_trade_confirmed(client, trade) -> bool: trade_record = await client.get_offer(trade.name()) return TradeStatus(trade_record.status) == TradeStatus.CONFIRMED time_out_assert(15, is_trade_confirmed, True, client, offer) # Test trade sorting def only_ids(trades): return [t.trade_id for t in trades] trade_record = await client.get_offer(offer.name()) all_offers = await client.get_all_offers(include_completed=True) # confirmed at index descending assert len(all_offers) == 2 assert only_ids(all_offers) == only_ids([trade_record, new_trade_record]) all_offers = await client.get_all_offers( include_completed=True, reverse=True ) # confirmed at index ascending assert only_ids(all_offers) == only_ids([new_trade_record, trade_record]) all_offers = await client.get_all_offers(include_completed=True, sort_key="RELEVANCE") # most relevant assert only_ids(all_offers) == only_ids([new_trade_record, trade_record]) all_offers = await client.get_all_offers( include_completed=True, sort_key="RELEVANCE", reverse=True ) # least relevant assert only_ids(all_offers) == only_ids([trade_record, new_trade_record]) # Test pagination all_offers = await client.get_all_offers(include_completed=True, start=0, end=1) assert len(all_offers) == 1 all_offers = await client.get_all_offers(include_completed=True, start=50) assert len(all_offers) == 0 all_offers = await client.get_all_offers(include_completed=True, start=0, end=50) assert len(all_offers) == 2 # Keys and addresses address = await client.get_next_address("1", True) assert len(address) > 10 all_transactions = await client.get_transactions("1") some_transactions = await client.get_transactions("1", 0, 5) some_transactions_2 = await client.get_transactions("1", 5, 10) assert len(all_transactions) > 1 assert some_transactions == all_transactions[0:5] assert some_transactions_2 == all_transactions[5:10] transaction_count = await client.get_transaction_count("1") assert transaction_count == len(all_transactions) pks = await client.get_public_keys() assert len(pks) == 1 assert (await client.get_height_info()) > 0 created_tx = await client.send_transaction("1", tx_amount, addr) async def tx_in_mempool_2(): tx = await client.get_transaction("1", created_tx.name) return tx.is_in_mempool() await time_out_assert(5, tx_in_mempool_2, True) assert len(await wallet.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(1)) == 1 await client.delete_unconfirmed_transactions("1") assert len(await wallet.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(1)) == 0 sk_dict = await client.get_private_key(pks[0]) assert sk_dict["fingerprint"] == pks[0] assert sk_dict["sk"] is not None assert sk_dict["pk"] is not None assert sk_dict["seed"] is not None mnemonic = await client.generate_mnemonic() assert len(mnemonic) == 24 await client.add_key(mnemonic) pks = await client.get_public_keys() assert len(pks) == 2 await client.log_in_and_skip(pks[1]) sk_dict = await client.get_private_key(pks[1]) assert sk_dict["fingerprint"] == pks[1] # Add in reward addresses into farmer and pool for testing delete key checks # set farmer to first private key sk = await wallet_node.get_key_for_fingerprint(pks[0]) test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk(sk, uint32(0)).get_g1()) test_config = load_config(wallet_node.root_path, "config.yaml") test_config["farmer"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch") # set pool to second private key sk = await wallet_node.get_key_for_fingerprint(pks[1]) test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk(sk, uint32(0)).get_g1()) test_config["pool"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch") save_config(wallet_node.root_path, "config.yaml", test_config) # Check first key sk_dict = await client.check_delete_key(pks[0]) assert sk_dict["fingerprint"] == pks[0] assert sk_dict["used_for_farmer_rewards"] is True assert sk_dict["used_for_pool_rewards"] is False # Check second key sk_dict = await client.check_delete_key(pks[1]) assert sk_dict["fingerprint"] == pks[1] assert sk_dict["used_for_farmer_rewards"] is False assert sk_dict["used_for_pool_rewards"] is True # Check unknown key sk_dict = await client.check_delete_key(123456) assert sk_dict["fingerprint"] == 123456 assert sk_dict["used_for_farmer_rewards"] is False assert sk_dict["used_for_pool_rewards"] is False await client.delete_key(pks[0]) await client.log_in_and_skip(pks[1]) assert len(await client.get_public_keys()) == 1 assert not (await client.get_sync_status()) wallets = await client.get_wallets() assert len(wallets) == 1 balance = await client.get_wallet_balance(wallets[0]["id"]) assert balance["unconfirmed_wallet_balance"] == 0 try: await client.send_transaction(wallets[0]["id"], 100, addr) raise Exception("Should not create tx if no balance") except ValueError: pass # Delete all keys await client.delete_all_keys() assert len(await client.get_public_keys()) == 0 finally: # Checks that the RPC manages to stop the node client.close() client_2.close() client_node.close() await client.await_closed() await client_2.await_closed() await client_node.await_closed() await rpc_cleanup() await rpc_cleanup_2() await rpc_cleanup_node()
async def generate_signed_transaction( self, amounts: List[uint64], puzzle_hashes: List[bytes32], fee: uint64 = uint64(0), coins: Set[Coin] = None, ignore_max_send_amount: bool = False, memos: Optional[List[List[bytes]]] = None, coin_announcements_to_consume: Optional[Set[Announcement]] = None, puzzle_announcements_to_consume: Optional[Set[Announcement]] = None, ) -> List[TransactionRecord]: if memos is None: memos = [[] for _ in range(len(puzzle_hashes))] if not (len(memos) == len(puzzle_hashes) == len(amounts)): raise ValueError( "Memos, puzzle_hashes, and amounts must have the same length") payments = [] for amount, puzhash, memo_list in zip(amounts, puzzle_hashes, memos): memos_with_hint: List[bytes] = [puzhash] memos_with_hint.extend(memo_list) payments.append(Payment(puzhash, amount, memos_with_hint)) payment_sum = sum([p.amount for p in payments]) if not ignore_max_send_amount: max_send = await self.get_max_send_amount() if payment_sum > max_send: raise ValueError( f"Can't send more than {max_send} in a single transaction") unsigned_spend_bundle, chia_tx = await self.generate_unsigned_spendbundle( payments, fee, coins=coins, coin_announcements_to_consume=coin_announcements_to_consume, puzzle_announcements_to_consume=puzzle_announcements_to_consume, ) spend_bundle = await self.sign(unsigned_spend_bundle) # TODO add support for array in stored records tx_list = [ TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzzle_hashes[0], amount=uint64(payment_sum), fee_amount=fee, confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), ) ] if chia_tx is not None: tx_list.append( TransactionRecord( confirmed_at_height=chia_tx.confirmed_at_height, created_at_time=chia_tx.created_at_time, to_puzzle_hash=chia_tx.to_puzzle_hash, amount=chia_tx.amount, fee_amount=chia_tx.fee_amount, confirmed=chia_tx.confirmed, sent=chia_tx.sent, spend_bundle=None, additions=chia_tx.additions, removals=chia_tx.removals, wallet_id=chia_tx.wallet_id, sent_to=chia_tx.sent_to, trade_id=chia_tx.trade_id, type=chia_tx.type, name=chia_tx.name, memos=[], )) return tx_list
async def recovery_spend( self, coin: Coin, puzhash: bytes32, parent_innerpuzhash_amounts_for_recovery_ids: List[Tuple[bytes, bytes, int]], pubkey: G1Element, spend_bundle: SpendBundle, ) -> SpendBundle: assert self.did_info.origin_coin is not None # innersol is mode new_amount message new_inner_puzhash parent_innerpuzhash_amounts_for_recovery_ids pubkey recovery_list_reveal) # noqa innersol: Program = Program.to( [ 2, coin.amount, puzhash, puzhash, parent_innerpuzhash_amounts_for_recovery_ids, bytes(pubkey), self.did_info.backup_ids, ] ) # full solution is (parent_info my_amount solution) assert self.did_info.current_inner is not None innerpuz: Program = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.name(), ) parent_info = self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to( [ [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ] ) list_of_solutions = [CoinSpend(coin, full_puzzle, fullsol)] index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pubkey) if index is None: raise ValueError("Unknown pubkey.") private = master_sk_to_wallet_sk_unhardened(self.wallet_state_manager.private_key, index) message = bytes(puzhash) sigs = [AugSchemeMPL.sign(private, message)] for _ in spend_bundle.coin_spends: sigs.append(AugSchemeMPL.sign(private, message)) aggsig = AugSchemeMPL.aggregate(sigs) # assert AugSchemeMPL.verify(pubkey, message, aggsig) if spend_bundle is None: spend_bundle = SpendBundle(list_of_solutions, aggsig) else: spend_bundle = spend_bundle.aggregate([spend_bundle, SpendBundle(list_of_solutions, aggsig)]) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzhash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=bytes32(token_bytes()), memos=list(compute_memos(spend_bundle).items()), ) await self.standard_wallet.push_transaction(did_record) new_did_info = DIDInfo( self.did_info.origin_coin, self.did_info.backup_ids, self.did_info.num_of_backup_ids_needed, self.did_info.parent_info, self.did_info.current_inner, self.did_info.temp_coin, self.did_info.temp_puzhash, self.did_info.temp_pubkey, True, ) await self.save_info(new_did_info, True) return spend_bundle
async def create_attestment( self, recovering_coin_name: bytes32, newpuz: bytes32, pubkey: G1Element, filename=None ) -> SpendBundle: assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coins = await self.select_coins(1) assert coins is not None and coins != set() coin = coins.pop() message = did_wallet_puzzles.create_recovery_message_puzzle(recovering_coin_name, newpuz, pubkey) innermessage = message.get_tree_hash() innerpuz: Program = self.did_info.current_inner # innerpuz solution is (mode, amount, message, new_inner_puzhash) messages = [(0, innermessage)] innersol = Program.to([1, coin.amount, messages, innerpuz.get_tree_hash()]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.name(), ) parent_info = self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to( [ [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ] ) list_of_solutions = [CoinSpend(coin, full_puzzle, fullsol)] message_spend = did_wallet_puzzles.create_spend_for_message(coin.name(), recovering_coin_name, newpuz, pubkey) message_spend_bundle = SpendBundle([message_spend], AugSchemeMPL.aggregate([])) # sign for AGG_SIG_ME to_sign = Program.to([innerpuz.get_tree_hash(), coin.amount, messages]).get_tree_hash() message = to_sign + coin.name() + self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pubkey) private = master_sk_to_wallet_sk_unhardened(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) # assert signature.validate([signature.PkMessagePair(pubkey, message)]) spend_bundle = SpendBundle(list_of_solutions, signature) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=coin.puzzle_hash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), name=bytes32(token_bytes()), memos=list(compute_memos(spend_bundle).items()), ) await self.standard_wallet.push_transaction(did_record) if filename is not None: f = open(filename, "w") f.write(self.get_my_DID()) f.write(":") f.write(bytes(message_spend_bundle).hex()) f.write(":") parent = coin.parent_coin_info.hex() innerpuzhash = self.did_info.current_inner.get_tree_hash().hex() amount = coin.amount f.write(parent) f.write(":") f.write(innerpuzhash) f.write(":") f.write(str(amount)) f.close() return message_spend_bundle
async def create_exit_spend(self, puzhash: bytes32): assert self.did_info.current_inner is not None assert self.did_info.origin_coin is not None coins = await self.select_coins(1) assert coins is not None coin = coins.pop() amount = coin.amount - 1 # innerpuz solution is (mode amount new_puzhash) innersol: Program = Program.to([0, amount, puzhash]) # full solution is (corehash parent_info my_amount innerpuz_reveal solution) innerpuz: Program = self.did_info.current_inner full_puzzle: Program = did_wallet_puzzles.create_fullpuz( innerpuz, self.did_info.origin_coin.name(), ) parent_info = self.get_parent_for_coin(coin) assert parent_info is not None fullsol = Program.to( [ [ parent_info.parent_name, parent_info.inner_puzzle_hash, parent_info.amount, ], coin.amount, innersol, ] ) list_of_solutions = [CoinSpend(coin, full_puzzle, fullsol)] # sign for AGG_SIG_ME message = ( Program.to([amount, puzhash]).get_tree_hash() + coin.name() + self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA ) pubkey = did_wallet_puzzles.get_pubkey_from_innerpuz(innerpuz) index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pubkey) private = master_sk_to_wallet_sk_unhardened(self.wallet_state_manager.private_key, index) signature = AugSchemeMPL.sign(private, message) # assert signature.validate([signature.PkMessagePair(pubkey, message)]) sigs = [signature] aggsig = AugSchemeMPL.aggregate(sigs) spend_bundle = SpendBundle(list_of_solutions, aggsig) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=puzhash, amount=uint64(coin.amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_info.id, sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=bytes32(token_bytes()), memos=list(compute_memos(spend_bundle).items()), ) await self.standard_wallet.push_transaction(did_record) return spend_bundle
async def create_new_did_wallet( wallet_state_manager: Any, wallet: Wallet, amount: uint64, backups_ids: List = [], num_of_backup_ids_needed: uint64 = None, name: str = None, ): """ This must be called under the wallet state manager lock """ self = DIDWallet() self.base_puzzle_program = None self.base_inner_puzzle_hash = None self.standard_wallet = wallet self.log = logging.getLogger(name if name else __name__) std_wallet_id = self.standard_wallet.wallet_id bal = await wallet_state_manager.get_confirmed_balance_for_wallet_already_locked(std_wallet_id) if amount > bal: raise ValueError("Not enough balance") if amount & 1 == 0: raise ValueError("DID amount must be odd number") self.wallet_state_manager = wallet_state_manager if num_of_backup_ids_needed is None: num_of_backup_ids_needed = uint64(len(backups_ids)) if num_of_backup_ids_needed > len(backups_ids): raise ValueError("Cannot require more IDs than are known.") self.did_info = DIDInfo(None, backups_ids, num_of_backup_ids_needed, [], None, None, None, None, False) info_as_string = json.dumps(self.did_info.to_json_dict()) self.wallet_info = await wallet_state_manager.user_store.create_wallet( "DID Wallet", WalletType.DISTRIBUTED_ID.value, info_as_string ) if self.wallet_info is None: raise ValueError("Internal Error") self.wallet_id = self.wallet_info.id std_wallet_id = self.standard_wallet.wallet_id bal = await wallet_state_manager.get_confirmed_balance_for_wallet_already_locked(std_wallet_id) if amount > bal: raise ValueError("Not enough balance") try: spend_bundle = await self.generate_new_decentralised_id(uint64(amount)) except Exception: await wallet_state_manager.user_store.delete_wallet(self.id(), False) raise if spend_bundle is None: await wallet_state_manager.user_store.delete_wallet(self.id(), False) raise ValueError("Failed to create spend.") await self.wallet_state_manager.add_new_wallet(self, self.wallet_info.id) assert self.did_info.origin_coin is not None assert self.did_info.current_inner is not None did_puzzle_hash = did_wallet_puzzles.create_fullpuz( self.did_info.current_inner, self.did_info.origin_coin.name() ).get_tree_hash() did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=did_puzzle_hash, amount=uint64(amount), fee_amount=uint64(0), confirmed=False, sent=uint32(10), spend_bundle=None, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.INCOMING_TX.value), name=bytes32(token_bytes()), memos=[], ) regular_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=did_puzzle_hash, amount=uint64(amount), fee_amount=uint64(0), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=self.wallet_state_manager.main_wallet.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=bytes32(token_bytes()), memos=list(compute_memos(spend_bundle).items()), ) await self.standard_wallet.push_transaction(regular_record) await self.standard_wallet.push_transaction(did_record) return self
async def generate_signed_transaction( self, amount: uint64, puzzle_hash: bytes32, fee: uint64 = uint64(0), origin_id: bytes32 = None, coins: Set[Coin] = None, primaries: Optional[List[AmountWithPuzzlehash]] = None, ignore_max_send_amount: bool = False, coin_announcements_to_consume: Set[Announcement] = None, puzzle_announcements_to_consume: Set[Announcement] = None, memos: Optional[List[bytes]] = None, negative_change_allowed: bool = False, ) -> TransactionRecord: """ Use this to generate transaction. Note: this must be called under a wallet state manager lock The first output is (amount, puzzle_hash, memos), and the rest of the outputs are in primaries. """ if primaries is None: non_change_amount = amount else: non_change_amount = uint64(amount + sum(p["amount"] for p in primaries)) transaction = await self._generate_unsigned_transaction( amount, puzzle_hash, fee, origin_id, coins, primaries, ignore_max_send_amount, coin_announcements_to_consume, puzzle_announcements_to_consume, memos, negative_change_allowed, ) assert len(transaction) > 0 self.log.info("About to sign a transaction") await self.hack_populate_secret_keys_for_coin_spends(transaction) spend_bundle: SpendBundle = await sign_coin_spends( transaction, self.secret_key_store.secret_key_for_public_key, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, ) now = uint64(int(time.time())) add_list: List[Coin] = list(spend_bundle.additions()) rem_list: List[Coin] = list(spend_bundle.removals()) output_amount = sum(a.amount for a in add_list) + fee input_amount = sum(r.amount for r in rem_list) if negative_change_allowed: assert output_amount >= input_amount else: assert output_amount == input_amount return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=now, to_puzzle_hash=puzzle_hash, amount=uint64(non_change_amount), fee_amount=uint64(fee), confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=add_list, removals=rem_list, wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), memos=list(compute_memos(spend_bundle).items()), )