Skip to content

Commit cdd19fb

Browse files
authored
[LABS-301] Extract split_coins and combine_coins RPCs to WalletStateManager (#20290)
* Extract `split_coins` to `WalletStateManager` * Extract `combine_coins` * < 2
1 parent be744ca commit cdd19fb

File tree

4 files changed

+190
-149
lines changed

4 files changed

+190
-149
lines changed

chia/_tests/wallet/rpc/test_wallet_rpc.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3591,9 +3591,12 @@ async def test_split_coins(wallet_environments: WalletTestFramework, capsys: pyt
35913591
}
35923592
)
35933593

3594-
with pytest.raises(ResponseFailureError, match="501 coins is greater then the maximum limit of 500 coins"):
3594+
with pytest.raises(ValueError, match="501 coins is greater then the maximum limit of 500 coins"):
35953595
await dataclasses.replace(xch_request, number_of_coins=501).run()
35963596

3597+
with pytest.raises(ValueError, match="Cannot split into 0 new coins"):
3598+
await dataclasses.replace(xch_request, number_of_coins=0).run()
3599+
35973600
with pytest.raises(ResponseFailureError, match="Could not find coin with ID 00000000000000000"):
35983601
await dataclasses.replace(xch_request, target_coin_id=bytes32.zeros).run()
35993602

