Skip to content

Commit

Permalink
[CHIA-2192] Add singleton records to action scopes (#19138)
Browse files Browse the repository at this point in the history
* Add singleton_records to action scopes

* Extract SingletonRecord to its own file

* Fix test_lifecycle

* Fix wallet action scope test mock
  • Loading branch information
Quexington authored Jan 27, 2025
1 parent a6cad88 commit 5e0d6a4
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 64 deletions.
18 changes: 6 additions & 12 deletions chia/_tests/wallet/db_wallet/test_dl_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,11 @@ async def test_lifecycle(
current_tree = MerkleTree(nodes)
current_root = current_tree.calculate_root()

async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=False) as action_scope:
async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
launcher_id = await dl_wallet.generate_new_reporter(current_root, action_scope)

assert await dl_wallet.get_latest_singleton(launcher_id) is not None

[std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions(
action_scope.side_effects.transactions
)
await full_node_api.process_transaction_records(records=[std_record])
await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions)

await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id)

Expand All @@ -276,7 +272,7 @@ async def test_lifecycle(

new_root = MerkleTree([Program.to("root").get_tree_hash()]).calculate_root()

async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=False) as action_scope:
async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
await dl_wallet.generate_signed_transaction(
[previous_record.lineage_proof.amount],
[previous_record.inner_puzzle_hash],
Expand Down Expand Up @@ -309,8 +305,7 @@ async def test_lifecycle(
assert new_record != previous_record
assert not new_record.confirmed

txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions)
await full_node_api.process_transaction_records(records=txs)
await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions)

await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id)
await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds - 2000000000000)
Expand All @@ -324,15 +319,14 @@ async def test_lifecycle(
previous_record = await dl_wallet.get_latest_singleton(launcher_id)

new_root = MerkleTree([Program.to("new root").get_tree_hash()]).calculate_root()
async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=False) as action_scope:
async with dl_wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
await dl_wallet.create_update_state_spend(launcher_id, new_root, action_scope)
new_record = await dl_wallet.get_latest_singleton(launcher_id)
assert new_record is not None
assert new_record != previous_record
assert not new_record.confirmed

txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(action_scope.side_effects.transactions)
await full_node_api.process_transaction_records(records=txs)
await full_node_api.process_transaction_records(records=action_scope.side_effects.transactions)

await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id)
await asyncio.sleep(0.5)
Expand Down
26 changes: 22 additions & 4 deletions chia/_tests/wallet/test_wallet_action_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from chia_rs import G2Element

from chia._tests.cmds.wallet.test_consts import STD_TX
from chia.data_layer.singleton_record import SingletonRecord
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint64
Expand Down Expand Up @@ -36,7 +37,15 @@ def test_back_and_forth_serialization() -> None:
@dataclass
class MockWalletStateManager:
most_recent_call: Optional[
tuple[list[TransactionRecord], bool, bool, bool, list[SigningResponse], list[WalletSpendBundle]]
tuple[
list[TransactionRecord],
bool,
bool,
bool,
list[SigningResponse],
list[WalletSpendBundle],
list[SingletonRecord],
],
] = None

