Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 46 additions & 39 deletions chia/_tests/wallet/rpc/test_wallet_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
from chia.wallet.wallet_node import WalletNode
from chia.wallet.wallet_protocol import WalletProtocol
from chia.wallet.wallet_request_types import (
Addition,
AddKey,
CancelOffer,
CancelOffers,
Expand All @@ -118,6 +119,7 @@
ClawbackPuzzleDecoratorOverride,
CombineCoins,
CreateOfferForIDs,
CreateSignedTransaction,
DefaultCAT,
DeleteKey,
DeleteNotifications,
Expand Down Expand Up @@ -294,21 +296,18 @@ async def wallet_rpc_environment(
yield WalletRpcTestEnvironment(wallet_bundle_1, wallet_bundle_2, node_bundle)


async def create_tx_outputs(wallet: Wallet, output_args: list[tuple[int, Optional[list[str]]]]) -> list[dict[str, Any]]:
outputs = []
async def create_tx_outputs(wallet: Wallet, output_args: list[tuple[int, Optional[list[str]]]]) -> list[Addition]:
async with wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
for args in output_args:
output = {
"amount": uint64(args[0]),
"puzzle_hash": await action_scope.get_puzzle_hash(
return [
Addition(
amount=uint64(args[0]),
puzzle_hash=await action_scope.get_puzzle_hash(
wallet.wallet_state_manager, override_reuse_puzhash_with=False
),
}
if args[1] is not None:
assert len(args[1]) > 0
output["memos"] = args[1]
outputs.append(output)
return outputs
memos=None if args[1] is None or len(args[1]) == 0 else args[1],
)
for args in output_args
]


async def assert_wallet_types(client: WalletRpcClient, expected: dict[WalletType, int]) -> None:
Expand All @@ -323,20 +322,20 @@ async def assert_wallet_types(client: WalletRpcClient, expected: dict[WalletType

def assert_tx_amounts(
tx: TransactionRecord,
outputs: list[dict[str, Any]],
outputs: list[Addition],
*,
amount_fee: uint64,
change_expected: bool,
is_cat: bool = False,
) -> None:
assert tx.fee_amount == amount_fee
assert tx.amount == sum(output["amount"] for output in outputs)
assert tx.amount == sum(output.amount for output in outputs)
expected_additions = len(outputs) + 1 if change_expected else len(outputs)
assert len(tx.additions) == expected_additions
addition_amounts = [addition.amount for addition in tx.additions]
removal_amounts = [removal.amount for removal in tx.removals]
for output in outputs:
assert output["amount"] in addition_amounts
assert output.amount in addition_amounts
if is_cat:
assert (sum(removal_amounts) - sum(addition_amounts)) == 0
else:
Expand Down Expand Up @@ -486,9 +485,8 @@ async def test_push_transactions(wallet_rpc_environment: WalletRpcTestEnvironmen

tx = (
await client.create_signed_transactions(
outputs,
CreateSignedTransaction(additions=outputs, fee=uint64(100)),
tx_config=DEFAULT_TX_CONFIG,
fee=uint64(100),
)
).signed_tx

Expand Down Expand Up @@ -654,35 +652,39 @@ async def test_create_signed_transaction(
await farm_transaction_block(full_node_api, wallet_1_node)

outputs = await create_tx_outputs(wallet_2, output_args)
amount_outputs = sum(output["amount"] for output in outputs)
amount_outputs = sum(output.amount for output in outputs)
amount_fee = uint64(fee)

if is_cat:
amount_total = amount_outputs
amount_total: int = amount_outputs
else:
amount_total = amount_outputs + amount_fee

selected_coin = None
if select_coin:
select_coins_response = await wallet_1_rpc.select_coins(
SelectCoins.from_coin_selection_config(
amount=amount_total, wallet_id=uint32(wallet_id), coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG
amount=uint64(amount_total),
wallet_id=uint32(wallet_id),
coin_selection_config=DEFAULT_COIN_SELECTION_CONFIG,
)
)
assert len(select_coins_response.coins) == 1
selected_coin = select_coins_response.coins[0]

txs = (
await wallet_1_rpc.create_signed_transactions(
outputs,
coins=[selected_coin] if selected_coin is not None else [],
fee=amount_fee,
wallet_id=wallet_id,
CreateSignedTransaction(
additions=outputs,
coins=[selected_coin] if selected_coin is not None else None,
fee=amount_fee,
wallet_id=uint32(wallet_id),
push=True,
),
# shouldn't actually block it
tx_config=DEFAULT_TX_CONFIG.override(
excluded_coin_amounts=[uint64(selected_coin.amount)] if selected_coin is not None else [],
),
push=True,
)
).transactions
change_expected = not selected_coin or selected_coin.amount - amount_total > 0
Expand Down Expand Up @@ -711,18 +713,18 @@ async def test_create_signed_transaction(
addition_dict: dict[bytes32, Coin] = {addition.name(): addition for addition in additions}
memo_dictionary: dict[bytes32, list[bytes]] = compute_memos(spend_bundle)
for output in outputs:
if "memos" in output:
if output.memos is not None:
found: bool = False
for addition_id, addition in addition_dict.items():
if (
is_cat
and addition.amount == output["amount"]
and memo_dictionary[addition_id][0] == output["puzzle_hash"]
and memo_dictionary[addition_id][1:] == [memo.encode() for memo in output["memos"]]
and addition.amount == output.amount
and memo_dictionary[addition_id][0] == output.puzzle_hash
and memo_dictionary[addition_id][1:] == [memo.encode() for memo in output.memos]
) or (
addition.amount == output["amount"]
and addition.puzzle_hash == output["puzzle_hash"]
and memo_dictionary[addition_id] == [memo.encode() for memo in output["memos"]]
addition.amount == output.amount
and addition.puzzle_hash == output.puzzle_hash
and memo_dictionary[addition_id] == [memo.encode() for memo in output.memos]
):
found = True
assert found
Expand Down Expand Up @@ -755,7 +757,9 @@ async def test_create_signed_transaction_with_coin_announcement(
outputs = await create_tx_outputs(wallet_2, [(signed_tx_amount, None)])
tx_res: TransactionRecord = (
await client.create_signed_transactions(
outputs, tx_config=DEFAULT_TX_CONFIG, extra_conditions=(*tx_coin_announcements,)
CreateSignedTransaction(additions=outputs),
tx_config=DEFAULT_TX_CONFIG,
extra_conditions=(*tx_coin_announcements,),
)
).signed_tx
assert_tx_amounts(tx_res, outputs, amount_fee=uint64(0), change_expected=True)
Expand Down Expand Up @@ -789,7 +793,9 @@ async def test_create_signed_transaction_with_puzzle_announcement(
outputs = await create_tx_outputs(wallet_2, [(signed_tx_amount, None)])
tx_res = (
await client.create_signed_transactions(
outputs, tx_config=DEFAULT_TX_CONFIG, extra_conditions=(*tx_puzzle_announcements,)
CreateSignedTransaction(additions=outputs),
tx_config=DEFAULT_TX_CONFIG,
extra_conditions=(*tx_puzzle_announcements,),
)
).signed_tx
assert_tx_amounts(tx_res, outputs, amount_fee=uint64(0), change_expected=True)
Expand All @@ -816,7 +822,7 @@ async def it_does_not_include_the_excluded_coins() -> None:

tx = (
await wallet_1_rpc.create_signed_transactions(
outputs,
CreateSignedTransaction(additions=outputs),
DEFAULT_TX_CONFIG.override(
excluded_coin_ids=[c.name() for c in select_coins_response.coins],
),
Expand All @@ -839,7 +845,7 @@ async def it_throws_an_error_when_all_spendable_coins_are_excluded() -> None:

with pytest.raises(ValueError):
await wallet_1_rpc.create_signed_transactions(
outputs,
CreateSignedTransaction(additions=outputs),
DEFAULT_TX_CONFIG.override(
excluded_coin_ids=[c.name() for c in select_coins_response.coins],
),
Expand Down Expand Up @@ -992,13 +998,13 @@ async def test_send_transaction_multi(wallet_rpc_environment: WalletRpcTestEnvir
)
) # we want a coin that won't be selected by default
outputs = await create_tx_outputs(wallet_2, [(uint64(1), ["memo_1"]), (uint64(2), ["memo_2"])])
amount_outputs = sum(output["amount"] for output in outputs)
amount_outputs = sum(output.amount for output in outputs)
amount_fee = uint64(amount_outputs + 1)

send_tx_res: TransactionRecord = (
await client.send_transaction_multi(
1,
outputs,
[{**output.to_json_dict(), "puzzle_hash": output.puzzle_hash} for output in outputs],
DEFAULT_TX_CONFIG,
coins=select_coins_response.coins,
fee=amount_fee,
Expand All @@ -1021,7 +1027,8 @@ async def test_send_transaction_multi(wallet_rpc_environment: WalletRpcTestEnvir
memos = tx_confirmed.memos
assert len(memos) == len(outputs)
for output in outputs:
assert [output["memos"][0].encode()] in memos.values()
assert output.memos is not None
assert [output.memos[0].encode()] in memos.values()
spend_bundle = send_tx_res.spend_bundle
assert spend_bundle is not None
for key in memos.keys():
Expand Down
18 changes: 11 additions & 7 deletions chia/_tests/wallet/vc_wallet/test_vc_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
from chia.wallet.wallet import Wallet
from chia.wallet.wallet_node import WalletNode
from chia.wallet.wallet_request_types import (
Addition,
CATSpend,
CreateSignedTransaction,
GetTransactions,
GetWallets,
VCAddProofs,
Expand Down Expand Up @@ -70,14 +72,16 @@ async def mint_cr_cat(
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=20)
tx = (
await client_0.create_signed_transactions(
[
{
"puzzle_hash": cat_puzzle.get_tree_hash(),
"amount": CAT_AMOUNT_0,
}
],
CreateSignedTransaction(
wallet_id=uint32(1),
additions=[
Addition(
puzzle_hash=cat_puzzle.get_tree_hash(),
amount=CAT_AMOUNT_0,
)
],
),
tx_config,
wallet_id=1,
)
).signed_tx
spend_bundle = tx.spend_bundle
Expand Down
68 changes: 66 additions & 2 deletions chia/wallet/wallet_request_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
from chia.types.blockchain_format.program import Program
from chia.types.coin_record import CoinRecord
from chia.util.byte_types import hexstr_to_bytes
from chia.util.hash import std_hash
from chia.util.streamable import Streamable, streamable
from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_to_json_dicts
from chia.wallet.conditions import (
AssertCoinAnnouncement,
AssertPuzzleAnnouncement,
Condition,
ConditionValidTimes,
conditions_to_json_dicts,
)
from chia.wallet.nft_wallet.nft_info import NFTInfo
from chia.wallet.notification_store import Notification
from chia.wallet.puzzle_drivers import PuzzleInfo, Solver
Expand Down Expand Up @@ -1434,7 +1441,7 @@ class CombineCoinsResponse(TransactionEndpointResponse):
pass


# utility for CATSpend
# utility for CATSpend/CreateSignedTransaction
# unfortunate that we can't use CreateCoin but the memos are taken as strings not bytes
@streamable
@dataclass(frozen=True)
Expand Down Expand Up @@ -1874,6 +1881,63 @@ class SendTransactionMultiResponse(TransactionEndpointResponse):
transaction_id: bytes32


@streamable
@dataclass(frozen=True)
class CSTCoinAnnouncement(Streamable):
coin_id: bytes32
message: bytes


@streamable
@dataclass(frozen=True)
class CSTPuzzleAnnouncement(Streamable):
puzzle_hash: bytes32
message: bytes


@streamable
@dataclass(frozen=True)
class CreateSignedTransaction(TransactionEndpointRequest):
additions: list[Addition] = field(default_factory=default_raise)
wallet_id: Optional[uint32] = None
coins: Optional[list[Coin]] = None
morph_bytes: Optional[bytes] = None
coin_announcements: list[CSTCoinAnnouncement] = field(default_factory=list)
puzzle_announcements: list[CSTPuzzleAnnouncement] = field(default_factory=list)

def __post_init__(self) -> None:
if len(self.additions) < 1:
raise ValueError("Must have at least one addition")
super().__post_init__()

@property
def coin_set(self) -> Optional[set[Coin]]:
if self.coins is None:
return None
else:
return set(self.coins)

@property
def asserted_coin_announcements(self) -> tuple[AssertCoinAnnouncement, ...]:
return tuple(
AssertCoinAnnouncement(
asserted_id=ca.coin_id,
asserted_msg=(ca.message if self.morph_bytes is None else std_hash(self.morph_bytes + ca.message)),
)
for ca in self.coin_announcements
)

@property
def asserted_puzzle_announcements(self) -> tuple[AssertPuzzleAnnouncement, ...]:
return tuple(
AssertPuzzleAnnouncement(
asserted_ph=pa.puzzle_hash,
asserted_msg=(pa.message if self.morph_bytes is None else std_hash(self.morph_bytes + pa.message)),
)
for pa in self.puzzle_announcements
)


@streamable
@dataclass(frozen=True)
class CreateSignedTransactionsResponse(TransactionEndpointResponse):
Expand Down
Loading
Loading