Skip to content

Commit 7677d00

Browse files
committed
Port get_spendable_coins
1 parent 4a97361 commit 7677d00

File tree

6 files changed

+105
-122
lines changed

6 files changed

+105
-122
lines changed

chia/_tests/cmds/cmd_test_utils.py

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
2323
from chia.rpc.rpc_client import RpcClient
2424
from chia.simulator.simulator_full_node_rpc_client import SimulatorFullNodeRpcClient
25-
from chia.types.coin_record import CoinRecord
2625
from chia.types.signing_mode import SigningMode
2726
from chia.util.bech32m import encode_puzzle_hash
2827
from chia.util.config import load_config
@@ -31,7 +30,7 @@
3130
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
3231
from chia.wallet.transaction_record import TransactionRecord
3332
from chia.wallet.util.transaction_type import TransactionType
34-
from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig
33+
from chia.wallet.util.tx_config import TXConfig
3534
from chia.wallet.util.wallet_types import WalletType
3635
from chia.wallet.wallet_request_types import (
3736
GetSyncStatusResponse,
@@ -232,46 +231,6 @@ async def nft_calculate_royalties(
232231
)
233232
)
234233

235-
async def get_spendable_coins(
236-
self,
237-
wallet_id: int,
238-
coin_selection_config: CoinSelectionConfig,
239-
) -> tuple[list[CoinRecord], list[CoinRecord], list[Coin]]:
240-
"""
241-
We return a tuple containing: (confirmed records, unconfirmed removals, unconfirmed additions)
242-
"""
243-
self.add_to_log(
244-
"get_spendable_coins",
245-
(wallet_id, coin_selection_config),
246-
)
247-
confirmed_records = [
248-
CoinRecord(
249-
Coin(bytes32([1] * 32), bytes32([2] * 32), uint64(1234560000)),
250-
uint32(123456),
251-
uint32(0),
252-
False,
253-
uint64(0),
254-
),
255-
CoinRecord(
256-
Coin(bytes32([3] * 32), bytes32([4] * 32), uint64(1234560000)),
257-
uint32(123456),
258-
uint32(0),
259-
False,
260-
uint64(0),
261-
),
262-
]
263-
unconfirmed_removals = [
264-
CoinRecord(
265-
Coin(bytes32([5] * 32), bytes32([6] * 32), uint64(1234570000)),
266-
uint32(123457),
267-
uint32(0),
268-
True,
269-
uint64(0),
270-
)
271-
]
272-
unconfirmed_additions = [Coin(bytes32([7] * 32), bytes32([8] * 32), uint64(1234580000))]
273-
return confirmed_records, unconfirmed_removals, unconfirmed_additions
274-
275234
async def send_transaction_multi(
276235
self,
277236
wallet_id: int,

chia/_tests/wallet/rpc/test_wallet_rpc.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
GetNextAddress,
125125
GetNotifications,
126126
GetPrivateKey,
127+
GetSpendableCoins,
127128
GetSyncStatusResponse,
128129
GetTimestampForHeight,
129130
GetTransaction,
@@ -2392,27 +2393,40 @@ async def test_select_coins_rpc(wallet_rpc_environment: WalletRpcTestEnvironment
23922393
assert coin != coin_300[0]
23932394

23942395
# test get coins
2395-
all_coins, _, _ = await client_2.get_spendable_coins(
2396-
wallet_id=1,
2397-
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(
2398-
excluded_coin_ids=[c.name() for c in excluded_amt_coins_response.coins]
2396+
spendable_coins_response = await client_2.get_spendable_coins(
2397+
GetSpendableCoins.from_coin_selection_config(
2398+
wallet_id=uint32(1),
2399+
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(
2400+
excluded_coin_ids=[c.name() for c in excluded_amt_coins_response.coins]
2401+
),
23992402
),
24002403
)
2401-
assert set(excluded_amt_coins_response.coins).intersection({rec.coin for rec in all_coins}) == set()
2402-
all_coins, _, _ = await client_2.get_spendable_coins(
2403-
wallet_id=1,
2404-
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(excluded_coin_amounts=[uint64(1000)]),
2404+
assert (
2405+
set(excluded_amt_coins_response.coins).intersection(
2406+
{rec.coin for rec in spendable_coins_response.confirmed_records}
2407+
)
2408+
== set()
24052409
)
2406-
assert len([rec for rec in all_coins if rec.coin.amount == 1000]) == 0
2407-
all_coins_2, _, _ = await client_2.get_spendable_coins(
2408-
wallet_id=1,
2409-
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(max_coin_amount=uint64(999)),
2410+
spendable_coins_response = await client_2.get_spendable_coins(
2411+
GetSpendableCoins.from_coin_selection_config(
2412+
wallet_id=uint32(1),
2413+
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(excluded_coin_amounts=[uint64(1000)]),
2414+
)
2415+
)
2416+
assert len([rec for rec in spendable_coins_response.confirmed_records if rec.coin.amount == 1000]) == 0
2417+
spendable_coins_response = await client_2.get_spendable_coins(
2418+
GetSpendableCoins.from_coin_selection_config(
2419+
wallet_id=uint32(1),
2420+
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(max_coin_amount=uint64(999)),
2421+
)
24102422
)
2411-
assert all_coins_2[0].coin == coin_300[0]
2423+
assert spendable_coins_response.confirmed_records[0].coin == coin_300[0]
24122424
with pytest.raises(ValueError): # validate fail on invalid coin id.
24132425
await client_2.get_spendable_coins(
2414-
wallet_id=1,
2415-
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(excluded_coin_ids=[b"a"]),
2426+
GetSpendableCoins.from_coin_selection_config(
2427+
wallet_id=uint32(1),
2428+
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG.override(excluded_coin_ids=[b"a"]),
2429+
)
24162430
)
24172431

24182432

chia/cmds/coin_funcs.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from chia.wallet.conditions import ConditionValidTimes
1919
from chia.wallet.transaction_record import TransactionRecord
2020
from chia.wallet.util.wallet_types import WalletType
21-
from chia.wallet.wallet_request_types import CombineCoins, SplitCoins
21+
from chia.wallet.wallet_request_types import CombineCoins, GetSpendableCoins, SplitCoins
2222

2323

2424
async def async_list(
@@ -44,23 +44,28 @@ async def async_list(
4444
if not (await client_info.client.get_sync_status()).synced:
4545
print("Wallet not synced. Please wait.")
4646
return
47-
conf_coins, unconfirmed_removals, unconfirmed_additions = await client_info.client.get_spendable_coins(
48-
wallet_id=wallet_id,
49-
coin_selection_config=CMDCoinSelectionConfigLoader(
50-
max_coin_amount=max_coin_amount,
51-
min_coin_amount=min_coin_amount,
52-
excluded_coin_amounts=list(excluded_amounts),
53-
excluded_coin_ids=list(excluded_coin_ids),
54-
).to_coin_selection_config(mojo_per_unit),
47+
response = await client_info.client.get_spendable_coins(
48+
GetSpendableCoins.from_coin_selection_config(
49+
wallet_id=uint32(wallet_id),
50+
coin_selection_config=CMDCoinSelectionConfigLoader(
51+
max_coin_amount=max_coin_amount,
52+
min_coin_amount=min_coin_amount,
53+
excluded_coin_amounts=list(excluded_amounts),
54+
excluded_coin_ids=list(excluded_coin_ids),
55+
).to_coin_selection_config(mojo_per_unit),
56+
)
57+
)
58+
print(
59+
f"There are a total of {len(response.confirmed_records) + len(response.unconfirmed_additions)}"
60+
f" coins in wallet {wallet_id}."
5561
)
56-
print(f"There are a total of {len(conf_coins) + len(unconfirmed_additions)} coins in wallet {wallet_id}.")
57-
print(f"{len(conf_coins)} confirmed coins.")
58-
print(f"{len(unconfirmed_additions)} unconfirmed additions.")
59-
print(f"{len(unconfirmed_removals)} unconfirmed removals.")
62+
print(f"{len(response.confirmed_records)} confirmed coins.")
63+
print(f"{len(response.unconfirmed_additions)} unconfirmed additions.")
64+
print(f"{len(response.unconfirmed_removals)} unconfirmed removals.")
6065
print("Confirmed coins:")
6166
print_coins(
6267
"\tAddress: {} Amount: {}, Confirmed in block: {}\n",
63-
[(cr.coin, str(cr.confirmed_block_index)) for cr in conf_coins],
68+
[(cr.coin, str(cr.confirmed_block_index)) for cr in response.confirmed_records],
6469
mojo_per_unit,
6570
addr_prefix,
6671
paginate,
@@ -69,15 +74,15 @@ async def async_list(
6974
print("\nUnconfirmed Removals:")
7075
print_coins(
7176
"\tPrevious Address: {} Amount: {}, Confirmed in block: {}\n",
72-
[(cr.coin, str(cr.confirmed_block_index)) for cr in unconfirmed_removals],
77+
[(cr.coin, str(cr.confirmed_block_index)) for cr in response.unconfirmed_removals],
7378
mojo_per_unit,
7479
addr_prefix,
7580
paginate,
7681
)
7782
print("\nUnconfirmed Additions:")
7883
print_coins(
7984
"\tNew Address: {} Amount: {}, Not yet confirmed in a block.{}\n",
80-
[(coin, "") for coin in unconfirmed_additions],
85+
[(coin, "") for coin in response.unconfirmed_additions],
8186
mojo_per_unit,
8287
addr_prefix,
8388
paginate,

chia/wallet/wallet_request_types.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from chia.data_layer.singleton_record import SingletonRecord
1414
from chia.pools.pool_wallet_info import PoolWalletInfo
1515
from chia.types.blockchain_format.program import Program
16+
from chia.types.coin_record import CoinRecord
1617
from chia.util.byte_types import hexstr_to_bytes
1718
from chia.util.streamable import Streamable, streamable
1819
from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_to_json_dicts
@@ -514,6 +515,30 @@ class SelectCoinsResponse(Streamable):
514515
coins: list[Coin]
515516

516517

518+
@streamable
519+
@dataclass(frozen=True)
520+
class GetSpendableCoins(CoinSelectionConfigLoader):
521+
wallet_id: uint32 = field(default_factory=default_raise)
522+
523+
@classmethod
524+
def from_coin_selection_config(cls, wallet_id: uint32, coin_selection_config: CoinSelectionConfig) -> Self:
525+
return cls(
526+
wallet_id=wallet_id,
527+
min_coin_amount=coin_selection_config.min_coin_amount,
528+
max_coin_amount=coin_selection_config.max_coin_amount,
529+
excluded_coin_amounts=coin_selection_config.excluded_coin_amounts,
530+
excluded_coin_ids=coin_selection_config.excluded_coin_ids,
531+
)
532+
533+
534+
@streamable
535+
@dataclass(frozen=True)
536+
class GetSpendableCoinsResponse(Streamable):
537+
confirmed_records: list[CoinRecord]
538+
unconfirmed_removals: list[CoinRecord]
539+
unconfirmed_additions: list[Coin]
540+
541+
517542
@streamable
518543
@dataclass(frozen=True)
519544
class GetCurrentDerivationIndexResponse(Streamable):

chia/wallet/wallet_rpc_api.py

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@
183183
GetPrivateKeyFormat,
184184
GetPrivateKeyResponse,
185185
GetPublicKeysResponse,
186+
GetSpendableCoins,
187+
GetSpendableCoinsResponse,
186188
GetSyncStatusResponse,
187189
GetTimestampForHeight,
188190
GetTimestampForHeightResponse,
@@ -1763,43 +1765,26 @@ async def select_coins(
17631765

17641766
return SelectCoinsResponse(coins=list(selected_coins))
17651767

1766-
async def get_spendable_coins(self, request: dict[str, Any]) -> EndpointResult:
1768+
@marshal
1769+
async def get_spendable_coins(self, request: GetSpendableCoins) -> GetSpendableCoinsResponse:
17671770
if await self.service.wallet_state_manager.synced() is False:
17681771
raise ValueError("Wallet needs to be fully synced before getting all coins")
17691772

1770-
wallet_id = uint32(request["wallet_id"])
1771-
min_coin_amount = uint64(request.get("min_coin_amount", 0))
1772-
max_coin_amount: uint64 = uint64(request.get("max_coin_amount", 0))
1773-
if max_coin_amount == 0:
1774-
max_coin_amount = uint64(self.service.wallet_state_manager.constants.MAX_COIN_AMOUNT)
1775-
excluded_coin_amounts: Optional[list[uint64]] = request.get("excluded_coin_amounts")
1776-
if excluded_coin_amounts is not None:
1777-
excluded_coin_amounts = [uint64(a) for a in excluded_coin_amounts]
1778-
else:
1779-
excluded_coin_amounts = []
1780-
excluded_coins_input: Optional[dict[str, dict[str, Any]]] = request.get("excluded_coins")
1781-
if excluded_coins_input is not None:
1782-
excluded_coins = [Coin.from_json_dict(json_coin) for json_coin in excluded_coins_input.values()]
1783-
else:
1784-
excluded_coins = []
1785-
excluded_coin_ids_input: Optional[list[str]] = request.get("excluded_coin_ids")
1786-
if excluded_coin_ids_input is not None:
1787-
excluded_coin_ids = [bytes32.from_hexstr(hex_id) for hex_id in excluded_coin_ids_input]
1788-
else:
1789-
excluded_coin_ids = []
17901773
state_mgr = self.service.wallet_state_manager
1791-
wallet = state_mgr.wallets[wallet_id]
1774+
wallet = state_mgr.wallets[request.wallet_id]
17921775
async with state_mgr.lock:
1793-
all_coin_records = await state_mgr.coin_store.get_unspent_coins_for_wallet(wallet_id)
1776+
all_coin_records = await state_mgr.coin_store.get_unspent_coins_for_wallet(request.wallet_id)
17941777
if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
17951778
assert isinstance(wallet, CATWallet)
17961779
spendable_coins: list[WalletCoinRecord] = await wallet.get_cat_spendable_coins(all_coin_records)
17971780
else:
1798-
spendable_coins = list(await state_mgr.get_spendable_coins_for_wallet(wallet_id, all_coin_records))
1781+
spendable_coins = list(
1782+
await state_mgr.get_spendable_coins_for_wallet(request.wallet_id, all_coin_records)
1783+
)
17991784

18001785
# Now we get the unconfirmed transactions and manually derive the additions and removals.
18011786
unconfirmed_transactions: list[TransactionRecord] = await state_mgr.tx_store.get_unconfirmed_for_wallet(
1802-
wallet_id
1787+
request.wallet_id
18031788
)
18041789
unconfirmed_removal_ids: dict[bytes32, uint64] = {
18051790
coin.name(): transaction.created_at_time
@@ -1810,7 +1795,7 @@ async def get_spendable_coins(self, request: dict[str, Any]) -> EndpointResult:
18101795
coin
18111796
for transaction in unconfirmed_transactions
18121797
for coin in transaction.additions
1813-
if await state_mgr.does_coin_belong_to_wallet(coin, wallet_id)
1798+
if await state_mgr.does_coin_belong_to_wallet(coin, request.wallet_id)
18141799
]
18151800
valid_spendable_cr: list[CoinRecord] = []
18161801
unconfirmed_removals: list[CoinRecord] = []
@@ -1820,23 +1805,26 @@ async def get_spendable_coins(self, request: dict[str, Any]) -> EndpointResult:
18201805
for coin_record in spendable_coins: # remove all the unconfirmed coins, exclude coins and dust.
18211806
if coin_record.name() in unconfirmed_removal_ids:
18221807
continue
1823-
if coin_record.coin in excluded_coins:
1824-
continue
1825-
if coin_record.name() in excluded_coin_ids:
1808+
if request.excluded_coin_ids is not None and coin_record.coin.name() in request.excluded_coin_ids:
18261809
continue
1827-
if coin_record.coin.amount < min_coin_amount or coin_record.coin.amount > max_coin_amount:
1810+
if (request.min_coin_amount is not None and coin_record.coin.amount < request.min_coin_amount) or (
1811+
request.max_coin_amount is not None and coin_record.coin.amount > request.max_coin_amount
1812+
):
18281813
continue
1829-
if coin_record.coin.amount in excluded_coin_amounts:
1814+
if (
1815+
request.excluded_coin_amounts is not None
1816+
and coin_record.coin.amount in request.excluded_coin_amounts
1817+
):
18301818
continue
18311819
c_r = await state_mgr.get_coin_record_by_wallet_record(coin_record)
18321820
assert c_r is not None and c_r.coin == coin_record.coin # this should never happen
18331821
valid_spendable_cr.append(c_r)
18341822

1835-
return {
1836-
"confirmed_records": [cr.to_json_dict() for cr in valid_spendable_cr],
1837-
"unconfirmed_removals": [cr.to_json_dict() for cr in unconfirmed_removals],
1838-
"unconfirmed_additions": [coin.to_json_dict() for coin in unconfirmed_additions],
1839-
}
1823+
return GetSpendableCoinsResponse(
1824+
confirmed_records=valid_spendable_cr,
1825+
unconfirmed_removals=unconfirmed_removals,
1826+
unconfirmed_additions=unconfirmed_additions,
1827+
)
18401828

18411829
async def get_coin_records_by_names(self, request: dict[str, Any]) -> EndpointResult:
18421830
if await self.service.wallet_state_manager.synced() is False:

chia/wallet/wallet_rpc_client.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from chia.wallet.trading.offer import Offer
1717
from chia.wallet.transaction_record import TransactionRecord
1818
from chia.wallet.util.clvm_streamable import json_deserialize_with_clvm_streamable
19-
from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig
19+
from chia.wallet.util.tx_config import TXConfig
2020
from chia.wallet.wallet_coin_store import GetCoinRecords
2121
from chia.wallet.wallet_request_types import (
2222
AddKey,
@@ -99,6 +99,8 @@
9999
GetPrivateKey,
100100
GetPrivateKeyResponse,
101101
GetPublicKeysResponse,
102+
GetSpendableCoins,
103+
GetSpendableCoinsResponse,
102104
GetSyncStatusResponse,
103105
GetTimestampForHeight,
104106
GetTimestampForHeightResponse,
@@ -413,18 +415,8 @@ async def select_coins(self, request: SelectCoins) -> SelectCoinsResponse:
413415
async def get_coin_records(self, request: GetCoinRecords) -> dict[str, Any]:
414416
return await self.fetch("get_coin_records", request.to_json_dict())
415417

416-
async def get_spendable_coins(
417-
self, wallet_id: int, coin_selection_config: CoinSelectionConfig
418-
) -> tuple[list[CoinRecord], list[CoinRecord], list[Coin]]:
419-
"""
420-
We return a tuple containing: (confirmed records, unconfirmed removals, unconfirmed additions)
421-
"""
422-
request = {"wallet_id": wallet_id, **coin_selection_config.to_json_dict()}
423-
response = await self.fetch("get_spendable_coins", request)
424-
confirmed_wrs = [CoinRecord.from_json_dict(coin) for coin in response["confirmed_records"]]
425-
unconfirmed_removals = [CoinRecord.from_json_dict(coin) for coin in response["unconfirmed_removals"]]
426-
unconfirmed_additions = [Coin.from_json_dict(coin) for coin in response["unconfirmed_additions"]]
427-
return confirmed_wrs, unconfirmed_removals, unconfirmed_additions
418+
async def get_spendable_coins(self, request: GetSpendableCoins) -> GetSpendableCoinsResponse:
419+
return GetSpendableCoinsResponse.from_json_dict(await self.fetch("get_spendable_coins", request.to_json_dict()))
428420

429421
async def get_coin_records_by_names(
430422
self,

0 commit comments

Comments
 (0)