Skip to content

Commit b27f498

Browse files
committed
Port send_transaction_multi to @marshal
1 parent 51ec259 commit b27f498

File tree

5 files changed

+164
-115
lines changed

5 files changed

+164
-115
lines changed

chia/_tests/cmds/cmd_test_utils.py

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import chia.cmds.wallet_funcs
1515
from chia._tests.cmds.testing_classes import create_test_block_record
16-
from chia._tests.cmds.wallet.test_consts import STD_TX, STD_UTX, get_bytes32
16+
from chia._tests.cmds.wallet.test_consts import get_bytes32
1717
from chia.cmds.chia import cli as chia_cli
1818
from chia.cmds.cmds_util import _T_RpcClient, node_config_section_names
1919
from chia.consensus.default_constants import DEFAULT_CONSTANTS
@@ -30,7 +30,6 @@
3030
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
3131
from chia.wallet.transaction_record import TransactionRecord
3232
from chia.wallet.util.transaction_type import TransactionType
33-
from chia.wallet.util.tx_config import TXConfig
3433
from chia.wallet.util.wallet_types import WalletType
3534
from chia.wallet.wallet_request_types import (
3635
CATAssetIDToName,
@@ -46,7 +45,6 @@
4645
NFTCalculateRoyaltiesResponse,
4746
NFTGetInfo,
4847
NFTGetInfoResponse,
49-
SendTransactionMultiResponse,
5048
SignMessageByAddress,
5149
SignMessageByAddressResponse,
5250
SignMessageByID,
@@ -235,44 +233,6 @@ async def nft_calculate_royalties(
235233
)
236234
)
237235

238-
async def send_transaction_multi(
239-
self,
240-
wallet_id: int,
241-
additions: list[dict[str, object]],
242-
tx_config: TXConfig,
243-
coins: Optional[list[Coin]] = None,
244-
fee: uint64 = uint64(0),
245-
push: bool = True,
246-
timelock_info: ConditionValidTimes = ConditionValidTimes(),
247-
) -> SendTransactionMultiResponse:
248-
self.add_to_log("send_transaction_multi", (wallet_id, additions, tx_config, coins, fee, push, timelock_info))
249-
name = bytes32([2] * 32)
250-
return SendTransactionMultiResponse(
251-
[STD_UTX],
252-
[STD_TX],
253-
TransactionRecord(
254-
confirmed_at_height=uint32(1),
255-
created_at_time=uint64(1234),
256-
to_puzzle_hash=bytes32([1] * 32),
257-
to_address=encode_puzzle_hash(bytes32([1] * 32), "xch"),
258-
amount=uint64(12345678),
259-
fee_amount=uint64(1234567),
260-
confirmed=False,
261-
sent=uint32(0),
262-
spend_bundle=WalletSpendBundle([], G2Element()),
263-
additions=[Coin(bytes32([1] * 32), bytes32([2] * 32), uint64(12345678))],
264-
removals=[Coin(bytes32([2] * 32), bytes32([4] * 32), uint64(12345678))],
265-
wallet_id=uint32(1),
266-
sent_to=[("aaaaa", uint8(1), None)],
267-
trade_id=None,
268-
type=uint32(TransactionType.OUTGOING_TX.value),
269-
name=name,
270-
memos={bytes32([3] * 32): [bytes([4] * 32)]},
271-
valid_times=ConditionValidTimes(),
272-
),
273-
name,
274-
)
275-
276236

277237
@dataclass
278238
class TestFullNodeRpcClient(TestRpcClient):

