mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
Add timelock information to Trades and Transactions (#15915)
This commit is contained in:
@@ -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),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,3 +10,4 @@ class TradeStatus(Enum):
|
||||
CANCELLED = 3
|
||||
CONFIRMED = 4
|
||||
FAILED = 5
|
||||
EXPIRED = 6
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 []),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user