Merge pull request #12619 from Chia-Network/post_1.5.0-expand_select_coins_rpc

post 1.5.0 - expand select coins rpc
This commit is contained in:
Earle Lowe 2022-07-27 09:09:31 -07:00 committed by GitHub
commit 916ccee549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 37 deletions

View File

@ -18,7 +18,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

View File

@ -23,7 +23,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
@ -840,7 +840,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
@ -896,10 +896,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]}
@ -999,7 +1005,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
@ -1037,7 +1043,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] = {}
@ -1096,7 +1102,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:
(success, trade_record, error,) = await self.service.wallet_state_manager.trade_manager.respond_to_offer(
@ -1805,7 +1811,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:

View File

@ -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 = {
@ -213,7 +213,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 = []
@ -255,8 +255,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"]]
@ -505,7 +519,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,
@ -525,7 +539,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:
@ -552,7 +566,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}
)

View File

@ -445,7 +445,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.
@ -532,7 +532,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.
@ -586,7 +586,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}
@ -722,7 +722,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))]
@ -828,7 +828,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:

View File

@ -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 = (

View File

@ -299,7 +299,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.

View File

@ -547,7 +547,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:

View File

@ -11,7 +11,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.spend_bundle import SpendBundle
from chia.util.db_wrapper import DBWrapper
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
@ -297,7 +297,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,
) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
if driver_dict is None:
driver_dict = {}
@ -331,7 +331,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,
) -> Tuple[bool, Optional[Offer], Optional[str]]:
"""
Offer is dictionary of wallet ids and amount
@ -586,7 +586,7 @@ class TradeManager:
return txs
async def respond_to_offer(
self, offer: Offer, fee=uint64(0), min_coin_amount: Optional[uint128] = None
self, offer: Offer, fee=uint64(0), min_coin_amount: Optional[uint64] = None
) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
take_offer_dict: Dict[Union[bytes32, int], int] = {}
arbitrage: Dict[Optional[bytes32], int] = offer.arbitrage()

View File

@ -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.
@ -291,7 +291,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)
@ -418,7 +418,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.
@ -531,7 +531,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}")

View File

@ -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
@ -1014,3 +1015,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]

View File

@ -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