chia/_tests/wallet/rpc/test_wallet_rpc.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
SelectCoins,
163163
SendNotification,
164164
SendTransaction,
165+
SendTransactionMulti,
165166
SetWalletResyncOnStartup,
166167
SpendClawbackCoins,
167168
SplitCoins,
@@ -1003,11 +1004,14 @@ async def test_send_transaction_multi(wallet_rpc_environment: WalletRpcTestEnvir
10031004

10041005
send_tx_res: TransactionRecord = (
10051006
await client.send_transaction_multi(
1006-
1,
1007-
[{**output.to_json_dict(), "puzzle_hash": output.puzzle_hash} for output in outputs],
1008-
DEFAULT_TX_CONFIG,
1009-
coins=select_coins_response.coins,
1010-
fee=amount_fee,
1007+
SendTransactionMulti(
1008+
wallet_id=uint32(1),
1009+
additions=outputs,
1010+
coins=select_coins_response.coins,
1011+
fee=amount_fee,
1012+
push=True,
1013+
),
1014+
tx_config=DEFAULT_TX_CONFIG,
10111015
)
10121016
).transaction
10131017
spend_bundle = send_tx_res.spend_bundle
@@ -1503,10 +1507,11 @@ async def test_offer_endpoints(wallet_environments: WalletTestFramework, wallet_
15031507
# Creates a wallet for the same CAT on wallet_2 and send 4 CAT from wallet_1 to it
15041508
await env_2.rpc_client.create_wallet_for_existing_cat(cat_asset_id)
15051509
wallet_2_address = (await env_2.rpc_client.get_next_address(GetNextAddress(cat_wallet_id, False))).address
1506-
adds = [{"puzzle_hash": decode_puzzle_hash(wallet_2_address), "amount": uint64(4), "memos": ["the cat memo"]}]
1510+
adds = [Addition(puzzle_hash=decode_puzzle_hash(wallet_2_address), amount=uint64(4), memos=["the cat memo"])]
15071511
tx_res = (
15081512
await env_1.rpc_client.send_transaction_multi(
1509-
cat_wallet_id, additions=adds, tx_config=wallet_environments.tx_config, fee=uint64(0)
1513+
SendTransactionMulti(wallet_id=uint32(cat_wallet_id), additions=adds, fee=uint64(0), push=True),
1514+
tx_config=wallet_environments.tx_config,
15101515
)
15111516
).transaction
15121517
spend_bundle = tx_res.spend_bundle

chia/wallet/wallet_request_types.py

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from dataclasses import dataclass, field
55
from functools import cached_property
6-
from typing import Any, BinaryIO, Optional, Union, final
6+
from typing import Any, BinaryIO, Optional, TypeVar, Union, final
77

88
from chia_rs import Coin, G1Element, G2Element, PrivateKey
99
from chia_rs.sized_bytes import bytes32
@@ -1870,17 +1870,6 @@ class VCRevokeResponse(TransactionEndpointResponse):
18701870
pass
18711871

18721872

1873-
# TODO: The section below needs corresponding request types
1874-
# TODO: The section below should be added to the API (currently only for client)
1875-
1876-
1877-
@streamable
1878-
@dataclass(frozen=True)
1879-
class SendTransactionMultiResponse(TransactionEndpointResponse):
1880-
transaction: TransactionRecord
1881-
transaction_id: bytes32
1882-
1883-
18841873
@streamable
18851874
@dataclass(frozen=True)
18861875
class CSTCoinAnnouncement(Streamable):
@@ -1945,6 +1934,94 @@ class CreateSignedTransactionsResponse(TransactionEndpointResponse):
19451934
signed_tx: TransactionRecord
19461935

19471936

1937+
_T_SendTransactionMultiProxy = TypeVar("_T_SendTransactionMultiProxy", CATSpend, CreateSignedTransaction)
1938+
1939+
1940+
@streamable
1941+
@dataclass(frozen=True)
1942+
class SendTransactionMulti(TransactionEndpointRequest):
1943+
# primarily for cat_spend
1944+
wallet_id: uint32 = field(default_factory=default_raise)
1945+
additions: Optional[list[Addition]] = None # for both
1946+
amount: Optional[uint64] = None
1947+
inner_address: Optional[str] = None
1948+
memos: Optional[list[str]] = None
1949+
coins: Optional[list[Coin]] = None # for both
1950+
extra_delta: Optional[str] = None # str to support negative ints :(
1951+
tail_reveal: Optional[bytes] = None
1952+
tail_solution: Optional[bytes] = None
1953+
# for create_signed_transaction
1954+
morph_bytes: Optional[bytes] = None
1955+
coin_announcements: Optional[list[CSTCoinAnnouncement]] = None
1956+
puzzle_announcements: Optional[list[CSTPuzzleAnnouncement]] = None
1957+
1958+
def convert_to_proxy(self, proxy_type: type[_T_SendTransactionMultiProxy]) -> _T_SendTransactionMultiProxy:
1959+
if proxy_type is CATSpend:
1960+
if self.morph_bytes is not None:
1961+
raise ValueError(
1962+
'Specified "morph_bytes" for a CAT-type wallet. Maybe you meant to specify an XCH wallet?'
1963+
)
1964+
elif self.coin_announcements or self.puzzle_announcements is not None:
1965+
raise ValueError(
1966+
'Specified "coin/puzzle_announcements" for a CAT-type wallet.'
1967+
"Maybe you meant to specify an XCH wallet?"
1968+
)
1969+
1970+
# not sure why mypy hasn't understood this is purely a CATSpend
1971+
return proxy_type(
1972+
wallet_id=self.wallet_id,
1973+
additions=self.additions, # type: ignore[arg-type]
1974+
amount=self.amount, # type: ignore[call-arg]
1975+
inner_address=self.inner_address,
1976+
memos=self.memos,
1977+
coins=self.coins,
1978+
extra_delta=self.extra_delta,
1979+
tail_reveal=self.tail_reveal,
1980+
tail_solution=self.tail_solution,
1981+
fee=self.fee,
1982+
push=self.push,
1983+
sign=self.sign,
1984+
)
1985+
elif proxy_type is CreateSignedTransaction:
1986+
if self.amount is not None:
1987+
raise ValueError('Specified "amount" for an XCH wallet. Maybe you meant to specify a CAT-type wallet?')
1988+
elif self.inner_address is not None:
1989+
raise ValueError(
1990+
'Specified "inner_address" for an XCH wallet. Maybe you meant to specify a CAT-type wallet?'
1991+
)
1992+
elif self.memos is not None:
1993+
raise ValueError('Specified "memos" for an XCH wallet. Maybe you meant to specify a CAT-type wallet?')
1994+
elif self.extra_delta is not None or self.tail_reveal is not None or self.tail_solution is not None:
1995+
raise ValueError(
1996+
'Specified "extra_delta", "tail_reveal", or "tail_solution" for an XCH wallet.'
1997+
"Maybe you meant to specify a CAT-type wallet?"
1998+
)
1999+
elif self.additions is None:
2000+
raise ValueError('"additions" are required for XCH wallets.')
2001+
2002+
# not sure why mypy hasn't understood this is purely a CreateSignedTransaction
2003+
return proxy_type(
2004+
additions=self.additions,
2005+
wallet_id=self.wallet_id,
2006+
coins=self.coins,
2007+
morph_bytes=self.morph_bytes, # type: ignore[call-arg]
2008+
coin_announcements=self.coin_announcements if self.coin_announcements is not None else [],
2009+
puzzle_announcements=self.puzzle_announcements if self.puzzle_announcements is not None else [],
2010+
fee=self.fee,
2011+
push=self.push,
2012+
sign=self.sign,
2013+
)
2014+
else:
2015+
raise ValueError("An unsupported wallet type was selected for `send_transaction_multi`")
2016+
2017+
2018+
@streamable
2019+
@dataclass(frozen=True)
2020+
class SendTransactionMultiResponse(TransactionEndpointResponse):
2021+
transaction: TransactionRecord
2022+
transaction_id: bytes32
2023+
2024+
19482025
@streamable
19492026
@dataclass(frozen=True)
19502027
class _OfferEndpointResponse(TransactionEndpointResponse):

chia/wallet/wallet_rpc_api.py

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@
279279
SendNotification,
280280
SendNotificationResponse,
281281
SendTransaction,
282+
SendTransactionMulti,
283+
SendTransactionMultiResponse,
282284
SendTransactionResponse,
283285
SetWalletResyncOnStartup,
284286
SignMessageByAddress,
@@ -374,25 +376,40 @@ async def rpc_endpoint(
374376
):
375377
raise ValueError("Relative timelocks are not currently supported in the RPC")
376378

377-
async with self.service.wallet_state_manager.new_action_scope(
378-
tx_config,
379-
push=request.get("push", push),
380-
merge_spends=request.get("merge_spends", merge_spends),
381-
sign=request.get("sign", self.service.config.get("auto_sign_txs", True)),
382-
) as action_scope:
379+
if "action_scope_override" in kwargs:
383380
response: EndpointResult = await func(
384381
self,
385382
request,
386383
*args,
387-
action_scope,
384+
kwargs["action_scope_override"],
388385
extra_conditions=extra_conditions,
389-
**kwargs,
386+
**{k: v for k, v in kwargs.items() if k != "action_scope_override"},
390387
)
388+
action_scope = cast(WalletActionScope, kwargs["action_scope_override"])
389+
else:
390+
async with self.service.wallet_state_manager.new_action_scope(
391+
tx_config,
392+
push=request.get("push", push),
393+
merge_spends=request.get("merge_spends", merge_spends),
394+
sign=request.get("sign", self.service.config.get("auto_sign_txs", True)),
395+
) as action_scope:
396+
response = await func(
397+
self,
398+
request,
399+
*args,
400+
action_scope,
401+
extra_conditions=extra_conditions,
402+
**kwargs,
403+
)
391404

392405
if func.__name__ == "create_new_wallet" and "transactions" not in response:
393406
# unfortunately, this API isn't solely a tx endpoint
394407
return response
395408

409+
if "action_scope_override" in kwargs:
410+
# deferring to parent action scope
411+
return response
412+
396413
unsigned_txs = await self.service.wallet_state_manager.gather_signing_info_for_txs(
397414
action_scope.side_effects.transactions
398415
)
@@ -1665,35 +1682,41 @@ async def send_transaction(
16651682
# tx_endpoint will take care of the default values here
16661683
return SendTransactionResponse([], [], transaction=REPLACEABLE_TRANSACTION_RECORD, transaction_id=bytes32.zeros)
16671684

1668-
async def send_transaction_multi(self, request: dict[str, Any]) -> EndpointResult:
1685+
@tx_endpoint(push=True)
1686+
@marshal
1687+
async def send_transaction_multi(
1688+
self,
1689+
request: SendTransactionMulti,
1690+
action_scope: WalletActionScope,
1691+
extra_conditions: tuple[Condition, ...] = tuple(),
1692+
) -> SendTransactionMultiResponse:
16691693
if await self.service.wallet_state_manager.synced() is False:
16701694
raise ValueError("Wallet needs to be fully synced before sending transactions")
16711695

1672-
# This is required because this is a "@tx_endpoint" that calls other @tx_endpoints
1673-
request.setdefault("push", True)
1674-
request.setdefault("merge_spends", True)
1675-
1676-
wallet_id = uint32(request["wallet_id"])
1677-
wallet = self.service.wallet_state_manager.wallets[wallet_id]
1696+
wallet = self.service.wallet_state_manager.wallets[request.wallet_id]
16781697

16791698
async with self.service.wallet_state_manager.lock:
1680-
if wallet.type() in {WalletType.CAT, WalletType.CRCAT, WalletType.RCAT}:
1681-
assert isinstance(wallet, CATWallet)
1682-
response = await self.cat_spend(request, hold_lock=False)
1683-
transaction = response["transaction"]
1684-
transactions = response["transactions"]
1699+
if issubclass(type(wallet), CATWallet):
1700+
await self.cat_spend(
1701+
request.convert_to_proxy(CATSpend).json_serialize_for_transport(
1702+
action_scope.config.tx_config, extra_conditions, ConditionValidTimes()
1703+
),
1704+
hold_lock=False,
1705+
action_scope_override=action_scope,
1706+
)
16851707
else:
1686-
response = await self.create_signed_transaction(request, hold_lock=False)
1687-
transaction = response["signed_tx"]
1688-
transactions = response["transactions"]
1708+
await self.create_signed_transaction(
1709+
request.convert_to_proxy(CreateSignedTransaction).json_serialize_for_transport(
1710+
action_scope.config.tx_config, extra_conditions, ConditionValidTimes()
1711+
),
1712+
hold_lock=False,
1713+
action_scope_override=action_scope,
1714+
)
16891715

1690-
# Transaction may not have been included in the mempool yet. Use get_transaction to check.
1691-
return {
1692-
"transaction": transaction,
1693-
"transaction_id": TransactionRecord.from_json_dict(transaction).name,
1694-
"transactions": transactions,
1695-
"unsigned_transactions": response["unsigned_transactions"],
1696-
}
1716+
# tx_endpoint will take care of these values
1717+
return SendTransactionMultiResponse(
1718+
[], [], transaction=REPLACEABLE_TRANSACTION_RECORD, transaction_id=bytes32.zeros
1719+
)
16971720

16981721
@tx_endpoint(push=True, merge_spends=False)
16991722
@marshal

0 commit comments

Comments
 (0)