async def add_pending_transactions(
Expand All @@ -47,8 +56,17 @@ async def add_pending_transactions(
sign: bool,
additional_signing_responses: list[SigningResponse],
extra_spends: list[WalletSpendBundle],
singleton_records: list[SingletonRecord],
) -> list[TransactionRecord]:
self.most_recent_call = (txs, push, merge_spends, sign, additional_signing_responses, extra_spends)
self.most_recent_call = (
txs,
push,
merge_spends,
sign,
additional_signing_responses,
extra_spends,
singleton_records,
)
return txs


Expand All @@ -73,7 +91,7 @@ async def test_wallet_action_scope() -> None:
action_scope.side_effects

assert action_scope.side_effects.transactions == [STD_TX]
assert wsm.most_recent_call == ([STD_TX], True, False, True, [], [])
assert wsm.most_recent_call == ([STD_TX], True, False, True, [], [], [])

async with wsm.new_action_scope( # type: ignore[attr-defined]
DEFAULT_TX_CONFIG, push=False, merge_spends=True, sign=True, additional_signing_responses=[], extra_spends=[]
Expand All @@ -82,4 +100,4 @@ async def test_wallet_action_scope() -> None:
interface.side_effects.transactions = []

assert action_scope.side_effects.transactions == []
assert wsm.most_recent_call == ([], False, True, True, [], [])
assert wsm.most_recent_call == ([], False, True, True, [], [], [])
3 changes: 2 additions & 1 deletion chia/data_layer/data_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
leaf_hash,
unspecified,
)
from chia.data_layer.data_layer_wallet import DataLayerWallet, Mirror, SingletonRecord, verify_offer
from chia.data_layer.data_layer_wallet import DataLayerWallet, Mirror, verify_offer
from chia.data_layer.data_store import DataStore
from chia.data_layer.download_data import (
delete_full_file_if_exists,
Expand All @@ -53,6 +53,7 @@
insert_from_delta_file,
write_files_for_root,
)
from chia.data_layer.singleton_record import SingletonRecord
from chia.rpc.rpc_server import StateChangedProtocol, default_get_connections
from chia.rpc.wallet_request_types import LogIn
from chia.rpc.wallet_rpc_client import WalletRpcClient
Expand Down
69 changes: 25 additions & 44 deletions chia/data_layer/data_layer_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from chia.consensus.block_record import BlockRecord
from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError, OfferIntegrityError
from chia.data_layer.data_layer_util import OfferStore, ProofOfInclusion, ProofOfInclusionLayer, StoreProofs, leaf_hash
from chia.data_layer.singleton_record import SingletonRecord
from chia.protocols.wallet_protocol import CoinState
from chia.server.ws_connection import WSChiaConnection
from chia.types.blockchain_format.coin import Coin
Expand All @@ -21,7 +22,6 @@
from chia.types.coin_spend import CoinSpend, compute_additions
from chia.types.condition_opcodes import ConditionOpcode
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.streamable import Streamable, streamable
from chia.wallet.conditions import (
AssertAnnouncement,
AssertCoinAnnouncement,
Expand Down Expand Up @@ -68,20 +68,6 @@
from chia.wallet.wallet_state_manager import WalletStateManager


@streamable
@dataclasses.dataclass(frozen=True)
class SingletonRecord(Streamable):
coin_id: bytes32
launcher_id: bytes32
root: bytes32
inner_puzzle_hash: bytes32
confirmed: bool
confirmed_at_height: uint32
lineage_proof: LineageProof
generation: uint32
timestamp: uint64


@dataclasses.dataclass(frozen=True)
class Mirror:
coin_id: bytes32
Expand Down Expand Up @@ -342,30 +328,31 @@ async def generate_new_reporter(
SerializedProgram.from_program(genesis_launcher_solution),
)
launcher_sb = WalletSpendBundle([launcher_cs], G2Element())
launcher_id = launcher_coin.name()

async with action_scope.use() as interface:
interface.side_effects.extra_spends.append(launcher_sb)
interface.side_effects.singleton_records.append(
SingletonRecord(
coin_id=Coin(launcher_id, full_puzzle.get_tree_hash(), uint64(1)).name(),
launcher_id=launcher_id,
root=initial_root,
inner_puzzle_hash=inner_puzzle.get_tree_hash(),
confirmed=False,
confirmed_at_height=uint32(0),
timestamp=uint64(0),
lineage_proof=LineageProof(
launcher_id,
create_host_layer_puzzle(inner_puzzle, initial_root).get_tree_hash(),
uint64(1),
),
generation=uint32(0),
)
)

singleton_record = SingletonRecord(
coin_id=Coin(launcher_coin.name(), full_puzzle.get_tree_hash(), uint64(1)).name(),
launcher_id=launcher_coin.name(),
root=initial_root,
inner_puzzle_hash=inner_puzzle.get_tree_hash(),
confirmed=False,
confirmed_at_height=uint32(0),
timestamp=uint64(0),
lineage_proof=LineageProof(
launcher_coin.name(),
create_host_layer_puzzle(inner_puzzle, initial_root).get_tree_hash(),
uint64(1),
),
generation=uint32(0),
)

await self.wallet_state_manager.dl_store.add_singleton_record(singleton_record)
await self.wallet_state_manager.add_interested_puzzle_hashes([singleton_record.launcher_id], [self.id()])
await self.wallet_state_manager.add_interested_puzzle_hashes([launcher_id], [self.id()])

return launcher_coin.name()
return launcher_id

async def create_tandem_xch_tx(
self,
Expand All @@ -390,7 +377,6 @@ async def create_update_state_spend(
new_puz_hash: Optional[bytes32] = None,
new_amount: Optional[uint64] = None,
fee: uint64 = uint64(0),
add_pending_singleton: bool = True,
announce_new_state: bool = False,
extra_conditions: tuple[Condition, ...] = tuple(),
) -> None:
Expand Down Expand Up @@ -591,18 +577,16 @@ async def create_update_state_spend(
action_scope,
)

if add_pending_singleton:
await self.wallet_state_manager.dl_store.add_singleton_record(
async with action_scope.use() as interface:
interface.side_effects.transactions.append(dl_tx)
interface.side_effects.singleton_records.append(
new_singleton_record,
)
if announce_new_state:
await self.wallet_state_manager.dl_store.add_singleton_record(
interface.side_effects.singleton_records.append(
second_singleton_record,
)

async with action_scope.use() as interface:
interface.side_effects.transactions.append(dl_tx)

async def generate_signed_transaction(
self,
amounts: list[uint64],
Expand All @@ -616,7 +600,6 @@ async def generate_signed_transaction(
) -> None:
launcher_id: Optional[bytes32] = kwargs.get("launcher_id", None)
new_root_hash: Optional[bytes32] = kwargs.get("new_root_hash", None)
add_pending_singleton: bool = kwargs.get("add_pending_singleton", True)
announce_new_state: bool = kwargs.get("announce_new_state", False)
# Figure out the launcher ID
if len(coins) == 0:
Expand All @@ -642,7 +625,6 @@ async def generate_signed_transaction(
puzzle_hashes[0],
amounts[0],
fee,
add_pending_singleton,
announce_new_state,
extra_conditions,
)
Expand Down Expand Up @@ -1057,7 +1039,6 @@ async def make_update_offer(
fee=fee_left_to_pay,
launcher_id=launcher,
new_root_hash=new_root,
add_pending_singleton=False,
announce_new_state=True,
extra_conditions=extra_conditions,
)
Expand Down
3 changes: 2 additions & 1 deletion chia/data_layer/dl_wallet_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from aiosqlite import Row

from chia.data_layer.data_layer_wallet import Mirror, SingletonRecord
from chia.data_layer.data_layer_wallet import Mirror
from chia.data_layer.singleton_record import SingletonRecord
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.db_wrapper import DBWrapper2, execute_fetchone
Expand Down
22 changes: 22 additions & 0 deletions chia/data_layer/singleton_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

import dataclasses

from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint32, uint64
from chia.util.streamable import Streamable, streamable
from chia.wallet.lineage_proof import LineageProof


@streamable
@dataclasses.dataclass(frozen=True)
class SingletonRecord(Streamable):
coin_id: bytes32
launcher_id: bytes32
root: bytes32
inner_puzzle_hash: bytes32
confirmed: bool
confirmed_at_height: uint32
lineage_proof: LineageProof
generation: uint32
timestamp: uint64
3 changes: 2 additions & 1 deletion chia/rpc/wallet_rpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from typing import Any, Optional, Union, cast

from chia.data_layer.data_layer_util import DLProof, VerifyProofResponse
from chia.data_layer.data_layer_wallet import Mirror, SingletonRecord
from chia.data_layer.data_layer_wallet import Mirror
from chia.data_layer.singleton_record import SingletonRecord
from chia.pools.pool_wallet_info import PoolWalletInfo
from chia.rpc.rpc_client import RpcClient
from chia.rpc.wallet_request_types import (
Expand Down
4 changes: 4 additions & 0 deletions chia/wallet/wallet_action_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Optional, cast, final

from chia.data_layer.singleton_record import SingletonRecord
from chia.types.blockchain_format.coin import Coin
from chia.util.action_scope import ActionScope
from chia.util.streamable import Streamable, streamable
Expand All @@ -25,6 +26,7 @@ class _StreamableWalletSideEffects(Streamable):
signing_responses: list[SigningResponse]
extra_spends: list[WalletSpendBundle]
selected_coins: list[Coin]
singleton_records: list[SingletonRecord]


@dataclass
Expand All @@ -33,6 +35,7 @@ class WalletSideEffects:
signing_responses: list[SigningResponse] = field(default_factory=list)
extra_spends: list[WalletSpendBundle] = field(default_factory=list)
selected_coins: list[Coin] = field(default_factory=list)
singleton_records: list[SingletonRecord] = field(default_factory=list)

def __bytes__(self) -> bytes:
return bytes(_StreamableWalletSideEffects(**self.__dict__))
Expand Down Expand Up @@ -93,4 +96,5 @@ async def new_wallet_action_scope(
sign=sign,
additional_signing_responses=self.side_effects.signing_responses,
extra_spends=self.side_effects.extra_spends,
singleton_records=self.side_effects.singleton_records,
)
1 change: 0 additions & 1 deletion chia/wallet/wallet_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class GSTOptionalArgs(TypedDict):
launcher_id: NotRequired[Optional[bytes32]]
new_root_hash: NotRequired[Optional[bytes32]]
sign: NotRequired[bool]
add_pending_singleton: NotRequired[bool]
announce_new_state: NotRequired[bool]
# CATWallet
cat_discrepancy: NotRequired[Optional[tuple[int, Program, Program]]]
Expand Down
5 changes: 5 additions & 0 deletions chia/wallet/wallet_state_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from chia.consensus.constants import ConsensusConstants
from chia.data_layer.data_layer_wallet import DataLayerWallet
from chia.data_layer.dl_wallet_store import DataLayerStore
from chia.data_layer.singleton_record import SingletonRecord
from chia.pools.pool_puzzles import (
get_most_recent_singleton_coin_from_coin_spend,
solution_to_pool_state,
Expand Down Expand Up @@ -2298,6 +2299,7 @@ async def add_pending_transactions(
sign: Optional[bool] = None,
additional_signing_responses: Optional[list[SigningResponse]] = None,
extra_spends: Optional[list[WalletSpendBundle]] = None,
singleton_records: list[SingletonRecord] = [],
) -> list[TransactionRecord]:
"""
Add a list of transactions to be submitted to the full node.
Expand Down Expand Up @@ -2344,6 +2346,9 @@ async def add_pending_transactions(
all_coins_names.extend([coin.name() for coin in tx_record.additions])
all_coins_names.extend([coin.name() for coin in tx_record.removals])

for singleton_record in singleton_records:
await self.dl_store.add_singleton_record(singleton_record)

await self.add_interested_coin_ids(all_coins_names)

if actual_spend_involved:
Expand Down

0 comments on commit 5e0d6a4

Please sign in to comment.