Skip to content

Commit ecb5148

Browse files
authored
[CHIA-3601] Port spend_clawback_coins to @marshal (#19965)
* Port `spend_clawback_coins` to `@marshal` * fix tests * fix test again * how do I keep missing these?
1 parent 11696e4 commit ecb5148

File tree

7 files changed

+97
-92
lines changed

7 files changed

+97
-92
lines changed

chia/_tests/cmds/wallet/test_wallet.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
RoyaltyAsset,
6868
SendTransaction,
6969
SendTransactionResponse,
70+
SpendClawbackCoins,
71+
SpendClawbackCoinsResponse,
7072
TakeOfferResponse,
7173
TransactionRecordWithMetadata,
7274
WalletInfoResponse,
@@ -539,23 +541,25 @@ def test_clawback(capsys: object, get_test_cli_clients: tuple[TestRpcClients, Pa
539541
class ClawbackWalletRpcClient(TestWalletRpcClient):
540542
async def spend_clawback_coins(
541543
self,
542-
coin_ids: list[bytes32],
543-
fee: int = 0,
544-
force: bool = False,
545-
push: bool = True,
544+
request: SpendClawbackCoins,
545+
tx_config: TXConfig,
546+
extra_conditions: tuple[Condition, ...] = tuple(),
546547
timelock_info: ConditionValidTimes = ConditionValidTimes(),
547-
) -> dict[str, Any]:
548-
self.add_to_log("spend_clawback_coins", (coin_ids, fee, force, push, timelock_info))
549-
tx_hex_list = [get_bytes32(6).hex(), get_bytes32(7).hex(), get_bytes32(8).hex()]
550-
return {
551-
"transaction_ids": tx_hex_list,
552-
"transactions": [STD_TX.to_json_dict()],
553-
}
548+
) -> SpendClawbackCoinsResponse:
549+
self.add_to_log(
550+
"spend_clawback_coins", (request.coin_ids, request.fee, request.force, request.push, timelock_info)
551+
)
552+
tx_list = [get_bytes32(6), get_bytes32(7), get_bytes32(8)]
553+
return SpendClawbackCoinsResponse(
554+
transaction_ids=tx_list,
555+
transactions=[STD_TX],
556+
unsigned_transactions=[STD_UTX],
557+
)
554558

555559
inst_rpc_client = ClawbackWalletRpcClient()
556560
test_rpc_clients.wallet_rpc_client = inst_rpc_client
557561
tx_ids = [get_bytes32(3), get_bytes32(4), get_bytes32(5)]
558-
r_tx_ids_hex = [get_bytes32(6).hex(), get_bytes32(7).hex(), get_bytes32(8).hex()]
562+
r_tx_ids_hex = ["0x" + get_bytes32(6).hex(), "0x" + get_bytes32(7).hex(), "0x" + get_bytes32(8).hex()]
559563
command_args = [
560564
"wallet",
561565
"clawback",
@@ -569,7 +573,7 @@ async def spend_clawback_coins(
569573
"--expires-at",
570574
"150",
571575
]
572-
run_cli_command_and_assert(capsys, root_dir, command_args, ["transaction_ids", str(r_tx_ids_hex)])
576+
run_cli_command_and_assert(capsys, root_dir, command_args, ["transaction_ids", *r_tx_ids_hex])
573577
# these are various things that should be in the output
574578
expected_calls: logType = {
575579
"spend_clawback_coins": [(tx_ids, 500000000000, False, True, test_condition_valid_times)],

chia/_tests/wallet/rpc/test_wallet_rpc.py

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
RoyaltyAsset,
144144
SendTransaction,
145145
SetWalletResyncOnStartup,
146+
SpendClawbackCoins,
146147
SplitCoins,
147148
VerifySignature,
148149
VerifySignatureResponse,
@@ -833,7 +834,6 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron
833834
wallet_1 = wallet_1_node.wallet_state_manager.main_wallet
834835
wallet_2 = wallet_2_node.wallet_state_manager.main_wallet
835836
full_node_api: FullNodeSimulator = env.full_node.api
836-
wallet_2_api = WalletRpcApi(wallet_2_node)
837837

838838
generated_funds = await generate_funds(full_node_api, env.wallet_1, 1)
839839
await generate_funds(full_node_api, env.wallet_2, 1)
@@ -876,41 +876,39 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron
876876
await time_out_assert(20, get_confirmed_balance, generated_funds - 500, wallet_1_rpc, 1)
877877
await time_out_assert(20, get_confirmed_balance, generated_funds - 500, wallet_2_rpc, 1)
878878
await asyncio.sleep(10)
879-
# Test missing coin_ids
880-
has_exception = False
881-
try:
882-
await wallet_2_api.spend_clawback_coins({})
883-
except ValueError:
884-
has_exception = True
885-
assert has_exception
886879
# Test coin ID is not a Clawback coin
887880
invalid_coin_id = tx.removals[0].name()
888-
resp = await wallet_2_rpc.spend_clawback_coins([invalid_coin_id], 500)
889-
assert resp["success"]
890-
assert resp["transaction_ids"] == []
881+
resp = await wallet_2_rpc.spend_clawback_coins(
882+
SpendClawbackCoins(coin_ids=[invalid_coin_id], fee=uint64(500), push=True), tx_config=DEFAULT_TX_CONFIG
883+
)
884+
assert resp.transaction_ids == []
891885
# Test unsupported wallet
892886
coin_record = await wallet_1_node.wallet_state_manager.coin_store.get_coin_record(clawback_coin_id_1)
893887
assert coin_record is not None
894888
await wallet_1_node.wallet_state_manager.coin_store.add_coin_record(
895889
dataclasses.replace(coin_record, wallet_type=WalletType.CAT)
896890
)
897-
resp = await wallet_1_rpc.spend_clawback_coins([clawback_coin_id_1], 100)
898-
assert resp["success"]
899-
assert len(resp["transaction_ids"]) == 0
891+
resp = await wallet_1_rpc.spend_clawback_coins(
892+
SpendClawbackCoins(coin_ids=[clawback_coin_id_1], fee=uint64(100), push=True), tx_config=DEFAULT_TX_CONFIG
893+
)
894+
assert len(resp.transaction_ids) == 0
900895
# Test missing metadata
901896
await wallet_1_node.wallet_state_manager.coin_store.add_coin_record(dataclasses.replace(coin_record, metadata=None))
902-
resp = await wallet_1_rpc.spend_clawback_coins([clawback_coin_id_1], 100)
903-
assert resp["success"]
904-
assert len(resp["transaction_ids"]) == 0
897+
resp = await wallet_1_rpc.spend_clawback_coins(
898+
SpendClawbackCoins(coin_ids=[clawback_coin_id_1], fee=uint64(100), push=True), tx_config=DEFAULT_TX_CONFIG
899+
)
900+
assert len(resp.transaction_ids) == 0
905901
# Test missing incoming tx
906902
coin_record = await wallet_1_node.wallet_state_manager.coin_store.get_coin_record(clawback_coin_id_2)
907903
assert coin_record is not None
908904
fake_coin = Coin(coin_record.coin.parent_coin_info, wallet_2_puzhash, coin_record.coin.amount)
909905
await wallet_1_node.wallet_state_manager.coin_store.add_coin_record(
910906
dataclasses.replace(coin_record, coin=fake_coin)
911907
)
912-
resp = await wallet_1_rpc.spend_clawback_coins([fake_coin.name()], 100)
913-
assert resp["transaction_ids"] == []
908+
resp = await wallet_1_rpc.spend_clawback_coins(
909+
SpendClawbackCoins(coin_ids=[fake_coin.name()], fee=uint64(100), push=True), tx_config=DEFAULT_TX_CONFIG
910+
)
911+
assert resp.transaction_ids == []
914912
# Test coin puzzle hash doesn't match the puzzle
915913
farmed_tx = (await wallet_1.wallet_state_manager.tx_store.get_farming_rewards())[0]
916914
await wallet_1.wallet_state_manager.tx_store.add_transaction_record(
@@ -919,8 +917,10 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron
919917
await wallet_1_node.wallet_state_manager.coin_store.add_coin_record(
920918
dataclasses.replace(coin_record, coin=fake_coin)
921919
)
922-
resp = await wallet_1_rpc.spend_clawback_coins([fake_coin.name()], 100)
923-
assert resp["transaction_ids"] == []
920+
resp = await wallet_1_rpc.spend_clawback_coins(
921+
SpendClawbackCoins(coin_ids=[fake_coin.name()], fee=uint64(100), push=True), tx_config=DEFAULT_TX_CONFIG
922+
)
923+
assert resp.transaction_ids == []
924924
# Test claim spend
925925
await wallet_2_rpc.set_auto_claim(
926926
AutoClaimSettings(
@@ -930,21 +930,23 @@ async def test_spend_clawback_coins(wallet_rpc_environment: WalletRpcTestEnviron
930930
batch_size=uint16(1),
931931
)
932932
)
933-
resp = await wallet_2_rpc.spend_clawback_coins([clawback_coin_id_1, clawback_coin_id_2], 100)
934-
assert resp["success"]
935-
assert len(resp["transaction_ids"]) == 2
936-
for _tx in resp["transactions"]:
937-
clawback_tx = TransactionRecord.from_json_dict(_tx)
933+
resp = await wallet_2_rpc.spend_clawback_coins(
934+
SpendClawbackCoins(coin_ids=[clawback_coin_id_1, clawback_coin_id_2], fee=uint64(100), push=True),
935+
tx_config=DEFAULT_TX_CONFIG,
936+
)
937+
assert len(resp.transaction_ids) == 2
938+
for clawback_tx in resp.transactions:
938939
if clawback_tx.spend_bundle is not None:
939940
await time_out_assert_not_none(
940941
10, full_node_api.full_node.mempool_manager.get_spendbundle, clawback_tx.spend_bundle.name()
941942
)
942943
await farm_transaction_block(full_node_api, wallet_2_node)
943944
await time_out_assert(20, get_confirmed_balance, generated_funds + 300, wallet_2_rpc, 1)
944945
# Test spent coin
945-
resp = await wallet_2_rpc.spend_clawback_coins([clawback_coin_id_1], 500)
946-
assert resp["success"]
947-
assert resp["transaction_ids"] == []
946+
resp = await wallet_2_rpc.spend_clawback_coins(
947+
SpendClawbackCoins(coin_ids=[clawback_coin_id_1], fee=uint64(500), push=True), tx_config=DEFAULT_TX_CONFIG
948+
)
949+
assert resp.transaction_ids == []
948950

949951

950952
@pytest.mark.anyio
@@ -2861,12 +2863,11 @@ async def test_set_wallet_resync_on_startup(wallet_rpc_environment: WalletRpcTes
28612863
await farm_transaction(full_node_api, wallet_node, tx.spend_bundle)
28622864
await time_out_assert(20, check_client_synced, True, wc)
28632865
await asyncio.sleep(10)
2864-
resp = await wc.spend_clawback_coins([clawback_coin_id], 0)
2865-
assert resp["success"]
2866-
assert len(resp["transaction_ids"]) == 1
2867-
await time_out_assert_not_none(
2868-
10, full_node_api.full_node.mempool_manager.get_spendbundle, bytes32.from_hexstr(resp["transaction_ids"][0])
2866+
resp = await wc.spend_clawback_coins(
2867+
SpendClawbackCoins(coin_ids=[clawback_coin_id], fee=uint64(0), push=True), tx_config=DEFAULT_TX_CONFIG
28692868
)
2869+
assert len(resp.transaction_ids) == 1
2870+
await time_out_assert_not_none(10, full_node_api.full_node.mempool_manager.get_spendbundle, resp.transaction_ids[0])
28702871
await farm_transaction_block(full_node_api, wallet_node)
28712872
await time_out_assert(20, check_client_synced, True, wc)
28722873
wallet_node_2._close()

chia/_tests/wallet/test_wallet.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,6 @@ async def test_wallet_clawback_clawback(self, wallet_environments: WalletTestFra
397397
assert not txs["transactions"][0]["confirmed"]
398398
assert txs["transactions"][0]["metadata"]["recipient_puzzle_hash"][2:] == normal_puzhash.hex()
399399
assert txs["transactions"][0]["metadata"]["coin_id"] == "0x" + merkle_coin.name().hex()
400-
with pytest.raises(ValueError):
401-
await api_0.spend_clawback_coins({})
402400

403401
test_fee = 10
404402
resp = await api_0.spend_clawback_coins(
@@ -408,7 +406,6 @@ async def test_wallet_clawback_clawback(self, wallet_environments: WalletTestFra
408406
**wallet_environments.tx_config.to_json_dict(),
409407
}
410408
)
411-
assert resp["success"]
412409
assert len(resp["transaction_ids"]) == 1
413410

414411
await wallet_environments.process_pending_states(
@@ -541,7 +538,6 @@ async def test_wallet_clawback_sent_self(self, wallet_environments: WalletTestFr
541538
**wallet_environments.tx_config.to_json_dict(),
542539
}
543540
)
544-
assert resp["success"]
545541
assert len(resp["transaction_ids"]) == 1
546542
# Wait mempool update
547543
await wallet_environments.process_pending_states(
@@ -678,7 +674,6 @@ async def test_wallet_clawback_claim_manual(self, wallet_environments: WalletTes
678674
**wallet_environments.tx_config.to_json_dict(),
679675
}
680676
)
681-
assert resp["success"]
682677
assert len(resp["transaction_ids"]) == 1
683678

684679
await wallet_environments.process_pending_states(
@@ -1094,10 +1089,8 @@ async def test_clawback_resync(self, self_hostname: str, wallet_environments: Wa
10941089
await time_out_assert(20, wsm_2.coin_store.count_small_unspent, 1, 1000, CoinType.CLAWBACK)
10951090
# clawback merkle coin
10961091
resp = await api_1.spend_clawback_coins({"coin_ids": [clawback_coin_id_1.hex()], "fee": 0})
1097-
assert resp["success"]
10981092
assert len(resp["transaction_ids"]) == 1
10991093
resp = await api_1.spend_clawback_coins({"coin_ids": [clawback_coin_id_2.hex()], "fee": 0})
1100-
assert resp["success"]
11011094
assert len(resp["transaction_ids"]) == 1
11021095

11031096
await wallet_environments.process_pending_states(

chia/cmds/wallet_funcs.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from chia.wallet.util.puzzle_decorator_type import PuzzleDecoratorType
4141
from chia.wallet.util.query_filter import HashFilter, TransactionTypeFilter
4242
from chia.wallet.util.transaction_type import CLAWBACK_INCOMING_TRANSACTION_TYPES, TransactionType
43+
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
4344
from chia.wallet.util.wallet_types import WalletType
4445
from chia.wallet.vc_wallet.vc_store import VCProofs
4546
from chia.wallet.wallet_coin_store import GetCoinRecords
@@ -79,6 +80,7 @@
7980
SignMessageByAddressResponse,
8081
SignMessageByID,
8182
SignMessageByIDResponse,
83+
SpendClawbackCoins,
8284
VCAddProofs,
8385
VCGet,
8486
VCGetList,
@@ -1684,14 +1686,12 @@ async def spend_clawback(
16841686
print("Batch fee cannot be negative.")
16851687
return []
16861688
response = await wallet_client.spend_clawback_coins(
1687-
tx_ids,
1688-
fee,
1689-
force,
1690-
push=push,
1689+
SpendClawbackCoins(coin_ids=tx_ids, fee=fee, force=force, push=push),
1690+
tx_config=DEFAULT_TX_CONFIG,
16911691
timelock_info=condition_valid_times,
16921692
)
16931693
print(str(response))
1694-
return [TransactionRecord.from_json_dict(tx) for tx in response["transactions"]]
1694+
return response.transactions
16951695

16961696

16971697
async def mint_vc(

chia/wallet/wallet_request_types.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,20 @@ class SendTransactionResponse(TransactionEndpointResponse):
11461146
transaction_id: bytes32
11471147

11481148

1149+
@streamable
1150+
@dataclass(frozen=True)
1151+
class SpendClawbackCoins(TransactionEndpointRequest):
1152+
coin_ids: list[bytes32] = field(default_factory=default_raise)
1153+
batch_size: Optional[uint16] = None
1154+
force: bool = False
1155+
1156+
1157+
@streamable
1158+
@dataclass(frozen=True)
1159+
class SpendClawbackCoinsResponse(TransactionEndpointResponse):
1160+
transaction_ids: list[bytes32]
1161+
1162+
11491163
@streamable
11501164
@dataclass(frozen=True)
11511165
class PushTransactions(TransactionEndpointRequest):

chia/wallet/wallet_rpc_api.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@
249249
SignMessageByAddressResponse,
250250
SignMessageByID,
251251
SignMessageByIDResponse,
252+
SpendClawbackCoins,
253+
SpendClawbackCoinsResponse,
252254
SplitCoins,
253255
SplitCoinsResponse,
254256
SubmitTransactions,
@@ -1655,34 +1657,33 @@ async def send_transaction_multi(self, request: dict[str, Any]) -> EndpointResul
16551657
}
16561658

16571659
@tx_endpoint(push=True, merge_spends=False)
1660+
@marshal
16581661
async def spend_clawback_coins(
16591662
self,
1660-
request: dict[str, Any],
1663+
request: SpendClawbackCoins,
16611664
action_scope: WalletActionScope,
16621665
extra_conditions: tuple[Condition, ...] = tuple(),
1663-
) -> EndpointResult:
1666+
) -> SpendClawbackCoinsResponse:
16641667
"""Spend clawback coins that were sent (to claw them back) or received (to claim them).
16651668
16661669
:param coin_ids: list of coin ids to be spent
16671670
:param batch_size: number of coins to spend per bundle
16681671
:param fee: transaction fee in mojos
16691672
:return:
16701673
"""
1671-
if "coin_ids" not in request:
1672-
raise ValueError("Coin IDs are required.")
1673-
coin_ids: list[bytes32] = [bytes32.from_hexstr(coin) for coin in request["coin_ids"]]
1674-
tx_fee: uint64 = uint64(request.get("fee", 0))
16751674
# Get inner puzzle
16761675
coin_records = await self.service.wallet_state_manager.coin_store.get_coin_records(
1677-
coin_id_filter=HashFilter.include(coin_ids),
1676+
coin_id_filter=HashFilter.include(request.coin_ids),
16781677
coin_type=CoinType.CLAWBACK,
16791678
wallet_type=WalletType.STANDARD_WALLET,
16801679
spent_range=UInt32Range(stop=uint32(0)),
16811680
)
16821681

16831682
coins: dict[Coin, ClawbackMetadata] = {}
1684-
batch_size = request.get(
1685-
"batch_size", self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50)
1683+
batch_size = (
1684+
request.batch_size
1685+
if request.batch_size is not None
1686+
else self.service.wallet_state_manager.config.get("auto_claim", {}).get("batch_size", 50)
16861687
)
16871688
for coin_id, coin_record in coin_records.coin_id_to_record.items():
16881689
try:
@@ -1692,9 +1693,9 @@ async def spend_clawback_coins(
16921693
if len(coins) >= batch_size:
16931694
await self.service.wallet_state_manager.spend_clawback_coins(
16941695
coins,
1695-
tx_fee,
1696+
request.fee,
16961697
action_scope,
1697-
request.get("force", False),
1698+
request.force,
16981699
extra_conditions=extra_conditions,
16991700
)
17001701
coins = {}
@@ -1703,17 +1704,14 @@ async def spend_clawback_coins(
17031704
if len(coins) > 0:
17041705
await self.service.wallet_state_manager.spend_clawback_coins(
17051706
coins,
1706-
tx_fee,
1707+
request.fee,
17071708
action_scope,
1708-
request.get("force", False),
1709+
request.force,
17091710
extra_conditions=extra_conditions,
17101711
)
17111712

1712-
return {
1713-
"success": True,
1714-
"transaction_ids": None, # tx_endpoint wrapper will take care of this
1715-
"transactions": None, # tx_endpoint wrapper will take care of this
1716-
}
1713+
# tx_endpoint will fill in the default values here
1714+
return SpendClawbackCoinsResponse([], [], transaction_ids=[])
17171715

17181716
@marshal
17191717
async def delete_unconfirmed_transactions(self, request: DeleteUnconfirmedTransactions) -> Empty:

0 commit comments

Comments
 (0)