mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-11 10:56:14 +03:00
Expand select_coins rpc (#12360)
* change type to uint64, 128 is too big anyway * add new options to select_coins endpoint * oops * add tests and finalize * oops
This commit is contained in:
parent
7fda9596af
commit
d5bf4d8b59
@ -17,7 +17,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.bech32m import bech32_decode, decode_puzzle_hash, encode_puzzle_hash
|
||||
from chia.util.config import load_config
|
||||
from chia.util.default_root import DEFAULT_ROOT_PATH
|
||||
from chia.util.ints import uint16, uint32, uint64, uint128
|
||||
from chia.util.ints import uint16, uint32, uint64
|
||||
from chia.wallet.did_wallet.did_info import DID_HRP
|
||||
from chia.wallet.nft_wallet.nft_info import NFT_HRP, NFTInfo
|
||||
from chia.wallet.trade_record import TradeRecord
|
||||
@ -213,17 +213,17 @@ async def send(args: dict, wallet_client: WalletRpcClient, fingerprint: int) ->
|
||||
|
||||
final_fee = uint64(int(fee * units["chia"]))
|
||||
final_amount: uint64
|
||||
final_min_coin_amount: uint128
|
||||
final_min_coin_amount: uint64
|
||||
if typ == WalletType.STANDARD_WALLET:
|
||||
final_amount = uint64(int(amount * units["chia"]))
|
||||
final_min_coin_amount = uint128(int(min_coin_amount * units["chia"]))
|
||||
final_min_coin_amount = uint64(int(min_coin_amount * units["chia"]))
|
||||
print("Submitting transaction...")
|
||||
res = await wallet_client.send_transaction(
|
||||
str(wallet_id), final_amount, address, final_fee, memos, final_min_coin_amount
|
||||
)
|
||||
elif typ == WalletType.CAT:
|
||||
final_amount = uint64(int(amount * units["cat"]))
|
||||
final_min_coin_amount = uint128(int(min_coin_amount * units["cat"]))
|
||||
final_min_coin_amount = uint64(int(min_coin_amount * units["cat"]))
|
||||
print("Submitting transaction...")
|
||||
res = await wallet_client.cat_spend(
|
||||
str(wallet_id), final_amount, address, final_fee, memos, final_min_coin_amount
|
||||
|
@ -24,7 +24,7 @@ from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
|
||||
from chia.util.byte_types import hexstr_to_bytes
|
||||
from chia.util.config import load_config
|
||||
from chia.util.ints import uint8, uint32, uint64, uint16, uint128
|
||||
from chia.util.ints import uint8, uint32, uint64, uint16
|
||||
from chia.util.keychain import KeyringIsLocked, bytes_to_mnemonic, generate_mnemonic
|
||||
from chia.util.path import path_from_root
|
||||
from chia.util.ws_message import WsRpcMessage, create_payload_dict
|
||||
@ -815,7 +815,7 @@ class WalletRpcApi:
|
||||
memos = [mem.encode("utf-8") for mem in request["memos"]]
|
||||
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
min_coin_amount: uint128 = uint128(request.get("min_coin_amount", 0))
|
||||
min_coin_amount: uint64 = uint64(request.get("min_coin_amount", 0))
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
tx: TransactionRecord = await wallet.generate_signed_transaction(
|
||||
amount, puzzle_hash, fee, memos=memos, min_coin_amount=min_coin_amount
|
||||
@ -862,10 +862,16 @@ class WalletRpcApi:
|
||||
|
||||
amount = uint64(request["amount"])
|
||||
wallet_id = uint32(request["wallet_id"])
|
||||
min_coin_amount = uint64(request.get("min_coin_amount", 0))
|
||||
excluded_coins: Optional[List] = request.get("excluded_coins")
|
||||
if excluded_coins is not None:
|
||||
excluded_coins = [Coin.from_json_dict(json_coin) for json_coin in excluded_coins]
|
||||
|
||||
wallet = self.service.wallet_state_manager.wallets[wallet_id]
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
selected_coins = await wallet.select_coins(amount=amount)
|
||||
selected_coins = await wallet.select_coins(
|
||||
amount=amount, min_coin_amount=min_coin_amount, exclude=excluded_coins
|
||||
)
|
||||
|
||||
return {"coins": [coin.to_json_dict() for coin in selected_coins]}
|
||||
|
||||
@ -912,7 +918,7 @@ class WalletRpcApi:
|
||||
raise ValueError("An integer amount or fee is required (too many decimals)")
|
||||
amount: uint64 = uint64(request["amount"])
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
min_coin_amount: uint128 = uint128(request.get("min_coin_amount", 0))
|
||||
min_coin_amount: uint64 = uint64(request.get("min_coin_amount", 0))
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
txs: List[TransactionRecord] = await wallet.generate_signed_transaction(
|
||||
[amount], [puzzle_hash], fee, memos=[memos], min_coin_amount=min_coin_amount
|
||||
@ -946,7 +952,7 @@ class WalletRpcApi:
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
validate_only: bool = request.get("validate_only", False)
|
||||
driver_dict_str: Optional[Dict[str, Any]] = request.get("driver_dict", None)
|
||||
min_coin_amount: uint128 = uint128(request.get("min_coin_amount", 0))
|
||||
min_coin_amount: uint64 = uint64(request.get("min_coin_amount", 0))
|
||||
|
||||
# This driver_dict construction is to maintain backward compatibility where everything is assumed to be a CAT
|
||||
driver_dict: Dict[bytes32, PuzzleInfo] = {}
|
||||
@ -999,7 +1005,7 @@ class WalletRpcApi:
|
||||
offer_hex: str = request["offer"]
|
||||
offer = Offer.from_bech32(offer_hex)
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
min_coin_amount: uint128 = uint128(request.get("min_coin_amount", 0))
|
||||
min_coin_amount: uint64 = uint64(request.get("min_coin_amount", 0))
|
||||
|
||||
async with self.service.wallet_state_manager.lock:
|
||||
result = await self.service.wallet_state_manager.trade_manager.respond_to_offer(
|
||||
@ -1687,7 +1693,7 @@ class WalletRpcApi:
|
||||
additional_outputs.append({"puzzlehash": receiver_ph, "amount": amount, "memos": memos})
|
||||
|
||||
fee: uint64 = uint64(request.get("fee", 0))
|
||||
min_coin_amount: uint128 = uint128(request.get("min_coin_amount", 0))
|
||||
min_coin_amount: uint64 = uint64(request.get("min_coin_amount", 0))
|
||||
|
||||
coins = None
|
||||
if "coins" in request and len(request["coins"]) > 0:
|
||||
|
@ -5,7 +5,7 @@ from chia.rpc.rpc_client import RpcClient
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint32, uint64, uint128
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.wallet.trade_record import TradeRecord
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
@ -146,7 +146,7 @@ class WalletRpcClient(RpcClient):
|
||||
address: str,
|
||||
fee: uint64 = uint64(0),
|
||||
memos: Optional[List[str]] = None,
|
||||
min_coin_amount: uint128 = uint128(0),
|
||||
min_coin_amount: uint64 = uint64(0),
|
||||
) -> TransactionRecord:
|
||||
if memos is None:
|
||||
send_dict: Dict = {
|
||||
@ -207,7 +207,7 @@ class WalletRpcClient(RpcClient):
|
||||
fee: uint64 = uint64(0),
|
||||
coin_announcements: Optional[List[Announcement]] = None,
|
||||
puzzle_announcements: Optional[List[Announcement]] = None,
|
||||
min_coin_amount: uint128 = uint128(0),
|
||||
min_coin_amount: uint64 = uint64(0),
|
||||
) -> TransactionRecord:
|
||||
# Converts bytes to hex for puzzle hashes
|
||||
additions_hex = []
|
||||
@ -249,8 +249,22 @@ class WalletRpcClient(RpcClient):
|
||||
response: Dict = await self.fetch("create_signed_transaction", request)
|
||||
return TransactionRecord.from_json_dict_convenience(response["signed_tx"])
|
||||
|
||||
async def select_coins(self, *, amount: int, wallet_id: int) -> List[Coin]:
|
||||
request = {"amount": amount, "wallet_id": wallet_id}
|
||||
async def select_coins(
|
||||
self,
|
||||
*,
|
||||
amount: int,
|
||||
wallet_id: int,
|
||||
excluded_coins: Optional[List[Coin]] = None,
|
||||
min_coin_amount: uint64 = uint64(0),
|
||||
) -> List[Coin]:
|
||||
if excluded_coins is None:
|
||||
excluded_coins = []
|
||||
request = {
|
||||
"amount": amount,
|
||||
"wallet_id": wallet_id,
|
||||
"min_coin_amount": min_coin_amount,
|
||||
"excluded_coins": [excluded_coin.to_json_dict() for excluded_coin in excluded_coins],
|
||||
}
|
||||
response: Dict[str, List[Dict]] = await self.fetch("select_coins", request)
|
||||
return [Coin.from_json_dict(coin) for coin in response["coins"]]
|
||||
|
||||
@ -499,7 +513,7 @@ class WalletRpcClient(RpcClient):
|
||||
inner_address: str,
|
||||
fee: uint64 = uint64(0),
|
||||
memos: Optional[List[str]] = None,
|
||||
min_coin_amount: uint128 = uint128(0),
|
||||
min_coin_amount: uint64 = uint64(0),
|
||||
) -> TransactionRecord:
|
||||
send_dict = {
|
||||
"wallet_id": wallet_id,
|
||||
@ -519,7 +533,7 @@ class WalletRpcClient(RpcClient):
|
||||
driver_dict: Dict[str, Any] = None,
|
||||
fee=uint64(0),
|
||||
validate_only: bool = False,
|
||||
min_coin_amount: uint128 = uint128(0),
|
||||
min_coin_amount: uint64 = uint64(0),
|
||||
) -> Tuple[Optional[Offer], TradeRecord]:
|
||||
send_dict: Dict[str, int] = {}
|
||||
for key in offer_dict:
|
||||
@ -546,7 +560,7 @@ class WalletRpcClient(RpcClient):
|
||||
res = await self.fetch("check_offer_validity", {"offer": offer.to_bech32()})
|
||||
return res["valid"]
|
||||
|
||||
async def take_offer(self, offer: Offer, fee=uint64(0), min_coin_amount: uint128 = uint128(0)) -> TradeRecord:
|
||||
async def take_offer(self, offer: Offer, fee=uint64(0), min_coin_amount: uint64 = uint64(0)) -> TradeRecord:
|
||||
res = await self.fetch(
|
||||
"take_offer", {"offer": offer.to_bech32(), "fee": fee, "min_coin_amount": min_coin_amount}
|
||||
)
|
||||
|
@ -422,7 +422,7 @@ class CATWallet:
|
||||
return result
|
||||
|
||||
async def select_coins(
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint128] = None
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint64] = None
|
||||
) -> Set[Coin]:
|
||||
"""
|
||||
Returns a set of coins that can be used for generating a new transaction.
|
||||
@ -508,7 +508,7 @@ class CATWallet:
|
||||
fee: uint64,
|
||||
amount_to_claim: uint64,
|
||||
announcement_to_assert: Optional[Announcement] = None,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Tuple[TransactionRecord, Optional[Announcement]]:
|
||||
"""
|
||||
This function creates a non-CAT transaction to pay fees, contribute funds for issuance, and absorb melt value.
|
||||
@ -562,7 +562,7 @@ class CATWallet:
|
||||
coins: Set[Coin] = None,
|
||||
coin_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Tuple[SpendBundle, Optional[TransactionRecord]]:
|
||||
if coin_announcements_to_consume is not None:
|
||||
coin_announcements_bytes: Optional[Set[bytes32]] = {a.name() for a in coin_announcements_to_consume}
|
||||
@ -698,7 +698,7 @@ class CATWallet:
|
||||
memos: Optional[List[List[bytes]]] = None,
|
||||
coin_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> List[TransactionRecord]:
|
||||
if memos is None:
|
||||
memos = [[] for _ in range(len(puzzle_hashes))]
|
||||
@ -804,7 +804,7 @@ class CATWallet:
|
||||
return PuzzleInfo({"type": AssetType.CAT.value, "tail": "0x" + self.get_asset_id()})
|
||||
|
||||
async def get_coins_to_offer(
|
||||
self, asset_id: Optional[bytes32], amount: uint64, min_coin_amount: Optional[uint128] = None
|
||||
self, asset_id: Optional[bytes32], amount: uint64, min_coin_amount: Optional[uint64] = None
|
||||
) -> Set[Coin]:
|
||||
balance = await self.get_confirmed_balance()
|
||||
if balance < amount:
|
||||
|
@ -16,7 +16,7 @@ async def select_coins(
|
||||
log: logging.Logger,
|
||||
amount: uint128,
|
||||
exclude: Optional[List[Coin]] = None,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Set[Coin]:
|
||||
"""
|
||||
Returns a set of coins that can be used for generating a new transaction.
|
||||
@ -24,7 +24,7 @@ async def select_coins(
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
if min_coin_amount is None:
|
||||
min_coin_amount = uint128(0)
|
||||
min_coin_amount = uint64(0)
|
||||
|
||||
if amount > spendable_amount:
|
||||
error_msg = (
|
||||
|
@ -297,7 +297,7 @@ class DIDWallet:
|
||||
return await self.wallet_state_manager.get_unconfirmed_balance(self.id(), record_list)
|
||||
|
||||
async def select_coins(
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint128] = None
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint64] = None
|
||||
) -> Optional[Set[Coin]]:
|
||||
"""
|
||||
Returns a set of coins that can be used for generating a new transaction.
|
||||
|
@ -525,7 +525,7 @@ class NFTWallet:
|
||||
return puzzle_info
|
||||
|
||||
async def get_coins_to_offer(
|
||||
self, nft_id: bytes32, amount: uint64, min_coin_amount: Optional[uint128] = None
|
||||
self, nft_id: bytes32, amount: uint64, min_coin_amount: Optional[uint64] = None
|
||||
) -> Set[Coin]:
|
||||
nft_coin: Optional[NFTCoinInfo] = self.get_nft(nft_id)
|
||||
if nft_coin is None:
|
||||
|
@ -13,7 +13,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.ints import uint32, uint64, uint128
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
|
||||
from chia.wallet.outer_puzzles import AssetType
|
||||
from chia.wallet.payment import Payment
|
||||
@ -299,7 +299,7 @@ class TradeManager:
|
||||
driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
validate_only: bool = False,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Union[Tuple[Literal[True], TradeRecord, None], Tuple[Literal[False], None, str]]:
|
||||
if driver_dict is None:
|
||||
driver_dict = {}
|
||||
@ -334,7 +334,7 @@ class TradeManager:
|
||||
offer_dict: Dict[Union[int, bytes32], int],
|
||||
driver_dict: Optional[Dict[bytes32, PuzzleInfo]] = None,
|
||||
fee: uint64 = uint64(0),
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Union[Tuple[Literal[True], Offer, None], Tuple[Literal[False], None, str]]:
|
||||
"""
|
||||
Offer is dictionary of wallet ids and amount
|
||||
@ -592,7 +592,7 @@ class TradeManager:
|
||||
self,
|
||||
offer: Offer,
|
||||
fee=uint64(0),
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> Union[Tuple[Literal[True], TradeRecord, None], Tuple[Literal[False], None, str]]:
|
||||
take_offer_dict: Dict[Union[bytes32, int], int] = {}
|
||||
arbitrage: Dict[Optional[bytes32], int] = offer.arbitrage()
|
||||
|
@ -247,7 +247,7 @@ class Wallet:
|
||||
return Program.to(python_program)
|
||||
|
||||
async def select_coins(
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint128] = None
|
||||
self, amount: uint64, exclude: Optional[List[Coin]] = None, min_coin_amount: Optional[uint64] = None
|
||||
) -> Set[Coin]:
|
||||
"""
|
||||
Returns a set of coins that can be used for generating a new transaction.
|
||||
@ -290,7 +290,7 @@ class Wallet:
|
||||
puzzle_announcements_to_consume: Set[Announcement] = None,
|
||||
memos: Optional[List[bytes]] = None,
|
||||
negative_change_allowed: bool = False,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> List[CoinSpend]:
|
||||
"""
|
||||
Generates a unsigned transaction in form of List(Puzzle, Solutions)
|
||||
@ -417,7 +417,7 @@ class Wallet:
|
||||
puzzle_announcements_to_consume: Set[Announcement] = None,
|
||||
memos: Optional[List[bytes]] = None,
|
||||
negative_change_allowed: bool = False,
|
||||
min_coin_amount: Optional[uint128] = None,
|
||||
min_coin_amount: Optional[uint64] = None,
|
||||
) -> TransactionRecord:
|
||||
"""
|
||||
Use this to generate transaction.
|
||||
@ -530,7 +530,7 @@ class Wallet:
|
||||
return spend_bundle
|
||||
|
||||
async def get_coins_to_offer(
|
||||
self, asset_id: Optional[bytes32], amount: uint64, min_coin_amount: Optional[uint128] = None
|
||||
self, asset_id: Optional[bytes32], amount: uint64, min_coin_amount: Optional[uint64] = None
|
||||
) -> Set[Coin]:
|
||||
if asset_id is not None:
|
||||
raise ValueError(f"The standard wallet cannot offer coins with asset id {asset_id}")
|
||||
|
@ -19,6 +19,7 @@ from chia.server.server import ChiaServer
|
||||
from chia.simulator.full_node_simulator import FullNodeSimulator
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
from chia.types.announcement import Announcement
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_record import CoinRecord
|
||||
@ -1019,3 +1020,45 @@ async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEn
|
||||
# Delete all keys
|
||||
await client.delete_all_keys()
|
||||
assert len(await client.get_public_keys()) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_select_coins_rpc(wallet_rpc_environment: WalletRpcTestEnvironment):
|
||||
env: WalletRpcTestEnvironment = wallet_rpc_environment
|
||||
|
||||
wallet_2: Wallet = env.wallet_2.wallet
|
||||
wallet_node: WalletNode = env.wallet_1.node
|
||||
full_node_api: FullNodeSimulator = env.full_node.api
|
||||
client: WalletRpcClient = env.wallet_1.rpc_client
|
||||
client_2: WalletRpcClient = env.wallet_2.rpc_client
|
||||
|
||||
funds = await generate_funds(full_node_api, env.wallet_1)
|
||||
|
||||
addr = encode_puzzle_hash(await wallet_2.get_new_puzzlehash(), "txch")
|
||||
coin_300: List[Coin]
|
||||
for tx_amount in [uint64(1000), uint64(300), uint64(1000), uint64(1000), uint64(10000)]:
|
||||
funds -= tx_amount
|
||||
# create coins for tests
|
||||
tx = await client.send_transaction("1", tx_amount, addr)
|
||||
spend_bundle = tx.spend_bundle
|
||||
assert spend_bundle is not None
|
||||
for coin in spend_bundle.additions():
|
||||
if coin.amount == uint64(300):
|
||||
coin_300 = [coin]
|
||||
|
||||
await time_out_assert(5, tx_in_mempool, True, client, tx.name)
|
||||
await farm_transaction(full_node_api, wallet_node, spend_bundle)
|
||||
await time_out_assert(5, get_confirmed_balance, funds, client, 1)
|
||||
|
||||
# test min coin amount
|
||||
min_coins: List[Coin] = await client_2.select_coins(amount=1000, wallet_id=1, min_coin_amount=uint64(1001))
|
||||
assert min_coins is not None
|
||||
assert len(min_coins) == 1 and min_coins[0].amount == uint64(10000)
|
||||
|
||||
# test excluded coins
|
||||
with pytest.raises(ValueError):
|
||||
await client_2.select_coins(amount=5000, wallet_id=1, excluded_coins=min_coins)
|
||||
excluded_test = await client_2.select_coins(amount=1300, wallet_id=1, excluded_coins=coin_300)
|
||||
assert len(excluded_test) == 2
|
||||
for coin in excluded_test:
|
||||
assert coin != coin_300[0]
|
||||
|
@ -449,7 +449,7 @@ class TestCoinSelection:
|
||||
{},
|
||||
logging.getLogger("test"),
|
||||
uint128(target_amount),
|
||||
min_coin_amount=uint128(min_coin_amount),
|
||||
min_coin_amount=uint64(min_coin_amount),
|
||||
)
|
||||
assert result is not None # this should never happen
|
||||
assert sum(coin.amount for coin in result) >= target_amount
|
||||
|
Loading…
Reference in New Issue
Block a user