Add timelock information to Trades and Transactions (#15915)

This commit is contained in:
Matt Hauff
2023-09-12 11:28:01 -05:00
committed by GitHub
parent fd51693ba0
commit efff83e7be
24 changed files with 348 additions and 104 deletions
+4 -1
View File
@@ -25,7 +25,7 @@ from chia.types.condition_opcodes import ConditionOpcode
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.streamable import Streamable, streamable
from chia.wallet.conditions import Condition, UnknownCondition
from chia.wallet.conditions import Condition, UnknownCondition, parse_timelock_info
from chia.wallet.db_wallet.db_wallet_puzzles import (
ACS_MU,
ACS_MU_PH,
@@ -355,6 +355,7 @@ class DataLayerWallet:
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=full_spend.name(),
valid_times=parse_timelock_info(extra_conditions),
)
singleton_record = SingletonRecord(
coin_id=Coin(launcher_coin.name(), full_puzzle.get_tree_hash(), uint64(1)).name(),
@@ -608,6 +609,7 @@ class DataLayerWallet:
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=singleton_record.coin_id,
valid_times=parse_timelock_info(extra_conditions),
)
assert dl_tx.spend_bundle is not None
if fee > 0:
@@ -822,6 +824,7 @@ class DataLayerWallet:
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=mirror_bundle.name(),
valid_times=parse_timelock_info(extra_conditions),
)
]
+4 -1
View File
@@ -46,7 +46,7 @@ from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend, compute_additions
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint32, uint64, uint128
from chia.wallet.conditions import Condition
from chia.wallet.conditions import Condition, ConditionValidTimes, parse_timelock_info
from chia.wallet.derive_keys import find_owner_sk
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key
from chia.wallet.sign_coin_spends import sign_coin_spends
@@ -460,6 +460,7 @@ class PoolWallet:
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
valid_times=parse_timelock_info(extra_conditions),
)
await standard_wallet.push_transaction(standard_wallet_record)
p2_singleton_puzzle_hash: bytes32 = launcher_id_to_p2_puzzle_hash(
@@ -632,6 +633,7 @@ class PoolWallet:
memos=[],
type=uint32(TransactionType.OUTGOING_TX.value),
name=signed_spend_bundle.name(),
valid_times=ConditionValidTimes(),
)
await self.publish_transactions(tx_record, fee_tx)
@@ -913,6 +915,7 @@ class PoolWallet:
trade_id=None,
type=uint32(TransactionType.OUTGOING_TX.value),
name=full_spend.name(),
valid_times=ConditionValidTimes(),
)
await self.publish_transactions(absorb_transaction, fee_tx)
+1
View File
@@ -3929,6 +3929,7 @@ class WalletRpcApi:
parsed_request.min_amount_to_claim,
tx_config,
fee=parsed_request.fee,
extra_conditions=extra_conditions,
)
for tx in txs:
await self.service.wallet_state_manager.add_pending_transaction(tx)
+4 -1
View File
@@ -32,7 +32,7 @@ from chia.wallet.cat_wallet.cat_utils import (
)
from chia.wallet.cat_wallet.lineage_store import CATLineageStore
from chia.wallet.coin_selection import select_coins
from chia.wallet.conditions import Condition, UnknownCondition
from chia.wallet.conditions import Condition, ConditionValidTimes, UnknownCondition, parse_timelock_info
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.outer_puzzles import AssetType
@@ -171,6 +171,7 @@ class CATWallet:
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32(token_bytes()),
memos=[],
valid_times=ConditionValidTimes(),
)
chia_tx = dataclasses.replace(chia_tx, spend_bundle=spend_bundle)
await self.standard_wallet.push_transaction(chia_tx)
@@ -844,6 +845,7 @@ class CATWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
]
@@ -866,6 +868,7 @@ class CATWallet:
type=chia_tx.type,
name=chia_tx.name,
memos=[],
valid_times=parse_timelock_info(extra_conditions),
)
)
+1 -1
View File
@@ -1210,7 +1210,7 @@ ABSOLUTE_PROPERTIES: Set[str] = {"min_time", "max_time", "min_height", "max_heig
ALL_PROPERTIES: Set[str] = SECONDS_PROPERTIES | HEIGHT_PROPERTIES
def parse_timelock_info(conditions: List[Condition]) -> ConditionValidTimes:
def parse_timelock_info(conditions: Iterable[Condition]) -> ConditionValidTimes:
valid_times: ConditionValidTimes = ConditionValidTimes()
properties: Set[str] = ALL_PROPERTIES.copy()
for condition in conditions:
+7 -1
View File
@@ -20,7 +20,7 @@ from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.util.ints import uint32, uint64, uint128
from chia.wallet.conditions import Condition
from chia.wallet.conditions import Condition, ConditionValidTimes, parse_timelock_info
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.derive_keys import master_sk_to_wallet_sk_unhardened
from chia.wallet.did_wallet import did_wallet_puzzles
@@ -618,6 +618,7 @@ class DIDWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
await self.wallet_state_manager.add_pending_transaction(did_record)
@@ -714,6 +715,7 @@ class DIDWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
await self.wallet_state_manager.add_pending_transaction(did_record)
return did_record
@@ -834,6 +836,7 @@ class DIDWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
valid_times=ConditionValidTimes(),
)
await self.wallet_state_manager.add_pending_transaction(did_record)
return spend_bundle
@@ -915,6 +918,7 @@ class DIDWallet:
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
attest_str: str = f"{self.get_my_DID()}:{bytes(message_spend_bundle).hex()}:{coin.parent_coin_info.hex()}:"
attest_str += f"{self.did_info.current_inner.get_tree_hash().hex()}:{coin.amount}"
@@ -1043,6 +1047,7 @@ class DIDWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32(token_bytes()),
memos=list(compute_memos(spend_bundle).items()),
valid_times=ConditionValidTimes(),
)
await self.wallet_state_manager.add_pending_transaction(did_record)
new_did_info = DIDInfo(
@@ -1284,6 +1289,7 @@ class DIDWallet:
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32(token_bytes()),
memos=[],
valid_times=ConditionValidTimes(),
)
regular_record = dataclasses.replace(tx_record, spend_bundle=None)
await self.wallet_state_manager.add_pending_transaction(regular_record)
+2 -1
View File
@@ -23,7 +23,7 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.util.hash import std_hash
from chia.util.ints import uint16, uint32, uint64, uint128
from chia.wallet.conditions import Condition, UnknownCondition
from chia.wallet.conditions import Condition, UnknownCondition, parse_timelock_info
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.did_wallet import did_wallet_puzzles
from chia.wallet.did_wallet.did_info import DIDInfo
@@ -672,6 +672,7 @@ class NFTWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
),
]
+15 -1
View File
@@ -17,7 +17,7 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.db_wrapper import DBWrapper2
from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64
from chia.wallet.conditions import Condition
from chia.wallet.conditions import Condition, ConditionValidTimes, parse_conditions_non_consensus, parse_timelock_info
from chia.wallet.db_wallet.db_wallet_puzzles import ACS_MU_PH
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.outer_puzzles import AssetType
@@ -255,6 +255,7 @@ class TradeManager:
continue
cancellation_additions: List[Coin] = []
valid_times: ConditionValidTimes = parse_timelock_info(extra_conditions)
for coin in Offer.from_bytes(trade.offer).get_cancellation_coins():
wallet = await self.wallet_state_manager.get_wallet_for_coin(coin.name())
@@ -329,6 +330,7 @@ class TradeManager:
type=uint32(TransactionType.INCOMING_TX.value),
name=cancellation_additions[0].name(),
memos=[],
valid_times=valid_times,
)
)
@@ -397,6 +399,7 @@ class TradeManager:
trade_id=created_offer.name(),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
sent_to=[],
valid_times=parse_timelock_info(extra_conditions),
)
if success is True and trade_offer is not None and not validate_only:
@@ -617,6 +620,13 @@ class TradeManager:
additions_dict.update({id: hc.coin for id, hc in hinted_coins.items()})
removals: List[Coin] = final_spend_bundle.removals()
additions: List[Coin] = list(a for a in additions_dict.values() if a not in removals)
valid_times: ConditionValidTimes = parse_timelock_info(
parse_conditions_non_consensus(
condition
for spend in final_spend_bundle.coin_spends
for condition in spend.puzzle_reveal.to_program().run(spend.solution.to_program()).as_iter()
)
)
all_fees = uint64(final_spend_bundle.fees())
txs = []
@@ -649,6 +659,7 @@ class TradeManager:
type=uint32(TransactionType.INCOMING_TRADE.value),
name=std_hash(final_spend_bundle.name() + addition.name()),
memos=[(coin_id, [hint]) for coin_id, hint in hint_dict.items()],
valid_times=valid_times,
)
)
else: # This is change
@@ -696,6 +707,7 @@ class TradeManager:
type=uint32(TransactionType.OUTGOING_TRADE.value),
name=std_hash(final_spend_bundle.name() + removal_tree_hash),
memos=[(coin_id, [hint]) for coin_id, hint in hint_dict.items()],
valid_times=valid_times,
)
)
@@ -772,6 +784,7 @@ class TradeManager:
trade_id=complete_offer.name(),
status=uint32(TradeStatus.PENDING_CONFIRM.value),
sent_to=[],
valid_times=parse_timelock_info(extra_conditions),
)
await self.save_trade(trade_record, offer)
@@ -794,6 +807,7 @@ class TradeManager:
type=uint32(TransactionType.OUTGOING_TRADE.value),
name=final_spend_bundle.name(),
memos=[],
valid_times=ConditionValidTimes(),
)
await self.wallet_state_manager.add_pending_transaction(push_tx)
for tx in tx_records:
+14 -3
View File
@@ -1,19 +1,22 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.ints import uint8, uint32, uint64
from chia.util.streamable import Streamable, streamable
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
_T_TradeRecord = TypeVar("_T_TradeRecord", bound="TradeRecordOld")
@streamable
@dataclass(frozen=True)
class TradeRecord(Streamable):
class TradeRecordOld(Streamable):
"""
Used for storing transaction data and status in wallets.
"""
@@ -47,10 +50,18 @@ class TradeRecord(Streamable):
return formatted
@classmethod
def from_json_dict_convenience(cls, record: Dict[str, Any], offer: str = "") -> "TradeRecord":
def from_json_dict_convenience(
cls: Type[_T_TradeRecord], record: Dict[str, Any], offer: str = ""
) -> _T_TradeRecord:
new_record = record.copy()
new_record["status"] = TradeStatus[record["status"]].value
del new_record["summary"]
del new_record["pending"]
new_record["offer"] = offer
return cls.from_json_dict(new_record)
@streamable
@dataclass(frozen=True)
class TradeRecord(TradeRecordOld):
valid_times: ConditionValidTimes
+1
View File
@@ -10,3 +10,4 @@ class TradeStatus(Enum):
CANCELLED = 3
CONFIRMED = 4
FAILED = 5
EXPIRED = 6
+54 -72
View File
@@ -1,8 +1,9 @@
from __future__ import annotations
import dataclasses
import logging
from time import perf_counter
from typing import List, Optional, Set, Tuple
from typing import Dict, List, Optional, Set, Tuple
import aiosqlite
@@ -11,7 +12,8 @@ from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.db_wrapper import DBWrapper2
from chia.util.errors import Err
from chia.util.ints import uint8, uint32
from chia.wallet.trade_record import TradeRecord
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.trade_record import TradeRecord, TradeRecordOld
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
@@ -24,7 +26,7 @@ async def migrate_coin_of_interest(log: logging.Logger, db: aiosqlite.Connection
inserts: List[Tuple[bytes32, bytes32]] = []
for row in rows:
record: TradeRecord = TradeRecord.from_bytes(row[0])
record: TradeRecordOld = TradeRecordOld.from_bytes(row[0])
for coin in record.coins_of_interest:
inserts.append((coin.name(), record.trade_id))
@@ -44,7 +46,7 @@ async def migrate_coin_of_interest(log: logging.Logger, db: aiosqlite.Connection
)
async def migrate_is_my_offer(log: logging.Logger, db_connection: aiosqlite.Connection) -> None:
async def migrate_is_my_offer(log: logging.Logger, db_connection: aiosqlite.Connection) -> None: # pragma: no cover
"""
Migrate the is_my_offer property contained in the serialized TradeRecord (trade_record column)
to the is_my_offer column in the trade_records table.
@@ -58,7 +60,7 @@ async def migrate_is_my_offer(log: logging.Logger, db_connection: aiosqlite.Conn
updates: List[Tuple[int, str]] = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
record = TradeRecordOld.from_bytes(row[0])
is_my_offer = 1 if record.is_my_offer else 0
updates.append((is_my_offer, row[1]))
@@ -140,6 +142,17 @@ class TradeStore:
except aiosqlite.OperationalError:
pass # ignore what is likely Duplicate column error
try:
await conn.execute("CREATE TABLE trade_record_times(trade_id blob PRIMARY KEY, valid_times blob)")
async with await conn.execute("SELECT trade_id from trade_records") as cursor:
trade_ids: List[bytes32] = [bytes32.from_hexstr(row[0]) for row in await cursor.fetchall()]
await conn.executemany(
"INSERT INTO trade_record_times (trade_id, valid_times) VALUES(?, ?)",
[(id, bytes(ConditionValidTimes())) for id in trade_ids],
)
except aiosqlite.OperationalError:
pass # ignore what is likely Duplicate table error
await conn.execute("CREATE INDEX IF NOT EXISTS trade_confirmed_index on trade_records(confirmed_at_index)")
await conn.execute("CREATE INDEX IF NOT EXISTS trade_status on trade_records(status)")
await conn.execute("CREATE INDEX IF NOT EXISTS trade_id on trade_records(trade_id)")
@@ -167,7 +180,9 @@ class TradeStore:
"(trade_record, trade_id, status, confirmed_at_index, created_at_time, sent, offer_name, is_my_offer) "
"VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
(
bytes(record),
bytes(
TradeRecordOld(**{k: v for k, v in dataclasses.asdict(record).items() if k != "valid_times"})
),
record.trade_id.hex(),
record.status,
record.confirmed_at_index,
@@ -178,6 +193,14 @@ class TradeStore:
),
)
await cursor.close()
cursor = await conn.execute(
"INSERT OR REPLACE INTO trade_record_times " "(trade_id, valid_times) " "VALUES(?, ?)",
(
record.trade_id,
bytes(record.valid_times),
),
)
await cursor.close()
# remove all current coin ids
await conn.execute("DELETE FROM coin_of_interest_to_trade_record WHERE trade_id=?", (record.trade_id,))
# now recreate them all
@@ -217,6 +240,7 @@ class TradeStore:
trade_id=current.trade_id,
status=uint32(status.value),
sent_to=current.sent_to,
valid_times=current.valid_times,
)
await self.add_trade_record(tx, offer_name)
@@ -254,6 +278,7 @@ class TradeStore:
trade_id=current.trade_id,
status=current.status,
sent_to=sent_to,
valid_times=current.valid_times,
)
offer = Offer.from_bytes(current.offer)
await self.add_trade_record(tx, offer.name())
@@ -296,8 +321,7 @@ class TradeStore:
row = await cursor.fetchone()
await cursor.close()
if row is not None:
record: TradeRecord = TradeRecord.from_bytes(row[0])
return record
return (await self._get_new_trade_records_from_old([TradeRecordOld.from_bytes(row[0])]))[0]
return None
async def get_trade_record_with_status(self, status: TradeStatus) -> List[TradeRecord]:
@@ -308,12 +332,8 @@ class TradeStore:
cursor = await conn.execute("SELECT trade_record from trade_records WHERE status=?", (status.value,))
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
return await self._get_new_trade_records_from_old([TradeRecordOld.from_bytes(row[0]) for row in rows])
async def get_coin_ids_of_interest_with_trade_statuses(self, trade_statuses: List[TradeStatus]) -> Set[bytes32]:
"""
@@ -330,39 +350,6 @@ class TradeStore:
)
return {bytes32(row[0]) for row in rows}
async def get_not_sent(self) -> List[TradeRecord]:
"""
Returns the list of trades that have not been received by full node yet.
"""
async with self.db_wrapper.reader_no_transaction() as conn:
cursor = await conn.execute("SELECT trade_record from trade_records WHERE sent<? and confirmed=?", (4, 0))
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
async def get_all_unconfirmed(self) -> List[TradeRecord]:
"""
Returns the list of all trades that have not yet been confirmed.
"""
async with self.db_wrapper.reader_no_transaction() as conn:
cursor = await conn.execute("SELECT trade_record from trade_records WHERE confirmed=?", (0,))
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
async def get_all_trades(self) -> List[TradeRecord]:
"""
Returns all stored trades.
@@ -372,13 +359,8 @@ class TradeStore:
cursor = await conn.execute("SELECT trade_record from trade_records")
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
return await self._get_new_trade_records_from_old([TradeRecordOld.from_bytes(row[0]) for row in rows])
async def get_trades_between(
self,
@@ -481,29 +463,29 @@ class TradeStore:
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
async def get_trades_above(self, height: uint32) -> List[TradeRecord]:
async with self.db_wrapper.reader_no_transaction() as conn:
cursor = await conn.execute("SELECT trade_record from trade_records WHERE confirmed_at_index>?", (height,))
rows = await cursor.fetchall()
await cursor.close()
records = []
for row in rows:
record = TradeRecord.from_bytes(row[0])
records.append(record)
return records
return await self._get_new_trade_records_from_old([TradeRecordOld.from_bytes(row[0]) for row in rows])
async def rollback_to_block(self, block_index: int) -> None:
async with self.db_wrapper.writer_maybe_transaction() as conn:
# Delete from storage
cursor = await conn.execute("DELETE FROM trade_records WHERE confirmed_at_index>?", (block_index,))
await cursor.close()
async def _get_new_trade_records_from_old(self, old_records: List[TradeRecordOld]) -> List[TradeRecord]:
async with self.db_wrapper.reader_no_transaction() as conn:
cursor = await conn.execute(
"SELECT trade_id, valid_times from trade_record_times WHERE "
f"trade_id IN ({','.join('?' * len(old_records))})",
tuple(trade.trade_id for trade in old_records),
)
valid_times: Dict[bytes32, ConditionValidTimes] = {
bytes32(res[0]): ConditionValidTimes.from_bytes(res[1]) for res in await cursor.fetchall()
}
await cursor.close()
return [
TradeRecord(
valid_times=valid_times[record.trade_id] if record.trade_id in valid_times else ConditionValidTimes(),
**dataclasses.asdict(record),
)
for record in old_records
]
+11 -3
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Generic, List, Optional, Tuple, TypeVar
from typing import Dict, Generic, List, Optional, Tuple, Type, TypeVar
from chia.consensus.coinbase import farmer_parent_id, pool_parent_id
from chia.types.blockchain_format.coin import Coin
@@ -12,9 +12,11 @@ from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash
from chia.util.errors import Err
from chia.util.ints import uint8, uint32, uint64
from chia.util.streamable import Streamable, streamable
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.util.transaction_type import TransactionType
T = TypeVar("T")
_T_TransactionRecord = TypeVar("_T_TransactionRecord", bound="TransactionRecordOld")
minimum_send_attempts = 6
@@ -27,7 +29,7 @@ class ItemAndTransactionRecords(Generic[T]):
@streamable
@dataclass(frozen=True)
class TransactionRecord(Streamable):
class TransactionRecordOld(Streamable):
"""
Used for storing transaction data and status in wallets.
"""
@@ -81,7 +83,7 @@ class TransactionRecord(Streamable):
return {coin_id: ms for coin_id, ms in self.memos}
@classmethod
def from_json_dict_convenience(cls, modified_tx_input: Dict):
def from_json_dict_convenience(cls: Type[_T_TransactionRecord], modified_tx_input: Dict) -> _T_TransactionRecord:
modified_tx = modified_tx_input.copy()
if "to_address" in modified_tx:
modified_tx["to_puzzle_hash"] = decode_puzzle_hash(modified_tx["to_address"]).hex()
@@ -127,3 +129,9 @@ class TransactionRecord(Streamable):
def hint_dict(self) -> Dict[bytes32, bytes32]:
return {coin_id: bytes32(memos[0]) for coin_id, memos in self.memos if len(memos) > 0 and len(memos[0]) == 32}
@streamable
@dataclass(frozen=True)
class TransactionRecord(TransactionRecordOld):
valid_times: ConditionValidTimes
+6 -1
View File
@@ -24,7 +24,7 @@ from chia.wallet.cat_wallet.cat_info import CATCoinData, CRCATInfo
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.coin_selection import select_coins
from chia.wallet.conditions import Condition, UnknownCondition
from chia.wallet.conditions import Condition, ConditionValidTimes, UnknownCondition, parse_timelock_info
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.outer_puzzles import AssetType
from chia.wallet.payment import Payment
@@ -249,6 +249,7 @@ class CRCATWallet(CATWallet):
type=uint32(TransactionType.INCOMING_CRCAT_PENDING),
name=coin.name(),
memos=list(memos.items()),
valid_times=ConditionValidTimes(),
)
await self.wallet_state_manager.tx_store.add_transaction_record(tx_record)
else: # pragma: no cover
@@ -694,6 +695,7 @@ class CRCATWallet(CATWallet):
type=uint32(TransactionType.OUTGOING_TX.value),
name=signed_spend_bundle.name(),
memos=list(compute_memos(signed_spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
for i, payment in enumerate(payments)
]
@@ -710,6 +712,7 @@ class CRCATWallet(CATWallet):
max_coin_amount: Optional[uint64] = None,
excluded_coin_amounts: Optional[List[uint64]] = None,
reuse_puzhash: Optional[bool] = None,
extra_conditions: Tuple[Condition, ...] = tuple(),
) -> List[TransactionRecord]:
# Select the relevant CR-CAT coins
crcat_records: Set[WalletCoinRecord] = await self.wallet_state_manager.coin_store.get_unspent_coins_for_wallet(
@@ -799,6 +802,7 @@ class CRCATWallet(CATWallet):
puzzle_announcements=set(crcat.expected_announcement() for crcat, _ in crcats_and_puzhashes),
coin_announcements={nonce},
coin_announcements_to_consume=set(expected_announcements),
extra_conditions=extra_conditions,
)
claim_bundle = SpendBundle.aggregate(
[claim_bundle, *(tx.spend_bundle for tx in vc_txs if tx.spend_bundle is not None)]
@@ -822,6 +826,7 @@ class CRCATWallet(CATWallet):
type=uint32(TransactionType.INCOMING_TX.value),
name=claim_bundle.name(),
memos=list(compute_memos(claim_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
),
*(dataclasses.replace(tx, spend_bundle=None) for tx in vc_txs),
*((dataclasses.replace(chia_tx, spend_bundle=None),) if chia_tx is not None else []),
+4 -1
View File
@@ -21,7 +21,7 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64, uint128
from chia.util.streamable import Streamable
from chia.wallet.conditions import Condition, UnknownCondition
from chia.wallet.conditions import Condition, UnknownCondition, parse_timelock_info
from chia.wallet.did_wallet.did_wallet import DIDWallet
from chia.wallet.payment import Payment
from chia.wallet.puzzle_drivers import Solver
@@ -216,6 +216,7 @@ class VCWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
return vc_record, [tx]
@@ -356,6 +357,7 @@ class VCWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
)
return tx_list
@@ -434,6 +436,7 @@ class VCWallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=final_bundle.name(),
memos=list(compute_memos(final_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
if fee > 0:
chia_tx: TransactionRecord = await self.wallet_state_manager.main_wallet.create_tandem_xch_tx(
+2 -1
View File
@@ -18,7 +18,7 @@ from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64, uint128
from chia.util.streamable import Streamable
from chia.wallet.coin_selection import select_coins
from chia.wallet.conditions import Condition
from chia.wallet.conditions import Condition, parse_timelock_info
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.payment import Payment
from chia.wallet.puzzles.clawback.metadata import ClawbackMetadata
@@ -495,6 +495,7 @@ class Wallet:
type=uint32(TransactionType.OUTGOING_TX.value),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
async def create_tandem_xch_tx(
+3 -1
View File
@@ -269,7 +269,7 @@ class WalletNode:
async def reset_sync_db(self, db_path: Union[Path, str], fingerprint: int) -> bool:
conn: aiosqlite.Connection
# are not part of core wallet tables, but might appear later
ignore_tables = {"lineage_proofs_", "sqlite_"}
ignore_tables = {"lineage_proofs_", "sqlite_", "MIGRATED_VALID_TIMES_TXS", "MIGRATED_VALID_TIMES_TRADES"}
required_tables = [
"coin_record",
"transaction_record",
@@ -280,6 +280,8 @@ class WalletNode:
"all_notification_ids",
"key_val_store",
"trade_records",
"trade_record_times",
"tx_times",
"pool_state_transitions",
"singleton_records",
"mirrors",
+7 -1
View File
@@ -51,7 +51,7 @@ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS
from chia.wallet.cat_wallet.cat_info import CATCoinData, CATInfo, CRCATInfo
from chia.wallet.cat_wallet.cat_utils import CAT_MOD, CAT_MOD_HASH, construct_cat_puzzle, match_cat_puzzle
from chia.wallet.cat_wallet.cat_wallet import CATWallet
from chia.wallet.conditions import Condition
from chia.wallet.conditions import Condition, ConditionValidTimes, parse_timelock_info
from chia.wallet.db_wallet.db_wallet_puzzles import MIRROR_PUZZLE_HASH
from chia.wallet.derivation_record import DerivationRecord
from chia.wallet.derive_keys import (
@@ -896,6 +896,7 @@ class WalletStateManager:
type=uint32(TransactionType.OUTGOING_CLAWBACK),
name=spend_bundle.name(),
memos=list(compute_memos(spend_bundle).items()),
valid_times=parse_timelock_info(extra_conditions),
)
await self.add_pending_transaction(tx_record)
# Update incoming tx to prevent double spend and mark it is pending
@@ -1335,6 +1336,7 @@ class WalletStateManager:
type=uint32(TransactionType.OUTGOING_CLAWBACK),
name=clawback_spend_bundle.name(),
memos=list(compute_memos(clawback_spend_bundle).items()),
valid_times=ConditionValidTimes(),
)
await self.tx_store.add_transaction_record(tx_record)
coin_record = WalletCoinRecord(
@@ -1376,6 +1378,7 @@ class WalletStateManager:
# Use coin ID as the TX ID to mapping with the coin table
name=coin_record.coin.name(),
memos=list(memos.items()),
valid_times=ConditionValidTimes(),
)
await self.tx_store.add_transaction_record(tx_record)
return None
@@ -1561,6 +1564,7 @@ class WalletStateManager:
type=uint32(tx_type),
name=bytes32(token_bytes()),
memos=[],
valid_times=ConditionValidTimes(),
)
await self.tx_store.add_transaction_record(tx_record)
@@ -1640,6 +1644,7 @@ class WalletStateManager:
type=uint32(TransactionType.OUTGOING_TX.value),
name=tx_name,
memos=[],
valid_times=ConditionValidTimes(),
)
await self.tx_store.add_transaction_record(tx_record)
@@ -1958,6 +1963,7 @@ class WalletStateManager:
type=uint32(tx_type),
name=coin_name,
memos=[],
valid_times=ConditionValidTimes(),
)
if tx_record.amount > 0:
await self.tx_store.add_transaction_record(tx_record)
+61 -12
View File
@@ -5,12 +5,15 @@ import logging
import time
from typing import Dict, List, Optional, Tuple
import aiosqlite
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.db_wrapper import DBWrapper2
from chia.util.errors import Err
from chia.util.ints import uint8, uint32
from chia.wallet.transaction_record import TransactionRecord, minimum_send_attempts
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.transaction_record import TransactionRecord, TransactionRecordOld, minimum_send_attempts
from chia.wallet.transaction_sorting import SortKey
from chia.wallet.util.query_filter import FilterMode, TransactionTypeFilter
from chia.wallet.util.transaction_type import TransactionType
@@ -81,6 +84,17 @@ class WalletTransactionStore:
"CREATE INDEX IF NOT EXISTS transaction_record_wallet_id on transaction_record(wallet_id)"
)
try:
await conn.execute("CREATE TABLE tx_times(txid blob PRIMARY KEY, valid_times blob)")
async with await conn.execute("SELECT bundle_id from transaction_record") as cursor:
txids: List[bytes32] = [bytes32(row[0]) for row in await cursor.fetchall()]
await conn.executemany(
"INSERT INTO tx_times (txid, valid_times) VALUES(?, ?)",
[(id, bytes(ConditionValidTimes())) for id in txids],
)
except aiosqlite.OperationalError:
pass # ignore what is likely Duplicate table error
self.tx_submitted = {}
self.last_wallet_tx_resend_time = int(time.time())
return self
@@ -93,7 +107,16 @@ class WalletTransactionStore:
await conn.execute_insert(
"INSERT OR REPLACE INTO transaction_record VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
bytes(record),
bytes(
TransactionRecordOld(
spend_bundle=record.spend_bundle,
**{
k: v
for k, v in dataclasses.asdict(record).items()
if k not in ("valid_times", "spend_bundle")
},
)
),
record.name,
record.confirmed_at_height,
record.created_at_time,
@@ -107,6 +130,13 @@ class WalletTransactionStore:
record.type,
),
)
await conn.execute_insert(
"INSERT OR REPLACE INTO tx_times " "(txid, valid_times) " "VALUES(?, ?)",
(
record.name,
bytes(record.valid_times),
),
)
async def delete_transaction_record(self, tx_id: bytes32) -> None:
async with self.db_wrapper.writer_maybe_transaction() as conn:
@@ -184,7 +214,7 @@ class WalletTransactionStore:
)
)
if len(rows) > 0:
return TransactionRecord.from_bytes(rows[0][0])
return (await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(rows[0][0])]))[0]
return None
# TODO: This should probably be split into separate function, one that
@@ -203,7 +233,7 @@ class WalletTransactionStore:
records = []
for row in rows:
record = TransactionRecord.from_bytes(row[0])
record = (await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0])]))[0]
if include_accepted_txs:
# Reset the "sent" state for peers that have replied about this transaction. Retain errors.
record = dataclasses.replace(record, sent=1, sent_to=filter_ok_mempool_status(record.sent_to))
@@ -236,7 +266,7 @@ class WalletTransactionStore:
"SELECT transaction_record from transaction_record WHERE confirmed=1 and (type=? or type=?)",
(fee_int, pool_int),
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_all_unconfirmed(self) -> List[TransactionRecord]:
"""
@@ -244,7 +274,7 @@ class WalletTransactionStore:
"""
async with self.db_wrapper.reader_no_transaction() as conn:
rows = await conn.execute_fetchall("SELECT transaction_record from transaction_record WHERE confirmed=0")
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_unconfirmed_for_wallet(self, wallet_id: int) -> List[TransactionRecord]:
"""
@@ -254,7 +284,7 @@ class WalletTransactionStore:
rows = await conn.execute_fetchall(
"SELECT transaction_record from transaction_record WHERE confirmed=0 AND wallet_id=?", (wallet_id,)
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_transactions_between(
self,
@@ -307,7 +337,7 @@ class WalletTransactionStore:
(wallet_id,),
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_transaction_count_for_wallet(
self,
@@ -352,7 +382,7 @@ class WalletTransactionStore:
type,
),
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_all_transactions(self) -> List[TransactionRecord]:
"""
@@ -360,7 +390,7 @@ class WalletTransactionStore:
"""
async with self.db_wrapper.reader_no_transaction() as conn:
rows = await conn.execute_fetchall("SELECT transaction_record from transaction_record")
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_transaction_above(self, height: int) -> List[TransactionRecord]:
# Can be -1 (get all tx)
@@ -369,14 +399,14 @@ class WalletTransactionStore:
rows = await conn.execute_fetchall(
"SELECT transaction_record from transaction_record WHERE confirmed_at_height>?", (height,)
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def get_transactions_by_trade_id(self, trade_id: bytes32) -> List[TransactionRecord]:
async with self.db_wrapper.reader_no_transaction() as conn:
rows = await conn.execute_fetchall(
"SELECT transaction_record from transaction_record WHERE trade_id=?", (trade_id,)
)
return [TransactionRecord.from_bytes(row[0]) for row in rows]
return await self._get_new_tx_records_from_old([TransactionRecordOld.from_bytes(row[0]) for row in rows])
async def rollback_to_block(self, height: int):
# Delete from storage
@@ -396,3 +426,22 @@ class WalletTransactionStore:
),
)
).close()
async def _get_new_tx_records_from_old(self, old_records: List[TransactionRecordOld]) -> List[TransactionRecord]:
async with self.db_wrapper.reader_no_transaction() as conn:
cursor = await conn.execute(
f"SELECT txid, valid_times from tx_times WHERE txid IN ({','.join('?' * len(old_records))})",
tuple(tx.name for tx in old_records),
)
valid_times: Dict[bytes32, ConditionValidTimes] = {
bytes32(res[0]): ConditionValidTimes.from_bytes(res[1]) for res in await cursor.fetchall()
}
await cursor.close()
return [
TransactionRecord(
valid_times=valid_times[record.name] if record.name in valid_times else ConditionValidTimes(),
spend_bundle=record.spend_bundle,
**{k: v for k, v in dataclasses.asdict(record).items() if k != "spend_bundle"},
)
for record in old_records
]
+3
View File
@@ -27,6 +27,7 @@ from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import encode_puzzle_hash
from chia.util.config import load_config
from chia.util.ints import uint8, uint16, uint32, uint64
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.nft_wallet.nft_info import NFTInfo
from chia.wallet.nft_wallet.nft_wallet import NFTWallet
from chia.wallet.transaction_record import TransactionRecord
@@ -126,6 +127,7 @@ class TestWalletRpcClient(TestRpcClient):
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32([2] * 32),
memos=[(bytes32([3] * 32), [bytes([4] * 32)])],
valid_times=ConditionValidTimes(),
)
async def get_cat_name(self, wallet_id: int) -> str:
@@ -270,6 +272,7 @@ class TestWalletRpcClient(TestRpcClient):
type=uint32(TransactionType.OUTGOING_TX.value),
name=bytes32([2] * 32),
memos=[(bytes32([3] * 32), [bytes([4] * 32)])],
valid_times=ConditionValidTimes(),
)
+2
View File
@@ -6,6 +6,7 @@ from chia_rs import Coin
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint8, uint32, uint64
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.transaction_record import TransactionRecord
from chia.wallet.util.transaction_type import TransactionType
@@ -39,4 +40,5 @@ STD_TX = TransactionRecord(
type=uint32(TransactionType.OUTGOING_TX.value),
name=get_bytes32(2),
memos=[(get_bytes32(3), [bytes([4] * 32)])],
valid_times=ConditionValidTimes(),
)
+7
View File
@@ -13,6 +13,7 @@ from chia.types.signing_mode import SigningMode
from chia.types.spend_bundle import SpendBundle
from chia.util.bech32m import encode_puzzle_hash
from chia.util.ints import uint8, uint32, uint64
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.trade_record import TradeRecord
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
@@ -121,6 +122,7 @@ def test_get_transactions(capsys: object, get_test_cli_clients: Tuple[TestRpcCli
type=uint32(t_type.value),
name=bytes32([2 + i] * 32),
memos=[(bytes32([3 + i] * 32), [bytes([4 + i] * 32)])],
valid_times=ConditionValidTimes(),
)
l_tx_rec.append(tx_rec)
@@ -330,6 +332,7 @@ def test_send(capsys: object, get_test_cli_clients: Tuple[TestRpcClients, Path])
type=uint32(TransactionType.OUTGOING_CLAWBACK.value),
name=get_bytes32(2),
memos=[(get_bytes32(3), [bytes([4] * 32)])],
valid_times=ConditionValidTimes(),
)
return tx_rec
@@ -660,6 +663,7 @@ def test_make_offer(capsys: object, get_test_cli_clients: Tuple[TestRpcClients,
coins_of_interest=[],
trade_id=get_bytes32(2),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
valid_times=ConditionValidTimes(),
)
return created_offer, trade_offer
@@ -796,6 +800,7 @@ def test_get_offers(capsys: object, get_test_cli_clients: Tuple[TestRpcClients,
],
trade_id=bytes32([1 + i] * 32),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
valid_times=ConditionValidTimes(),
)
records.append(trade_offer)
return records
@@ -856,6 +861,7 @@ def test_take_offer(capsys: object, get_test_cli_clients: Tuple[TestRpcClients,
coins_of_interest=offer.get_involved_coins(),
trade_id=offer.name(),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
valid_times=ConditionValidTimes(),
)
inst_rpc_client = TakeOfferRpcClient() # pylint: disable=no-value-for-parameter
@@ -905,6 +911,7 @@ def test_cancel_offer(capsys: object, get_test_cli_clients: Tuple[TestRpcClients
coins_of_interest=offer.get_involved_coins(),
trade_id=offer.name(),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
valid_times=ConditionValidTimes(),
)
async def cancel_offer(
+69 -1
View File
@@ -11,7 +11,8 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.errors import Err
from chia.util.ints import uint8, uint32, uint64
from chia.wallet.transaction_record import TransactionRecord, minimum_send_attempts
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.transaction_record import TransactionRecord, TransactionRecordOld, minimum_send_attempts
from chia.wallet.util.query_filter import TransactionTypeFilter
from chia.wallet.util.transaction_type import TransactionType
from chia.wallet.wallet_transaction_store import WalletTransactionStore, filter_ok_mempool_status
@@ -38,6 +39,7 @@ tr1 = TransactionRecord(
uint32(TransactionType.OUTGOING_TX), # type
bytes32(token_bytes(32)), # name
[], # List[Tuple[bytes32, List[bytes]]] memos
ConditionValidTimes(),
)
@@ -738,3 +740,69 @@ async def test_transaction_record_is_valid() -> None:
assert dataclasses.replace(tr1, sent_to=invalid_attempts + [mempool_success]).is_valid()
assert dataclasses.replace(tr1, sent_to=invalid_attempts + [low_fee]).is_valid()
assert dataclasses.replace(tr1, sent_to=invalid_attempts + [close_to_zero]).is_valid()
@pytest.mark.asyncio
async def test_valid_times_migration() -> None:
async with DBConnection(1) as db_wrapper:
async with db_wrapper.writer_maybe_transaction() as conn:
await conn.execute(
(
"CREATE TABLE IF NOT EXISTS transaction_record("
" transaction_record blob,"
" bundle_id text PRIMARY KEY,"
" confirmed_at_height bigint,"
" created_at_time bigint,"
" to_puzzle_hash text,"
" amount blob,"
" fee_amount blob,"
" confirmed int,"
" sent int,"
" wallet_id bigint,"
" trade_id text,"
" type int)"
)
)
old_record = TransactionRecordOld(
confirmed_at_height=uint32(0),
created_at_time=uint64(1000000000),
to_puzzle_hash=bytes32([0] * 32),
amount=uint64(0),
fee_amount=uint64(0),
confirmed=False,
sent=uint32(10),
spend_bundle=None,
additions=[],
removals=[],
wallet_id=uint32(1),
sent_to=[],
trade_id=None,
type=uint32(TransactionType.INCOMING_TX.value),
name=bytes32([0] * 32),
memos=[],
)
async with db_wrapper.writer_maybe_transaction() as conn:
await conn.execute_insert(
"INSERT OR REPLACE INTO transaction_record VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
bytes(old_record),
old_record.name,
old_record.confirmed_at_height,
old_record.created_at_time,
old_record.to_puzzle_hash.hex(),
bytes(old_record.amount),
bytes(old_record.fee_amount),
int(old_record.confirmed),
old_record.sent,
old_record.wallet_id,
old_record.trade_id,
old_record.type,
),
)
store = await WalletTransactionStore.create(db_wrapper)
rec = await store.get_transaction_record(old_record.name)
assert rec is not None
assert rec.valid_times == ConditionValidTimes()
+2
View File
@@ -22,6 +22,7 @@ from chia.types.coin_spend import compute_additions
from chia.types.peer_info import PeerInfo
from chia.util.bech32m import encode_puzzle_hash
from chia.util.ints import uint16, uint32, uint64
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.derive_keys import master_sk_to_wallet_sk
from chia.wallet.payment import Payment
from chia.wallet.transaction_record import TransactionRecord
@@ -1423,6 +1424,7 @@ class TestWalletSimulator:
type=uint32(TransactionType.OUTGOING_TX.value),
name=name,
memos=list(compute_memos(stolen_sb).items()),
valid_times=ConditionValidTimes(),
)
await wallet.push_transaction(stolen_tx)
+64 -1
View File
@@ -4,11 +4,15 @@ import time
from secrets import token_bytes
import pytest
from blspy import G2Element
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint32, uint64
from chia.wallet.trade_record import TradeRecord
from chia.wallet.conditions import ConditionValidTimes
from chia.wallet.trade_record import TradeRecord, TradeRecordOld
from chia.wallet.trading.offer import Offer
from chia.wallet.trading.trade_status import TradeStatus
from chia.wallet.trading.trade_store import TradeStore, migrate_coin_of_interest
from chia.wallet.util.wallet_types import WalletType
@@ -46,6 +50,7 @@ async def test_get_coins_of_interest_with_trade_statuses() -> None:
trade_id=tr1_name,
status=uint32(TradeStatus.PENDING_ACCEPT.value),
sent_to=[],
valid_times=ConditionValidTimes(),
)
await trade_store.add_trade_record(tr1, offer_name=bytes32(token_bytes(32)))
@@ -62,6 +67,7 @@ async def test_get_coins_of_interest_with_trade_statuses() -> None:
trade_id=tr2_name,
status=uint32(TradeStatus.PENDING_CONFIRM.value),
sent_to=[],
valid_times=ConditionValidTimes(),
)
await trade_store.add_trade_record(tr2, offer_name=bytes32(token_bytes(32)))
@@ -86,6 +92,7 @@ async def test_get_coins_of_interest_with_trade_statuses() -> None:
trade_id=tr2_name,
status=uint32(TradeStatus.PENDING_CONFIRM.value),
sent_to=[],
valid_times=ConditionValidTimes(),
)
await trade_store.add_trade_record(tr2_1, offer_name=bytes32(token_bytes(32)))
@@ -105,3 +112,59 @@ async def test_get_coins_of_interest_with_trade_statuses() -> None:
assert await trade_store.get_coin_ids_of_interest_with_trade_statuses([TradeStatus.PENDING_ACCEPT]) == {
coin_2.name()
}
@pytest.mark.asyncio
async def test_valid_times_migration() -> None:
async with DBConnection(1) as db_wrapper:
async with db_wrapper.writer_maybe_transaction() as conn:
await conn.execute(
(
"CREATE TABLE IF NOT EXISTS trade_records("
" trade_record blob,"
" trade_id text PRIMARY KEY,"
" status int,"
" confirmed_at_index int,"
" created_at_time bigint,"
" sent int,"
" is_my_offer tinyint)"
)
)
fake_offer = Offer({}, SpendBundle([], G2Element()), {})
fake_coin = Coin(bytes32([0] * 32), bytes32([0] * 32), uint64(0))
old_record = TradeRecordOld(
confirmed_at_index=uint32(0),
accepted_at_time=None,
created_at_time=uint64(1000000),
is_my_offer=True,
sent=uint32(0),
offer=bytes(fake_offer),
taken_offer=None,
coins_of_interest=[fake_coin],
trade_id=bytes32([0] * 32),
status=uint32(TradeStatus.PENDING_ACCEPT.value),
sent_to=[],
)
async with db_wrapper.writer_maybe_transaction() as conn:
cursor = await conn.execute(
"INSERT INTO trade_records "
"(trade_record, trade_id, status, confirmed_at_index, created_at_time, sent, is_my_offer) "
"VALUES(?, ?, ?, ?, ?, ?, ?)",
(
bytes(old_record),
old_record.trade_id.hex(),
old_record.status,
old_record.confirmed_at_index,
old_record.created_at_time,
old_record.sent,
old_record.is_my_offer,
),
)
await cursor.close()
trade_store = await TradeStore.create(db_wrapper)
rec = await trade_store.get_trade_record(old_record.trade_id)
assert rec is not None
assert rec.valid_times == ConditionValidTimes()