@@ -3624,10 +3627,6 @@ async def test_split_coins(wallet_environments: WalletTestFramework, capsys: pyt
36243627

36253628
del env.wallet_state_manager.wallets[uint32(42)]
36263629

3627-
await dataclasses.replace(xch_request, number_of_coins=0).run()
3628-
output = (capsys.readouterr()).out
3629-
assert "Transaction sent" not in output
3630-
36313630
with wallet_environments.new_puzzle_hashes_allowed():
36323631
await xch_request.run()
36333632

@@ -3787,13 +3786,13 @@ async def test_combine_coins(wallet_environments: WalletTestFramework, capsys: p
37873786
)
37883787

37893788
# Test some error cases first
3790-
with pytest.raises(ResponseFailureError, match="greater then the maximum limit"):
3789+
with pytest.raises(ValueError, match="greater then the maximum limit"):
37913790
await dataclasses.replace(xch_combine_request, number_of_coins=uint16(501)).run()
37923791

3793-
with pytest.raises(ResponseFailureError, match="You need at least two coins to combine"):
3792+
with pytest.raises(ValueError, match="You need at least two coins to combine"):
37943793
await dataclasses.replace(xch_combine_request, number_of_coins=uint16(0)).run()
37953794

3796-
with pytest.raises(ResponseFailureError, match="More coin IDs specified than desired number of coins to combine"):
3795+
with pytest.raises(ValueError, match="More coin IDs specified than desired number of coins to combine"):
37973796
await dataclasses.replace(xch_combine_request, input_coins=(bytes32.zeros,) * 100).run()
37983797

37993798
# We catch this one

chia/wallet/wallet_request_types.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,12 @@ class SplitCoins(TransactionEndpointRequest):
14021402
amount_per_coin: uint64 = field(default_factory=default_raise)
14031403
target_coin_id: bytes32 = field(default_factory=default_raise)
14041404

1405+
def __post_init__(self) -> None:
1406+
if self.number_of_coins > 500:
1407+
raise ValueError(f"{self.number_of_coins} coins is greater then the maximum limit of 500 coins.")
1408+
if self.number_of_coins == 0:
1409+
raise ValueError("Cannot split into 0 new coins")
1410+
14051411

14061412
@streamable
14071413
@dataclass(frozen=True)
@@ -1419,6 +1425,16 @@ class CombineCoins(TransactionEndpointRequest):
14191425
target_coin_amount: uint64 | None = None
14201426
coin_num_limit: uint16 = uint16(500)
14211427

1428+
def __post_init__(self) -> None:
1429+
if self.number_of_coins > self.coin_num_limit:
1430+
raise ValueError(
1431+
f"{self.number_of_coins} coins is greater then the maximum limit of {self.coin_num_limit} coins."
1432+
)
1433+
if self.number_of_coins < 2:
1434+
raise ValueError("You need at least two coins to combine")
1435+
if len(self.target_coin_ids) > self.number_of_coins:
1436+
raise ValueError("More coin IDs specified than desired number of coins to combine")
1437+
14221438

14231439
@streamable
14241440
@dataclass(frozen=True)

chia/wallet/wallet_rpc_api.py

Lines changed: 18 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from chia_rs import AugSchemeMPL, Coin, CoinSpend, CoinState, G1Element, G2Element, PrivateKey
1111
from chia_rs.sized_bytes import bytes32
12-
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
12+
from chia_rs.sized_ints import uint16, uint32, uint64
1313
from clvm_tools.binutils import assemble
1414

1515
from chia.consensus.block_rewards import calculate_base_farmer_reward
@@ -89,7 +89,7 @@
8989
from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
9090
from chia.wallet.util.compute_memos import compute_memos
9191
from chia.wallet.util.curry_and_treehash import NIL_TREEHASH
92-
from chia.wallet.util.query_filter import FilterMode, HashFilter
92+
from chia.wallet.util.query_filter import HashFilter
9393
from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType
9494
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG, TXConfig, TXConfigLoader
9595
from chia.wallet.util.wallet_sync_utils import fetch_coin_spend_for_coin_state
@@ -1376,57 +1376,13 @@ async def get_transaction_memo(self, request: GetTransactionMemo) -> GetTransact
13761376
async def split_coins(
13771377
self, request: SplitCoins, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple()
13781378
) -> SplitCoinsResponse:
1379-
if request.number_of_coins > 500:
1380-
raise ValueError(f"{request.number_of_coins} coins is greater then the maximum limit of 500 coins.")
1381-
1382-
optional_coin = await self.service.wallet_state_manager.coin_store.get_coin_record(request.target_coin_id)
1383-
if optional_coin is None:
1384-
raise ValueError(f"Could not find coin with ID {request.target_coin_id}")
1385-
else:
1386-
coin = optional_coin.coin
1387-
1388-
total_amount = request.amount_per_coin * request.number_of_coins
1389-
1390-
if coin.amount < total_amount:
1391-
raise ValueError(
1392-
f"Coin amount: {coin.amount} is less than the total amount of the split: {total_amount}, exiting."
1393-
)
1394-
1395-
if request.wallet_id not in self.service.wallet_state_manager.wallets:
1396-
raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1397-
wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1398-
if not isinstance(wallet, (Wallet, CATWallet)):
1399-
raise ValueError("Cannot split coins from non-fungible wallet types")
1400-
1401-
outputs = [
1402-
CreateCoin(
1403-
await action_scope.get_puzzle_hash(
1404-
self.service.wallet_state_manager, override_reuse_puzhash_with=False
1405-
),
1406-
request.amount_per_coin,
1407-
)
1408-
for _ in range(request.number_of_coins)
1409-
]
1410-
if len(outputs) == 0:
1411-
return SplitCoinsResponse([], [])
1412-
1413-
if wallet.type() == WalletType.STANDARD_WALLET and coin.amount < total_amount + request.fee:
1414-
async with action_scope.use() as interface:
1415-
interface.side_effects.selected_coins.append(coin)
1416-
coins = await wallet.select_coins(
1417-
uint64(total_amount + request.fee - coin.amount),
1418-
action_scope,
1419-
)
1420-
coins.add(coin)
1421-
else:
1422-
coins = {coin}
1423-
1424-
await wallet.generate_signed_transaction(
1425-
[output.amount for output in outputs],
1426-
[output.puzzle_hash for output in outputs],
1427-
action_scope,
1428-
request.fee,
1429-
coins=coins,
1379+
await self.service.wallet_state_manager.split_coins(
1380+
action_scope=action_scope,
1381+
wallet_id=request.wallet_id,
1382+
target_coin_id=request.target_coin_id,
1383+
amount_per_coin=request.amount_per_coin,
1384+
number_of_coins=request.number_of_coins,
1385+
fee=request.fee,
14301386
extra_conditions=extra_conditions,
14311387
)
14321388

@@ -1437,93 +1393,17 @@ async def split_coins(
14371393
async def combine_coins(
14381394
self, request: CombineCoins, action_scope: WalletActionScope, extra_conditions: tuple[Condition, ...] = tuple()
14391395
) -> CombineCoinsResponse:
1440-
# Some "number of coins" validation
1441-
if request.number_of_coins > request.coin_num_limit:
1442-
raise ValueError(
1443-
f"{request.number_of_coins} coins is greater then the maximum limit of {request.coin_num_limit} coins."
1444-
)
1445-
if request.number_of_coins < 1:
1446-
raise ValueError("You need at least two coins to combine")
1447-
if len(request.target_coin_ids) > request.number_of_coins:
1448-
raise ValueError("More coin IDs specified than desired number of coins to combine")
1449-
1450-
if request.wallet_id not in self.service.wallet_state_manager.wallets:
1451-
raise ValueError(f"Wallet with ID {request.wallet_id} does not exist")
1452-
wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
1453-
if not isinstance(wallet, (Wallet, CATWallet)):
1454-
raise ValueError("Cannot combine coins from non-fungible wallet types")
1455-
1456-
coins: list[Coin] = []
1457-
1458-
# First get the coin IDs specified
1459-
if request.target_coin_ids != []:
1460-
coins.extend(
1461-
cr.coin
1462-
for cr in (
1463-
await self.service.wallet_state_manager.coin_store.get_coin_records(
1464-
wallet_id=request.wallet_id,
1465-
coin_id_filter=HashFilter(request.target_coin_ids, mode=uint8(FilterMode.include.value)),
1466-
)
1467-
).records
1468-
)
1469-
1470-
async with action_scope.use() as interface:
1471-
interface.side_effects.selected_coins.extend(coins)
1472-
1473-
# Next let's select enough coins to meet the target + fee if there is one
1474-
fungible_amount_needed = uint64(0) if request.target_coin_amount is None else request.target_coin_amount
1475-
if isinstance(wallet, Wallet):
1476-
fungible_amount_needed = uint64(fungible_amount_needed + request.fee)
1477-
amount_selected = sum(c.amount for c in coins)
1478-
if amount_selected < fungible_amount_needed: # implicit fungible_amount_needed > 0 here
1479-
coins.extend(
1480-
await wallet.select_coins(
1481-
amount=uint64(fungible_amount_needed - amount_selected), action_scope=action_scope
1482-
)
1483-
)
1484-
1485-
if len(coins) > request.number_of_coins:
1486-
raise ValueError(
1487-
f"Options specified cannot be met without selecting more coins than specified: {len(coins)}"
1488-
)
1489-
1490-
# Now let's select enough coins to get to the target number to combine
1491-
if len(coins) < request.number_of_coins:
1492-
async with action_scope.use() as interface:
1493-
coins.extend(
1494-
cr.coin
1495-
for cr in (
1496-
await self.service.wallet_state_manager.coin_store.get_coin_records(
1497-
wallet_id=request.wallet_id,
1498-
limit=uint32(request.number_of_coins - len(coins)),
1499-
order=CoinRecordOrder.amount,
1500-
coin_id_filter=HashFilter(
1501-
[c.name() for c in interface.side_effects.selected_coins],
1502-
mode=uint8(FilterMode.exclude.value),
1503-
),
1504-
reverse=request.largest_first,
1505-
)
1506-
).records
1507-
)
1508-
1509-
async with action_scope.use() as interface:
1510-
interface.side_effects.selected_coins.extend(coins)
1511-
1512-
primary_output_amount = (
1513-
uint64(sum(c.amount for c in coins)) if request.target_coin_amount is None else request.target_coin_amount
1514-
)
1515-
if isinstance(wallet, Wallet):
1516-
primary_output_amount = uint64(primary_output_amount - request.fee)
1517-
1518-
await wallet.generate_signed_transaction(
1519-
[primary_output_amount],
1520-
[await action_scope.get_puzzle_hash(self.service.wallet_state_manager)],
1521-
action_scope,
1522-
request.fee,
1523-
coins=set(coins),
1396+
await self.service.wallet_state_manager.combine_coins(
1397+
action_scope=action_scope,
1398+
wallet_id=request.wallet_id,
1399+
number_of_coins=request.number_of_coins,
1400+
largest_first=request.largest_first,
1401+
coin_num_limit=request.coin_num_limit,
1402+
fee=request.fee,
1403+
target_coin_amount=request.target_coin_amount,
1404+
target_coin_ids=request.target_coin_ids if request.target_coin_ids != [] else None,
15241405
extra_conditions=extra_conditions,
15251406
)
1526-
15271407
return CombineCoinsResponse([], []) # tx_endpoint will take care to fill this out
15281408

15291409
@marshal

0 commit comments

Comments
 (0)