mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-09-02 18:14:27 -05:00
Merge remote-tracking branch 'origin/main' into quex.remove_announcement_main
This commit is contained in:
@@ -19,6 +19,7 @@ from chia.cmds.cmds_util import (
|
||||
)
|
||||
from chia.cmds.peer_funcs import print_connections
|
||||
from chia.cmds.units import units
|
||||
from chia.rpc.wallet_request_types import GetNotifications
|
||||
from chia.rpc.wallet_rpc_client import WalletRpcClient
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.bech32m import bech32_decode, decode_puzzle_hash, encode_puzzle_hash
|
||||
@@ -1388,10 +1389,12 @@ async def get_notifications(
|
||||
if ids is not None and len(ids) == 0:
|
||||
ids = None
|
||||
|
||||
notifications = await wallet_client.get_notifications(ids=ids, pagination=(start, end))
|
||||
for notification in notifications:
|
||||
response = await wallet_client.get_notifications(
|
||||
GetNotifications(ids=ids, start=uint32.construct_optional(start), end=uint32.construct_optional(end))
|
||||
)
|
||||
for notification in response.notifications:
|
||||
print("")
|
||||
print(f"ID: {notification.coin_id.hex()}")
|
||||
print(f"ID: {notification.id.hex()}")
|
||||
print(f"message: {notification.message.decode('utf-8')}")
|
||||
print(f"amount: {notification.amount}")
|
||||
|
||||
|
||||
@@ -118,6 +118,13 @@ class ForkInfo:
|
||||
assert coin.name() not in self.additions_since_fork
|
||||
self.additions_since_fork[coin.name()] = ForkAdd(coin, uint32(block.height), uint64(timestamp), None, True)
|
||||
|
||||
def rollback(self, header_hash: bytes32, height: int) -> None:
|
||||
assert height <= self.peak_height
|
||||
self.peak_height = height
|
||||
self.peak_hash = header_hash
|
||||
self.additions_since_fork = {k: v for k, v in self.additions_since_fork.items() if v.confirmed_height <= height}
|
||||
self.removals_since_fork = {k: v for k, v in self.removals_since_fork.items() if v.height <= height}
|
||||
|
||||
|
||||
async def validate_block_body(
|
||||
constants: ConsensusConstants,
|
||||
@@ -401,6 +408,7 @@ async def validate_block_body(
|
||||
# and coins created after fork (additions_since_fork)
|
||||
if rem in fork_info.removals_since_fork:
|
||||
# This coin was spent in the fork
|
||||
log.error(f"Err.DOUBLE_SPEND_IN_FORK {fork_info.removals_since_fork[rem]}")
|
||||
return Err.DOUBLE_SPEND_IN_FORK, None
|
||||
removals_from_db.append(rem)
|
||||
|
||||
@@ -433,7 +441,7 @@ async def validate_block_body(
|
||||
# This coin is not in the current heaviest chain, so it must be in the fork
|
||||
if rem not in fork_info.additions_since_fork:
|
||||
# Check for spending a coin that does not exist in this fork
|
||||
log.error(f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} NPC RESULT: {npc_result}")
|
||||
log.error(f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} fork_info: {fork_info}")
|
||||
return Err.UNKNOWN_UNSPENT, None
|
||||
addition: ForkAdd = fork_info.additions_since_fork[rem]
|
||||
new_coin_record: CoinRecord = CoinRecord(
|
||||
|
||||
@@ -194,6 +194,26 @@ class Blockchain(BlockchainInterface):
|
||||
return None
|
||||
return self.height_to_block_record(self._peak_height)
|
||||
|
||||
def get_tx_peak(self) -> Optional[BlockRecord]:
|
||||
"""
|
||||
Return the most recent transaction block. i.e. closest to the peak of the blockchain
|
||||
Requires the blockchain to be initialized and there to be a peak set
|
||||
"""
|
||||
|
||||
if self._peak_height is None:
|
||||
return None
|
||||
tx_height = self._peak_height
|
||||
tx_peak = self.height_to_block_record(tx_height)
|
||||
while not tx_peak.is_transaction_block:
|
||||
# it seems BlockTools only produce chains where the first block is a
|
||||
# transaction block, which makes it hard to test this case
|
||||
if tx_height == 0: # pragma: no cover
|
||||
return None
|
||||
tx_height = uint32(tx_height - 1)
|
||||
tx_peak = self.height_to_block_record(tx_height)
|
||||
|
||||
return tx_peak
|
||||
|
||||
async def get_full_peak(self) -> Optional[FullBlock]:
|
||||
if self._peak_height is None:
|
||||
return None
|
||||
@@ -465,6 +485,13 @@ class Blockchain(BlockchainInterface):
|
||||
self._peak_height = block_record.height
|
||||
|
||||
except BaseException as e:
|
||||
# depending on exactly when the failure of adding the block
|
||||
# happened, we may not have added it to the block record cache
|
||||
try:
|
||||
self.remove_block_record(header_hash)
|
||||
except KeyError:
|
||||
pass
|
||||
fork_info.rollback(header_hash, -1 if previous_peak_height is None else previous_peak_height)
|
||||
self.block_store.rollback_cache_block(header_hash)
|
||||
self._peak_height = previous_peak_height
|
||||
log.error(
|
||||
|
||||
@@ -411,6 +411,97 @@ class CoinStore:
|
||||
|
||||
return coins
|
||||
|
||||
async def batch_coin_states_by_puzzle_hashes(
|
||||
self,
|
||||
puzzle_hashes: List[bytes32],
|
||||
*,
|
||||
min_height: uint32 = uint32(0),
|
||||
include_spent: bool = True,
|
||||
include_unspent: bool = True,
|
||||
include_hinted: bool = True,
|
||||
max_items: int = 50000,
|
||||
) -> Tuple[List[CoinState], Optional[uint32]]:
|
||||
"""
|
||||
Returns the coin states, as well as the next block height (or `None` if finished).
|
||||
Note that the maximum number of puzzle hashes is currently set to 15000.
|
||||
"""
|
||||
|
||||
# This number is chosen such that it's below half of the Python 3.8+ SQLite variable limit.
|
||||
# It can be changed later without breaking the protocol, but this is a practical limit for now.
|
||||
assert len(puzzle_hashes) <= 15000
|
||||
|
||||
coin_states: List[CoinState] = []
|
||||
|
||||
async with self.db_wrapper.reader_no_transaction() as conn:
|
||||
puzzle_hashes_db = tuple(puzzle_hashes)
|
||||
puzzle_hash_count = len(puzzle_hashes_db)
|
||||
|
||||
if include_hinted:
|
||||
require_spent = "cr.spent_index>0"
|
||||
require_unspent = "cr.spent_index=0"
|
||||
else:
|
||||
require_spent = "spent_index>0"
|
||||
require_unspent = "spent_index=0"
|
||||
|
||||
if include_spent and include_unspent:
|
||||
height_filter = ""
|
||||
elif include_spent:
|
||||
height_filter = f"AND {require_spent}"
|
||||
elif include_unspent:
|
||||
height_filter = f"AND {require_unspent}"
|
||||
else:
|
||||
# There are no coins which are both spent and unspent, so we're finished.
|
||||
return [], None
|
||||
|
||||
if include_hinted:
|
||||
cursor = await conn.execute(
|
||||
f"SELECT cr.confirmed_index, cr.spent_index, cr.coinbase, cr.puzzle_hash, "
|
||||
f"cr.coin_parent, cr.amount, cr.timestamp FROM coin_record cr "
|
||||
f"LEFT JOIN hints h ON cr.coin_name = h.coin_id "
|
||||
f'WHERE (cr.puzzle_hash in ({"?," * (puzzle_hash_count - 1)}?) '
|
||||
f'OR h.hint in ({"?," * (puzzle_hash_count - 1)}?)) '
|
||||
f"AND (cr.confirmed_index>=? OR cr.spent_index>=?) "
|
||||
f"{height_filter} "
|
||||
f"ORDER BY MAX(cr.confirmed_index, cr.spent_index) ASC "
|
||||
f"LIMIT ?",
|
||||
puzzle_hashes_db + puzzle_hashes_db + (min_height, min_height, max_items + 1),
|
||||
)
|
||||
else:
|
||||
cursor = await conn.execute(
|
||||
f"SELECT confirmed_index, spent_index, coinbase, puzzle_hash, "
|
||||
f"coin_parent, amount, timestamp FROM coin_record INDEXED BY coin_puzzle_hash "
|
||||
f'WHERE puzzle_hash in ({"?," * (puzzle_hash_count - 1)}?) '
|
||||
f"AND (confirmed_index>=? OR spent_index>=?) "
|
||||
f"{height_filter} "
|
||||
f"ORDER BY MAX(confirmed_index, spent_index) ASC "
|
||||
f"LIMIT ?",
|
||||
puzzle_hashes_db + (min_height, min_height, max_items + 1),
|
||||
)
|
||||
|
||||
for row in await cursor.fetchall():
|
||||
coin_states.append(self.row_to_coin_state(row))
|
||||
|
||||
# If there aren't too many coin states, we've finished syncing these hashes.
|
||||
# There is no next height to start from, so return `None`.
|
||||
if len(coin_states) <= max_items:
|
||||
return coin_states, None
|
||||
|
||||
# The last item is the start of the next batch of coin states.
|
||||
next_coin_state = coin_states.pop()
|
||||
next_height = uint32(max(next_coin_state.created_height or 0, next_coin_state.spent_height or 0))
|
||||
|
||||
# In order to prevent blocks from being split up between batches, remove
|
||||
# all coin states whose max height is the same as the last coin state's height.
|
||||
while len(coin_states) > 0:
|
||||
last_coin_state = coin_states[-1]
|
||||
height = uint32(max(last_coin_state.created_height or 0, last_coin_state.spent_height or 0))
|
||||
if height != next_height:
|
||||
break
|
||||
|
||||
coin_states.pop()
|
||||
|
||||
return coin_states, next_height
|
||||
|
||||
async def rollback_to_block(self, block_index: int) -> List[CoinRecord]:
|
||||
"""
|
||||
Note that block_index can be negative, in which case everything is rolled back
|
||||
|
||||
@@ -316,7 +316,7 @@ class FullNode:
|
||||
f"time taken: {int(time_taken)}s"
|
||||
)
|
||||
async with self.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.high):
|
||||
pending_tx = await self.mempool_manager.new_peak(peak, None)
|
||||
pending_tx = await self.mempool_manager.new_peak(self.blockchain.get_tx_peak(), None)
|
||||
assert len(pending_tx) == 0 # no pending transactions when starting up
|
||||
|
||||
full_peak: Optional[FullBlock] = await self.blockchain.get_full_peak()
|
||||
@@ -1544,9 +1544,8 @@ class FullNode:
|
||||
|
||||
# Update the mempool (returns successful pending transactions added to the mempool)
|
||||
spent_coins: List[bytes32] = [coin_id for coin_id, _ in state_change_summary.removals]
|
||||
mempool_new_peak_result: List[Tuple[SpendBundle, NPCResult, bytes32]] = await self.mempool_manager.new_peak(
|
||||
self.blockchain.get_peak(), spent_coins
|
||||
)
|
||||
mempool_new_peak_result: List[Tuple[SpendBundle, NPCResult, bytes32]]
|
||||
mempool_new_peak_result = await self.mempool_manager.new_peak(self.blockchain.get_tx_peak(), spent_coins)
|
||||
|
||||
# Check if we detected a spent transaction, to load up our generator cache
|
||||
if block.transactions_generator is not None and self.full_node_store.previous_generator is None:
|
||||
|
||||
@@ -620,6 +620,11 @@ class MempoolManager:
|
||||
) -> List[Tuple[SpendBundle, NPCResult, bytes32]]:
|
||||
"""
|
||||
Called when a new peak is available, we try to recreate a mempool for the new tip.
|
||||
new_peak should always be the most recent *transaction* block of the chain. Since
|
||||
the mempool cannot traverse the chain to find the most recent transaction block,
|
||||
we wouldn't be able to detect, and correctly update the mempool, if we saw a
|
||||
non-transaction block on a fork. self.peak must always be set to a transaction
|
||||
block.
|
||||
"""
|
||||
if new_peak is None:
|
||||
return []
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.ints import uint32
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
from chia.wallet.notification_store import Notification
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class GetNotifications(Streamable):
|
||||
ids: Optional[List[bytes32]] = None
|
||||
start: Optional[uint32] = None
|
||||
end: Optional[uint32] = None
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclass(frozen=True)
|
||||
class GetNotificationsResponse(Streamable):
|
||||
notifications: List[Notification]
|
||||
@@ -18,7 +18,8 @@ from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletIn
|
||||
from chia.protocols.protocol_message_types import ProtocolMessageTypes
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.rpc.rpc_server import Endpoint, EndpointResult, default_get_connections
|
||||
from chia.rpc.util import tx_endpoint
|
||||
from chia.rpc.util import marshal, tx_endpoint
|
||||
from chia.rpc.wallet_request_types import GetNotifications, GetNotificationsResponse
|
||||
from chia.server.outbound_message import NodeType, make_msg
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol
|
||||
@@ -1413,34 +1414,22 @@ class WalletRpcApi:
|
||||
|
||||
return {"success": True, "index": updated_index}
|
||||
|
||||
async def get_notifications(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
ids: Optional[List[str]] = request.get("ids", None)
|
||||
start: Optional[int] = request.get("start", None)
|
||||
end: Optional[int] = request.get("end", None)
|
||||
if ids is None:
|
||||
@marshal
|
||||
async def get_notifications(self, request: GetNotifications) -> GetNotificationsResponse:
|
||||
if request.ids is None:
|
||||
notifications: List[
|
||||
Notification
|
||||
] = await self.service.wallet_state_manager.notification_manager.notification_store.get_all_notifications(
|
||||
pagination=(start, end)
|
||||
pagination=(request.start, request.end)
|
||||
)
|
||||
else:
|
||||
notifications = (
|
||||
await self.service.wallet_state_manager.notification_manager.notification_store.get_notifications(
|
||||
[bytes32.from_hexstr(id) for id in ids]
|
||||
request.ids
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"notifications": [
|
||||
{
|
||||
"id": notification.coin_id.hex(),
|
||||
"message": notification.message.hex(),
|
||||
"amount": notification.amount,
|
||||
"height": notification.height,
|
||||
}
|
||||
for notification in notifications
|
||||
]
|
||||
}
|
||||
return GetNotificationsResponse(notifications)
|
||||
|
||||
async def delete_notifications(self, request: Dict[str, Any]) -> EndpointResult:
|
||||
ids: Optional[List[str]] = request.get("ids", None)
|
||||
@@ -3058,11 +3047,7 @@ class WalletRpcApi:
|
||||
else:
|
||||
nfts = await self.service.wallet_state_manager.nft_store.get_nft_list(start_index=start_index, count=count)
|
||||
for nft in nfts:
|
||||
nft_info = await nft_puzzles.get_nft_info_from_puzzle(
|
||||
nft,
|
||||
self.service.wallet_state_manager.config,
|
||||
request.get("ignore_size_limit", False),
|
||||
)
|
||||
nft_info = await nft_puzzles.get_nft_info_from_puzzle(nft, self.service.wallet_state_manager.config)
|
||||
nft_info_list.append(nft_info)
|
||||
return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list}
|
||||
|
||||
@@ -3442,7 +3427,6 @@ class WalletRpcApi:
|
||||
uint32(coin_state.created_height) if coin_state.created_height else uint32(0),
|
||||
),
|
||||
self.service.wallet_state_manager.config,
|
||||
request.get("ignore_size_limit", False),
|
||||
)
|
||||
# This is a bit hacky, it should just come out like this, but this works for this RPC
|
||||
nft_info = dataclasses.replace(nft_info, p2_address=p2_puzzle_hash)
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
||||
from chia.data_layer.data_layer_wallet import Mirror, SingletonRecord
|
||||
from chia.pools.pool_wallet_info import PoolWalletInfo
|
||||
from chia.rpc.rpc_client import RpcClient
|
||||
from chia.rpc.wallet_request_types import GetNotifications, GetNotificationsResponse
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
@@ -13,7 +14,6 @@ from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.ints import uint16, uint32, uint64
|
||||
from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_to_json_dicts
|
||||
from chia.wallet.notification_store import Notification
|
||||
from chia.wallet.trade_record import TradeRecord
|
||||
from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
@@ -1197,27 +1197,8 @@ class WalletRpcClient(RpcClient):
|
||||
)
|
||||
return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]]
|
||||
|
||||
async def get_notifications(
|
||||
self, ids: Optional[List[bytes32]] = None, pagination: Optional[Tuple[Optional[int], Optional[int]]] = None
|
||||
) -> List[Notification]:
|
||||
request: Dict[str, Any] = {}
|
||||
if ids is not None:
|
||||
request["ids"] = [id.hex() for id in ids]
|
||||
if pagination is not None:
|
||||
if pagination[0] is not None:
|
||||
request["start"] = pagination[0]
|
||||
if pagination[1] is not None:
|
||||
request["end"] = pagination[1]
|
||||
response = await self.fetch("get_notifications", request)
|
||||
return [
|
||||
Notification(
|
||||
bytes32.from_hexstr(notification["id"]),
|
||||
bytes.fromhex(notification["message"]),
|
||||
uint64(notification["amount"]),
|
||||
uint32(notification["height"]),
|
||||
)
|
||||
for notification in response["notifications"]
|
||||
]
|
||||
async def get_notifications(self, request: GetNotifications) -> GetNotificationsResponse:
|
||||
return GetNotificationsResponse.from_json_dict(await self.fetch("get_notifications", request.to_json_dict()))
|
||||
|
||||
async def delete_notifications(self, ids: Optional[List[bytes32]] = None) -> bool:
|
||||
request = {}
|
||||
|
||||
@@ -16,7 +16,7 @@ from chia.server.outbound_message import NodeType
|
||||
from chia.server.start_service import RpcInfo, Service, async_run
|
||||
from chia.types.aliases import FullNodeService
|
||||
from chia.util.chia_logging import initialize_service_logging
|
||||
from chia.util.config import load_config, load_config_cli
|
||||
from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli
|
||||
from chia.util.default_root import DEFAULT_ROOT_PATH
|
||||
from chia.util.ints import uint16
|
||||
from chia.util.misc import SignalHandlers
|
||||
@@ -61,6 +61,7 @@ async def create_full_node_service(
|
||||
advertised_port=service_config["port"],
|
||||
service_name=SERVICE_NAME,
|
||||
upnp_ports=upnp_list,
|
||||
connect_peers=get_unresolved_peer_infos(service_config, NodeType.FULL_NODE),
|
||||
on_connect_callback=full_node.on_connect,
|
||||
network_id=network_id,
|
||||
rpc_info=rpc_info,
|
||||
|
||||
@@ -353,6 +353,8 @@ timelord:
|
||||
full_node:
|
||||
# The full node server (if run) will run on this port
|
||||
port: 8444
|
||||
# The full node will attempt to connect to these full nodes
|
||||
full_node_peers: []
|
||||
|
||||
# controls the sync-to-disk behavior of the database connection. Can be one of:
|
||||
# "on" enables syncing to disk, minimizes risk of corrupting the DB in
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, cast
|
||||
|
||||
from clvm.casts import int_from_bytes
|
||||
from clvm_tools.binutils import disassemble
|
||||
@@ -58,11 +58,7 @@ def create_nft_layer_puzzle_with_curry_params(
|
||||
|
||||
def create_full_puzzle_with_nft_puzzle(singleton_id: bytes32, inner_puzzle: Program) -> Program:
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
log.debug(
|
||||
"Creating full NFT puzzle with inner puzzle: \n%r\n%r",
|
||||
singleton_id,
|
||||
inner_puzzle.get_tree_hash(),
|
||||
)
|
||||
log.debug("Creating full NFT puzzle with inner puzzle: \n%r\n%r", singleton_id, inner_puzzle.get_tree_hash())
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (singleton_id, LAUNCHER_PUZZLE_HASH)))
|
||||
|
||||
full_puzzle = SINGLETON_TOP_LAYER_MOD.curry(singleton_struct, inner_puzzle)
|
||||
@@ -91,9 +87,7 @@ def create_full_puzzle(
|
||||
return full_puzzle
|
||||
|
||||
|
||||
async def get_nft_info_from_puzzle(
|
||||
nft_coin_info: NFTCoinInfo, config: Dict[str, Any], ignore_size_limit: bool = False
|
||||
) -> NFTInfo:
|
||||
async def get_nft_info_from_puzzle(nft_coin_info: NFTCoinInfo, config: Dict[str, Any]) -> NFTInfo:
|
||||
"""
|
||||
Extract NFT info from a full puzzle
|
||||
:param nft_coin_info NFTCoinInfo in local database
|
||||
@@ -175,7 +169,6 @@ def prepend_value(key: bytes, value: Program, metadata: Dict[bytes, Any]) -> Non
|
||||
:param metadata: Metadata
|
||||
:return:
|
||||
"""
|
||||
|
||||
if value != Program.to(0):
|
||||
if metadata[key] == b"":
|
||||
metadata[key] = [value.as_python()]
|
||||
@@ -201,12 +194,7 @@ def construct_ownership_layer(
|
||||
transfer_program: Program,
|
||||
inner_puzzle: Program,
|
||||
) -> Program:
|
||||
return NFT_OWNERSHIP_LAYER.curry(
|
||||
NFT_OWNERSHIP_LAYER_HASH,
|
||||
current_owner,
|
||||
transfer_program,
|
||||
inner_puzzle,
|
||||
)
|
||||
return NFT_OWNERSHIP_LAYER.curry(NFT_OWNERSHIP_LAYER_HASH, current_owner, transfer_program, inner_puzzle)
|
||||
|
||||
|
||||
def create_ownership_layer_puzzle(
|
||||
@@ -226,11 +214,7 @@ def create_ownership_layer_puzzle(
|
||||
singleton_struct = Program.to((SINGLETON_MOD_HASH, (nft_id, LAUNCHER_PUZZLE_HASH)))
|
||||
if not royalty_puzzle_hash:
|
||||
royalty_puzzle_hash = p2_puzzle.get_tree_hash()
|
||||
transfer_program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(
|
||||
singleton_struct,
|
||||
royalty_puzzle_hash,
|
||||
percentage,
|
||||
)
|
||||
transfer_program = NFT_TRANSFER_PROGRAM_DEFAULT.curry(singleton_struct, royalty_puzzle_hash, percentage)
|
||||
nft_inner_puzzle = p2_puzzle
|
||||
|
||||
nft_ownership_layer_puzzle = construct_ownership_layer(
|
||||
@@ -240,10 +224,7 @@ def create_ownership_layer_puzzle(
|
||||
|
||||
|
||||
def create_ownership_layer_transfer_solution(
|
||||
new_did: bytes,
|
||||
new_did_inner_hash: bytes,
|
||||
trade_prices_list: List[List[int]],
|
||||
new_puzhash: bytes32,
|
||||
new_did: bytes, new_did_inner_hash: bytes, trade_prices_list: List[List[int]], new_puzhash: bytes32
|
||||
) -> Program:
|
||||
log.debug(
|
||||
"Creating a transfer solution with: DID:%s Inner_puzhash:%s trade_price:%s puzhash:%s",
|
||||
@@ -252,23 +233,12 @@ def create_ownership_layer_transfer_solution(
|
||||
str(trade_prices_list),
|
||||
new_puzhash.hex(),
|
||||
)
|
||||
condition_list = [
|
||||
[
|
||||
51,
|
||||
new_puzhash,
|
||||
1,
|
||||
[new_puzhash],
|
||||
],
|
||||
[-10, new_did, trade_prices_list, new_did_inner_hash],
|
||||
]
|
||||
condition_list = [[51, new_puzhash, 1, [new_puzhash]], [-10, new_did, trade_prices_list, new_did_inner_hash]]
|
||||
log.debug("Condition list raw: %r", condition_list)
|
||||
solution = Program.to(
|
||||
[
|
||||
[solution_for_conditions(condition_list)],
|
||||
]
|
||||
)
|
||||
solution = Program.to([[solution_for_conditions(condition_list)]])
|
||||
log.debug("Generated transfer solution: %s", solution)
|
||||
return solution
|
||||
# TODO: Remove cast when we improve typing
|
||||
return cast(Program, solution)
|
||||
|
||||
|
||||
def get_metadata_and_phs(unft: UncurriedNFT, solution: SerializedProgram) -> Tuple[Program, bytes32]:
|
||||
|
||||
@@ -8,11 +8,13 @@ from typing import List, Optional, Tuple
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.util.streamable import Streamable, streamable
|
||||
|
||||
|
||||
@streamable
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Notification:
|
||||
coin_id: bytes32
|
||||
class Notification(Streamable):
|
||||
id: bytes32
|
||||
message: bytes
|
||||
amount: uint64
|
||||
height: uint32
|
||||
@@ -70,7 +72,7 @@ class NotificationStore:
|
||||
cursor = await conn.execute(
|
||||
"INSERT OR REPLACE INTO notifications (coin_id, msg, amount, height) VALUES(?, ?, ?, ?)",
|
||||
(
|
||||
notification.coin_id,
|
||||
notification.id,
|
||||
notification.message,
|
||||
notification.amount.stream_to_bytes(),
|
||||
notification.height,
|
||||
@@ -78,7 +80,7 @@ class NotificationStore:
|
||||
)
|
||||
cursor = await conn.execute(
|
||||
"INSERT OR REPLACE INTO all_notification_ids (coin_id) VALUES(?)",
|
||||
(notification.coin_id,),
|
||||
(notification.id,),
|
||||
)
|
||||
await cursor.close()
|
||||
|
||||
@@ -116,18 +118,25 @@ class NotificationStore:
|
||||
"""
|
||||
if pagination is not None:
|
||||
if pagination[1] is not None and pagination[0] is not None:
|
||||
pagination_str = f" LIMIT {pagination[0]}, {pagination[1] - pagination[0]}"
|
||||
pagination_str = " LIMIT ?, ?"
|
||||
pagination_params: Tuple[int, ...] = (pagination[0], pagination[1] - pagination[0])
|
||||
elif pagination[1] is None and pagination[0] is not None:
|
||||
pagination_str = f" LIMIT {pagination[0]}, (SELECT COUNT(*) from notifications)"
|
||||
pagination_str = " LIMIT ?, (SELECT COUNT(*) from notifications)"
|
||||
pagination_params = (pagination[0],)
|
||||
elif pagination[1] is not None and pagination[0] is None:
|
||||
pagination_str = f" LIMIT {pagination[1]}"
|
||||
pagination_str = " LIMIT ?"
|
||||
pagination_params = (pagination[1],)
|
||||
else:
|
||||
pagination_str = ""
|
||||
pagination_params = tuple()
|
||||
else:
|
||||
pagination_str = ""
|
||||
pagination_params = tuple()
|
||||
|
||||
async with self.db_wrapper.reader_no_transaction() as conn:
|
||||
rows = await conn.execute_fetchall(f"SELECT * from notifications ORDER BY amount DESC{pagination_str}")
|
||||
rows = await conn.execute_fetchall(
|
||||
f"SELECT * from notifications ORDER BY amount DESC{pagination_str}", pagination_params
|
||||
)
|
||||
|
||||
return [
|
||||
Notification(
|
||||
|
||||
@@ -343,15 +343,10 @@ def claim_p2_singleton(
|
||||
|
||||
# Get the CoinSpend for spending to a delayed puzzle
|
||||
def spend_to_delayed_puzzle(
|
||||
p2_singleton_coin: Coin,
|
||||
output_amount: uint64,
|
||||
launcher_id: bytes32,
|
||||
delay_time: uint64,
|
||||
delay_ph: bytes32,
|
||||
p2_singleton_coin: Coin, output_amount: uint64, launcher_id: bytes32, delay_time: uint64, delay_ph: bytes32
|
||||
) -> CoinSpend:
|
||||
claim_coinsol = make_spend(
|
||||
return make_spend(
|
||||
p2_singleton_coin,
|
||||
pay_to_singleton_or_delay_puzzle(launcher_id, delay_time, delay_ph),
|
||||
solution_for_p2_delayed_puzzle(output_amount),
|
||||
)
|
||||
return claim_coinsol
|
||||
|
||||
@@ -22,7 +22,6 @@ chia.wallet.chialisp
|
||||
chia.wallet.did_wallet.did_wallet
|
||||
chia.wallet.key_val_store
|
||||
chia.wallet.lineage_proof
|
||||
chia.wallet.nft_wallet.nft_puzzles
|
||||
chia.wallet.payment
|
||||
chia.wallet.puzzles.load_clvm
|
||||
chia.wallet.puzzles.p2_conditions
|
||||
@@ -105,7 +104,6 @@ tests.util.time_out_assert
|
||||
tests.wallet.cat_wallet.test_trades
|
||||
tests.wallet.did_wallet.test_did
|
||||
tests.wallet.rpc.test_wallet_rpc
|
||||
tests.wallet.simple_sync.test_simple_sync_protocol
|
||||
tests.wallet.sync.test_wallet_sync
|
||||
tests.wallet.test_chialisp
|
||||
tests.wallet.test_singleton_lifecycle
|
||||
|
||||
@@ -15,10 +15,12 @@ from clvm.casts import int_to_bytes
|
||||
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_header_validation import validate_finished_header_block
|
||||
from chia.consensus.block_record import BlockRecord
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward
|
||||
from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
from chia.consensus.coinbase import create_farmer_coin
|
||||
from chia.consensus.constants import ConsensusConstants
|
||||
from chia.consensus.full_block_to_block_record import block_to_block_record
|
||||
from chia.consensus.multiprocess_validation import PreValidationResult
|
||||
from chia.consensus.pot_iterations import is_overflow_block
|
||||
from chia.full_node.bundle_tools import detect_potential_template_generator
|
||||
@@ -3101,6 +3103,12 @@ class TestBodyValidation:
|
||||
assert preval_results[0].error == Err.BAD_AGGREGATE_SIGNATURE.value
|
||||
|
||||
|
||||
def maybe_header_hash(block: Optional[BlockRecord]) -> Optional[bytes32]:
|
||||
if block is None:
|
||||
return None
|
||||
return block.header_hash
|
||||
|
||||
|
||||
class TestReorgs:
|
||||
@pytest.mark.anyio
|
||||
async def test_basic_reorg(self, empty_blockchain, bt):
|
||||
@@ -3121,6 +3129,46 @@ class TestReorgs:
|
||||
await _validate_and_add_block(b, reorg_block)
|
||||
assert b.get_peak().height == 16
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_tx_peak_reorg(self, empty_blockchain, bt, consensus_mode: ConsensusMode):
|
||||
b = empty_blockchain
|
||||
|
||||
if consensus_mode == ConsensusMode.PLAIN:
|
||||
reorg_point = 13
|
||||
else:
|
||||
reorg_point = 12
|
||||
blocks = bt.get_consecutive_blocks(reorg_point)
|
||||
|
||||
last_tx_block: Optional[bytes32] = None
|
||||
for block in blocks:
|
||||
assert maybe_header_hash(b.get_tx_peak()) == last_tx_block
|
||||
await _validate_and_add_block(b, block)
|
||||
if block.is_transaction_block():
|
||||
last_tx_block = block.header_hash
|
||||
assert b.get_peak().height == reorg_point - 1
|
||||
assert maybe_header_hash(b.get_tx_peak()) == last_tx_block
|
||||
|
||||
reorg_last_tx_block: Optional[bytes32] = None
|
||||
|
||||
blocks_reorg_chain = bt.get_consecutive_blocks(7, blocks[:10], seed=b"2")
|
||||
assert blocks_reorg_chain[reorg_point].is_transaction_block() is False
|
||||
for reorg_block in blocks_reorg_chain:
|
||||
if reorg_block.height < 10:
|
||||
await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ALREADY_HAVE_BLOCK)
|
||||
elif reorg_block.height < reorg_point:
|
||||
await _validate_and_add_block(b, reorg_block, expected_result=AddBlockResult.ADDED_AS_ORPHAN)
|
||||
elif reorg_block.height >= reorg_point:
|
||||
await _validate_and_add_block(b, reorg_block)
|
||||
|
||||
if reorg_block.is_transaction_block():
|
||||
reorg_last_tx_block = reorg_block.header_hash
|
||||
if reorg_block.height >= reorg_point:
|
||||
last_tx_block = reorg_last_tx_block
|
||||
|
||||
assert maybe_header_hash(b.get_tx_peak()) == last_tx_block
|
||||
|
||||
assert b.get_peak().height == 16
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("light_blocks", [True, False])
|
||||
async def test_long_reorg(
|
||||
@@ -3693,3 +3741,28 @@ async def test_reorg_flip_flop(empty_blockchain, bt):
|
||||
|
||||
for block in chain_b[40:]:
|
||||
await _validate_and_add_block(b, block)
|
||||
|
||||
|
||||
async def test_get_tx_peak(default_400_blocks, empty_blockchain):
|
||||
bc = empty_blockchain
|
||||
test_blocks = default_400_blocks[:100]
|
||||
|
||||
res = await bc.pre_validate_blocks_multiprocessing(test_blocks, {}, validate_signatures=False)
|
||||
|
||||
last_tx_block: Optional[FullBlock] = None
|
||||
for b, prevalidation_res in zip(test_blocks, res):
|
||||
assert bc.get_tx_peak() == last_tx_block
|
||||
res, err, state = await bc.add_block(b, prevalidation_res)
|
||||
assert err is None
|
||||
|
||||
if b.is_transaction_block():
|
||||
block_record = block_to_block_record(
|
||||
bc.constants,
|
||||
bc,
|
||||
prevalidation_res.required_iters,
|
||||
b,
|
||||
None,
|
||||
)
|
||||
last_tx_block = block_record
|
||||
|
||||
assert bc.get_tx_peak() == last_tx_block
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple, cast
|
||||
|
||||
from chia.rpc.wallet_request_types import GetNotifications, GetNotificationsResponse
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.util.bech32m import encode_puzzle_hash
|
||||
from chia.util.ints import uint32, uint64
|
||||
@@ -62,11 +63,11 @@ def test_notifications_get(capsys: object, get_test_cli_clients: Tuple[TestRpcCl
|
||||
|
||||
# set RPC Client
|
||||
class NotificationsGetRpcClient(TestWalletRpcClient):
|
||||
async def get_notifications(
|
||||
self, ids: Optional[List[bytes32]] = None, pagination: Optional[Tuple[Optional[int], Optional[int]]] = None
|
||||
) -> List[Notification]:
|
||||
self.add_to_log("get_notifications", (ids, pagination))
|
||||
return [Notification(get_bytes32(1), bytes("hello", "utf8"), uint64(1000000000), uint32(50))]
|
||||
async def get_notifications(self, request: GetNotifications) -> GetNotificationsResponse:
|
||||
self.add_to_log("get_notifications", (request,))
|
||||
return GetNotificationsResponse(
|
||||
[Notification(get_bytes32(1), bytes("hello", "utf8"), uint64(1000000000), uint32(50))]
|
||||
)
|
||||
|
||||
inst_rpc_client = NotificationsGetRpcClient() # pylint: disable=no-value-for-parameter
|
||||
test_rpc_clients.wallet_rpc_client = inst_rpc_client
|
||||
@@ -87,7 +88,7 @@ def test_notifications_get(capsys: object, get_test_cli_clients: Tuple[TestRpcCl
|
||||
"amount: 1000000000",
|
||||
]
|
||||
run_cli_command_and_assert(capsys, root_dir, command_args, assert_list)
|
||||
expected_calls: logType = {"get_notifications": [([get_bytes32(1)], (10, 10))]}
|
||||
expected_calls: logType = {"get_notifications": [(GetNotifications([get_bytes32(1)], uint32(10), uint32(10)),)]}
|
||||
test_rpc_clients.wallet_rpc_client.check_log(expected_calls)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Set, Tuple
|
||||
|
||||
@@ -11,7 +12,9 @@ from chia.consensus.blockchain import AddBlockResult, Blockchain
|
||||
from chia.consensus.coinbase import create_farmer_coin, create_pool_coin
|
||||
from chia.full_node.block_store import BlockStore
|
||||
from chia.full_node.coin_store import CoinStore
|
||||
from chia.full_node.hint_store import HintStore
|
||||
from chia.full_node.mempool_check_conditions import get_name_puzzle_conditions
|
||||
from chia.protocols.wallet_protocol import CoinState
|
||||
from chia.simulator.block_tools import BlockTools, test_constants
|
||||
from chia.simulator.wallet_tools import WalletTool
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
@@ -493,6 +496,192 @@ async def test_get_coin_states(db_version: int) -> None:
|
||||
assert len(await coin_store.get_coin_states_by_ids(True, coins, uint32(0), max_items=10000)) == 600
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RandomCoinRecords:
|
||||
items: List[CoinRecord]
|
||||
puzzle_hashes: List[bytes32]
|
||||
hints: List[Tuple[bytes32, bytes]]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def random_coin_records() -> RandomCoinRecords:
|
||||
coin_records: List[CoinRecord] = []
|
||||
puzzle_hashes: List[bytes32] = []
|
||||
hints: List[Tuple[bytes32, bytes]] = []
|
||||
|
||||
for i in range(50000):
|
||||
is_spent = i % 2 == 0
|
||||
is_hinted = i % 7 == 0
|
||||
created_height = uint32(i)
|
||||
spent_height = uint32(created_height + 100)
|
||||
|
||||
puzzle_hash = std_hash(i.to_bytes(4, byteorder="big"))
|
||||
|
||||
coin = Coin(
|
||||
std_hash(b"Parent Coin Id " + i.to_bytes(4, byteorder="big")),
|
||||
puzzle_hash,
|
||||
uint64(1000),
|
||||
)
|
||||
|
||||
if is_hinted:
|
||||
hint = std_hash(b"Hinted " + puzzle_hash)
|
||||
hints.append((coin.name(), hint))
|
||||
puzzle_hashes.append(hint)
|
||||
else:
|
||||
puzzle_hashes.append(puzzle_hash)
|
||||
|
||||
coin_records.append(
|
||||
CoinRecord(
|
||||
coin=coin,
|
||||
confirmed_block_index=created_height,
|
||||
spent_block_index=spent_height if is_spent else uint32(0),
|
||||
coinbase=False,
|
||||
timestamp=uint64(0),
|
||||
)
|
||||
)
|
||||
|
||||
coin_records.sort(key=lambda cr: max(cr.confirmed_block_index, cr.spent_block_index))
|
||||
|
||||
return RandomCoinRecords(coin_records, puzzle_hashes, hints)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("include_spent", [True, False])
|
||||
@pytest.mark.parametrize("include_unspent", [True, False])
|
||||
@pytest.mark.parametrize("include_hinted", [True, False])
|
||||
async def test_coin_state_batches(
|
||||
db_version: int,
|
||||
random_coin_records: RandomCoinRecords,
|
||||
include_spent: bool,
|
||||
include_unspent: bool,
|
||||
include_hinted: bool,
|
||||
) -> None:
|
||||
async with DBConnection(db_version) as db_wrapper:
|
||||
# Initialize coin and hint stores.
|
||||
coin_store = await CoinStore.create(db_wrapper)
|
||||
hint_store = await HintStore.create(db_wrapper)
|
||||
|
||||
await coin_store._add_coin_records(random_coin_records.items)
|
||||
await hint_store.add_hints(random_coin_records.hints)
|
||||
|
||||
# Make sure all of the coin states are found when batching.
|
||||
ph_set = set(random_coin_records.puzzle_hashes)
|
||||
expected_crs = []
|
||||
for cr in random_coin_records.items:
|
||||
if cr.spent_block_index == 0 and not include_unspent:
|
||||
continue
|
||||
if cr.spent_block_index > 0 and not include_spent:
|
||||
continue
|
||||
if cr.coin.puzzle_hash not in ph_set and not include_hinted:
|
||||
continue
|
||||
expected_crs.append(cr)
|
||||
|
||||
height: Optional[uint32] = uint32(0)
|
||||
all_coin_states: List[CoinState] = []
|
||||
remaining_phs = random_coin_records.puzzle_hashes.copy()
|
||||
|
||||
def height_of(coin_state: CoinState) -> int:
|
||||
return max(coin_state.created_height or 0, coin_state.spent_height or 0)
|
||||
|
||||
while height is not None:
|
||||
(coin_states, height) = await coin_store.batch_coin_states_by_puzzle_hashes(
|
||||
remaining_phs[:15000],
|
||||
min_height=height,
|
||||
include_spent=include_spent,
|
||||
include_unspent=include_unspent,
|
||||
include_hinted=include_hinted,
|
||||
)
|
||||
|
||||
# Ensure that all of the returned coin states are in order.
|
||||
assert all(height_of(coin_states[i]) <= height_of(coin_states[i + 1]) for i in range(len(coin_states) - 1))
|
||||
|
||||
all_coin_states += coin_states
|
||||
|
||||
if height is None:
|
||||
remaining_phs = remaining_phs[15000:]
|
||||
|
||||
if len(remaining_phs) > 0:
|
||||
height = uint32(0)
|
||||
|
||||
assert len(all_coin_states) == len(expected_crs)
|
||||
|
||||
all_coin_states.sort(key=height_of)
|
||||
|
||||
for i in range(len(expected_crs)):
|
||||
actual = all_coin_states[i]
|
||||
expected = expected_crs[i]
|
||||
|
||||
assert actual.coin == expected.coin, i
|
||||
assert uint32(actual.created_height or 0) == expected.confirmed_block_index, i
|
||||
assert uint32(actual.spent_height or 0) == expected.spent_block_index, i
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.parametrize("cut_off_middle", [True, False])
|
||||
async def test_batch_many_coin_states(db_version: int, cut_off_middle: bool) -> None:
|
||||
async with DBConnection(db_version) as db_wrapper:
|
||||
ph = bytes32(b"0" * 32)
|
||||
|
||||
# Generate coin records.
|
||||
coin_records: List[CoinRecord] = []
|
||||
count = 50000
|
||||
|
||||
for i in range(count):
|
||||
# Create coin records at either height 10 or 12.
|
||||
created_height = uint32((i % 2) * 2 + 10)
|
||||
coin = Coin(
|
||||
std_hash(b"Parent Coin Id " + i.to_bytes(4, byteorder="big")),
|
||||
ph,
|
||||
uint64(i),
|
||||
)
|
||||
coin_records.append(
|
||||
CoinRecord(
|
||||
coin=coin,
|
||||
confirmed_block_index=created_height,
|
||||
spent_block_index=uint32(0),
|
||||
coinbase=False,
|
||||
timestamp=uint64(0),
|
||||
)
|
||||
)
|
||||
|
||||
# Initialize coin and hint stores.
|
||||
coin_store = await CoinStore.create(db_wrapper)
|
||||
await HintStore.create(db_wrapper)
|
||||
|
||||
await coin_store._add_coin_records(coin_records)
|
||||
|
||||
# Make sure all of the coin states are found.
|
||||
(all_coin_states, next_height) = await coin_store.batch_coin_states_by_puzzle_hashes([ph])
|
||||
all_coin_states.sort(key=lambda cs: cs.coin.amount)
|
||||
|
||||
assert next_height is None
|
||||
assert len(all_coin_states) == len(coin_records)
|
||||
|
||||
for i in range(min(len(coin_records), len(all_coin_states))):
|
||||
assert coin_records[i].coin.name().hex() == all_coin_states[i].coin.name().hex(), i
|
||||
|
||||
# For the middle case, insert a coin record between the two heights 10 and 12.
|
||||
await coin_store._add_coin_records(
|
||||
[
|
||||
CoinRecord(
|
||||
coin=Coin(std_hash(b"extra coin"), ph, 0),
|
||||
# Insert a coin record in the middle between heights 10 and 12.
|
||||
# Or after all of the other coins if testing the batch limit.
|
||||
confirmed_block_index=uint32(11 if cut_off_middle else 50),
|
||||
spent_block_index=uint32(0),
|
||||
coinbase=False,
|
||||
timestamp=uint64(0),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
(all_coin_states, next_height) = await coin_store.batch_coin_states_by_puzzle_hashes([ph])
|
||||
|
||||
# Make sure that the extra coin records are not included in the results.
|
||||
assert next_height == (12 if cut_off_middle else 50)
|
||||
assert len(all_coin_states) == (25001 if cut_off_middle else 50000)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_unsupported_version() -> None:
|
||||
with pytest.raises(RuntimeError, match="CoinStore does not support database schema v1"):
|
||||
|
||||
@@ -16,6 +16,7 @@ from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate
|
||||
from chia.consensus.coinbase import create_puzzlehash_for_pk
|
||||
from chia.rpc.full_node_rpc_client import FullNodeRpcClient
|
||||
from chia.rpc.rpc_server import RpcServer
|
||||
from chia.rpc.wallet_request_types import GetNotifications
|
||||
from chia.rpc.wallet_rpc_api import WalletRpcApi
|
||||
from chia.rpc.wallet_rpc_client import WalletRpcClient
|
||||
from chia.server.server import ChiaServer
|
||||
@@ -2059,14 +2060,14 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen
|
||||
await farm_transaction(full_node_api, wallet_node, tx.spend_bundle)
|
||||
await time_out_assert(20, env.wallet_2.wallet.get_confirmed_balance, uint64(100000000000))
|
||||
|
||||
notification = (await client_2.get_notifications())[0]
|
||||
assert [notification] == (await client_2.get_notifications([notification.coin_id]))
|
||||
assert [] == (await client_2.get_notifications(pagination=(0, 0)))
|
||||
assert [notification] == (await client_2.get_notifications(pagination=(None, 1)))
|
||||
assert [] == (await client_2.get_notifications(pagination=(1, None)))
|
||||
assert [notification] == (await client_2.get_notifications(pagination=(None, None)))
|
||||
notification = (await client_2.get_notifications(GetNotifications())).notifications[0]
|
||||
assert [notification] == (await client_2.get_notifications(GetNotifications([notification.id]))).notifications
|
||||
assert [] == (await client_2.get_notifications(GetNotifications(None, uint32(0), uint32(0)))).notifications
|
||||
assert [notification] == (await client_2.get_notifications(GetNotifications(None, None, uint32(1)))).notifications
|
||||
assert [] == (await client_2.get_notifications(GetNotifications(None, uint32(1), None))).notifications
|
||||
assert [notification] == (await client_2.get_notifications(GetNotifications(None, None, None))).notifications
|
||||
assert await client_2.delete_notifications()
|
||||
assert [] == (await client_2.get_notifications([notification.coin_id]))
|
||||
assert [] == (await client_2.get_notifications(GetNotifications([notification.id]))).notifications
|
||||
|
||||
tx = await client.send_notification(
|
||||
await wallet_2.get_new_puzzlehash(),
|
||||
@@ -2085,9 +2086,9 @@ async def test_notification_rpcs(wallet_rpc_environment: WalletRpcTestEnvironmen
|
||||
await farm_transaction(full_node_api, wallet_node, tx.spend_bundle)
|
||||
await time_out_assert(20, env.wallet_2.wallet.get_confirmed_balance, uint64(200000000000))
|
||||
|
||||
notification = (await client_2.get_notifications())[0]
|
||||
assert await client_2.delete_notifications([notification.coin_id])
|
||||
assert [] == (await client_2.get_notifications([notification.coin_id]))
|
||||
notification = (await client_2.get_notifications(GetNotifications())).notifications[0]
|
||||
assert await client_2.delete_notifications([notification.id])
|
||||
assert [] == (await client_2.get_notifications(GetNotifications([notification.id]))).notifications
|
||||
|
||||
|
||||
# The signatures below were made from an ephemeral key pair that isn't included in the test code.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# flake8: noqa: F811, F401
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
from clvm.casts import int_to_bytes
|
||||
@@ -12,21 +11,17 @@ from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate
|
||||
from chia.protocols import wallet_protocol
|
||||
from chia.protocols.full_node_protocol import RespondTransaction
|
||||
from chia.protocols.protocol_message_types import ProtocolMessageTypes
|
||||
from chia.protocols.wallet_protocol import CoinStateUpdate, RespondToCoinUpdates, RespondToPhUpdates
|
||||
from chia.server.outbound_message import NodeType
|
||||
from chia.protocols.wallet_protocol import CoinStateUpdate, RespondToCoinUpdates
|
||||
from chia.server.outbound_message import Message, NodeType
|
||||
from chia.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol
|
||||
from chia.simulator.wallet_tools import WalletTool
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.coin_record import CoinRecord
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.types.condition_with_args import ConditionWithArgs
|
||||
from chia.types.peer_info import PeerInfo
|
||||
from chia.types.spend_bundle import SpendBundle
|
||||
from chia.util.ints import uint32, uint64
|
||||
from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_state_manager import WalletStateManager
|
||||
from tests.connection_utils import add_dummy_connection
|
||||
from tests.util.setup_nodes import OldSimulatorsAndWallets
|
||||
from tests.util.time_out_assert import time_out_assert
|
||||
@@ -36,7 +31,7 @@ log = getLogger(__name__)
|
||||
zero_ph = bytes32(32 * b"\0")
|
||||
|
||||
|
||||
async def get_all_messages_in_queue(queue):
|
||||
async def get_all_messages_in_queue(queue: asyncio.Queue[Message]) -> List[Message]:
|
||||
all_messages = []
|
||||
await asyncio.sleep(2)
|
||||
while not queue.empty():
|
||||
@@ -46,44 +41,43 @@ async def get_all_messages_in_queue(queue):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
incoming_queue, peer_id = await add_dummy_connection(fn_server, self_hostname, 12312, NodeType.WALLET)
|
||||
|
||||
junk_ph = 32 * b"\a"
|
||||
junk_ph = bytes32(32 * b"\a")
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
msg = wallet_protocol.RegisterForPhUpdates([zero_ph], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([zero_ph], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
assert data_response.coin_states == []
|
||||
|
||||
# Farm few more with reward
|
||||
for i in range(0, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
if i == num_blocks - 1:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(junk_ph))
|
||||
else:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
msg = wallet_protocol.RegisterForPhUpdates([zero_ph], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([zero_ph], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
# we have already subscribed to this puzzle hash, it will be ignored
|
||||
# we still receive the updates (see below)
|
||||
assert data_response.coin_states == []
|
||||
|
||||
# Farm more rewards to check the incoming queue for the updates
|
||||
for i in range(0, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
if i == num_blocks - 1:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(junk_ph))
|
||||
@@ -98,35 +92,36 @@ async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in data_response.items:
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
assert len(coin_state_update.items) == 2 # 2 per height farmer / pool reward
|
||||
for coin_state in coin_state_update.items:
|
||||
notified_zero_coins.add(coin_state)
|
||||
assert len(data_response.items) == 2 # 2 per height farmer / pool reward
|
||||
|
||||
assert all_zero_coin == notified_zero_coins
|
||||
|
||||
# Test subscribing to more coins
|
||||
one_ph = 32 * b"\1"
|
||||
msg = wallet_protocol.RegisterForPhUpdates([one_ph], 0)
|
||||
one_ph = bytes32(32 * b"\1")
|
||||
msg = wallet_protocol.RegisterForPhUpdates([one_ph], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
peak = full_node_api.full_node.blockchain.get_peak()
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
if i == num_blocks - 1:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(junk_ph))
|
||||
else:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
if i == num_blocks - 1:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(one_ph))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(junk_ph))
|
||||
else:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(one_ph))
|
||||
|
||||
assert peak is not None
|
||||
zero_coins = await full_node_api.full_node.coin_store.get_coin_states_by_puzzle_hashes(
|
||||
True, {zero_ph}, peak.height + 1
|
||||
True, {zero_ph}, uint32(peak.height + 1)
|
||||
)
|
||||
one_coins = await full_node_api.full_node.coin_store.get_coin_states_by_puzzle_hashes(True, {one_ph})
|
||||
|
||||
@@ -139,18 +134,18 @@ async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in data_response.items:
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
assert len(coin_state_update.items) == 2 # 2 per height farmer / pool reward
|
||||
for coin_state in coin_state_update.items:
|
||||
notified_all_coins.add(coin_state)
|
||||
assert len(data_response.items) == 2 # 2 per height farmer / pool reward
|
||||
|
||||
assert all_coins == notified_all_coins
|
||||
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
wallet: Wallet = wsm.wallets[1]
|
||||
wallet = wallet_node.wallet_state_manager.wallets[uint32(1)]
|
||||
assert isinstance(wallet, Wallet)
|
||||
puzzle_hash = await wallet.get_new_puzzlehash()
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
for i in range(num_blocks):
|
||||
if i == num_blocks - 1:
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash))
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(junk_ph))
|
||||
@@ -168,15 +163,16 @@ async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
await time_out_assert(20, wallet.get_confirmed_balance, funds)
|
||||
assert funds == fn_amount
|
||||
|
||||
msg_1 = wallet_protocol.RegisterForPhUpdates([puzzle_hash], 0)
|
||||
msg_1 = wallet_protocol.RegisterForPhUpdates([puzzle_hash], uint32(0))
|
||||
msg_response_1 = await full_node_api.register_interest_in_puzzle_hash(msg_1, fake_wallet_peer)
|
||||
assert msg_response_1.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response_1: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response_1.data)
|
||||
data_response_1 = RespondToCoinUpdates.from_bytes(msg_response_1.data)
|
||||
assert len(data_response_1.coin_states) == 2 * num_blocks # 2 per height farmer / pool reward
|
||||
|
||||
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
|
||||
|
||||
[tx_record] = await wallet.generate_signed_transaction(uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0))
|
||||
assert tx_record.spend_bundle is not None
|
||||
assert len(tx_record.spend_bundle.removals()) == 1
|
||||
spent_coin = tx_record.spend_bundle.removals()[0]
|
||||
assert spent_coin.puzzle_hash == puzzle_hash
|
||||
@@ -211,8 +207,8 @@ async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in data_response.items:
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in coin_state_update.items:
|
||||
if coin_state.coin.name() == spent_coin.name():
|
||||
notified_state = coin_state
|
||||
|
||||
@@ -222,14 +218,14 @@ async def test_subscribe_for_ph(simulator_and_wallet, self_hostname):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
async def test_subscribe_for_coin_id(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
standard_wallet: Wallet = wsm.wallets[1]
|
||||
standard_wallet = wallet_node.wallet_state_manager.wallets[uint32(1)]
|
||||
assert isinstance(standard_wallet, Wallet)
|
||||
puzzle_hash = await standard_wallet.get_new_puzzlehash()
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
@@ -238,7 +234,7 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
|
||||
# Farm to create a coin that we'll track
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash))
|
||||
|
||||
funds = sum(
|
||||
@@ -247,16 +243,14 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
|
||||
await time_out_assert(20, standard_wallet.get_confirmed_balance, funds)
|
||||
|
||||
my_coins: List[CoinRecord] = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(
|
||||
True, puzzle_hash
|
||||
)
|
||||
my_coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, puzzle_hash)
|
||||
coin_to_spend = my_coins[0].coin
|
||||
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([coin_to_spend.name()], 0)
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([coin_to_spend.name()], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_coin(msg, fake_wallet_peer)
|
||||
assert msg_response is not None
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_coin_update.value
|
||||
data_response: RespondToCoinUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
assert data_response.coin_states[0].coin == coin_to_spend
|
||||
|
||||
coins = set()
|
||||
@@ -273,8 +267,8 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
notified_coins = set()
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in data_response.items:
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in coin_state_update.items:
|
||||
notified_coins.add(coin_state.coin)
|
||||
assert coin_state.spent_height is not None
|
||||
|
||||
@@ -287,18 +281,19 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0)
|
||||
)
|
||||
|
||||
added_target: Optional[Coin] = None
|
||||
added_target = None
|
||||
assert tx_record.spend_bundle is not None
|
||||
for coin in tx_record.spend_bundle.additions():
|
||||
if coin.puzzle_hash == puzzle_hash:
|
||||
added_target = coin
|
||||
|
||||
assert added_target is not None
|
||||
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([added_target.name()], 0)
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([added_target.name()], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_coin(msg, fake_wallet_peer)
|
||||
assert msg_response is not None
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_coin_update.value
|
||||
data_response: RespondToCoinUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
assert len(data_response.coin_states) == 0
|
||||
|
||||
await standard_wallet.push_transaction(tx_record)
|
||||
@@ -311,8 +306,8 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in data_response.items:
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
for coin_state in coin_state_update.items:
|
||||
if coin_state.coin.name() == added_target.name():
|
||||
notified_state = coin_state
|
||||
|
||||
@@ -322,15 +317,15 @@ async def test_subscribe_for_coin_id(simulator_and_wallet, self_hostname):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_ph_reorg(simulator_and_wallet, self_hostname):
|
||||
async def test_subscribe_for_ph_reorg(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
num_blocks = 4
|
||||
long_blocks = 20
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
standard_wallet: Wallet = wsm.wallets[1]
|
||||
standard_wallet = wallet_node.wallet_state_manager.wallets[uint32(1)]
|
||||
assert isinstance(standard_wallet, Wallet)
|
||||
puzzle_hash = await standard_wallet.get_new_puzzlehash()
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
@@ -339,18 +334,18 @@ async def test_subscribe_for_ph_reorg(simulator_and_wallet, self_hostname):
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
|
||||
# Farm to create a coin that we'll track
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
for i in range(0, long_blocks):
|
||||
for _ in range(long_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
msg = wallet_protocol.RegisterForPhUpdates([puzzle_hash], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([puzzle_hash], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
assert msg_response is not None
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash))
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
expected_height = uint32(long_blocks + 2 * num_blocks + 1)
|
||||
@@ -358,8 +353,8 @@ async def test_subscribe_for_ph_reorg(simulator_and_wallet, self_hostname):
|
||||
|
||||
coin_records = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, puzzle_hash)
|
||||
assert len(coin_records) > 0
|
||||
fork_height = expected_height - num_blocks - 5
|
||||
req = ReorgProtocol(fork_height, expected_height + 5, zero_ph, None)
|
||||
fork_height = uint32(expected_height - num_blocks - 5)
|
||||
req = ReorgProtocol(fork_height, uint32(expected_height + 5), zero_ph, None)
|
||||
await full_node_api.reorg_from_index_to_new_index(req)
|
||||
|
||||
coin_records = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, puzzle_hash)
|
||||
@@ -367,11 +362,11 @@ async def test_subscribe_for_ph_reorg(simulator_and_wallet, self_hostname):
|
||||
|
||||
all_messages = await get_all_messages_in_queue(incoming_queue)
|
||||
|
||||
coin_update_messages = []
|
||||
coin_update_messages: List[CoinStateUpdate] = []
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
coin_update_messages.append(data_response)
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
coin_update_messages.append(coin_state_update)
|
||||
|
||||
# First state is creation, second one is a reorg
|
||||
assert len(coin_update_messages) == 2
|
||||
@@ -397,15 +392,15 @@ async def test_subscribe_for_ph_reorg(simulator_and_wallet, self_hostname):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_coin_id_reorg(simulator_and_wallet, self_hostname):
|
||||
async def test_subscribe_for_coin_id_reorg(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
num_blocks = 4
|
||||
long_blocks = 20
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
standard_wallet: Wallet = wsm.wallets[1]
|
||||
standard_wallet = wallet_node.wallet_state_manager.wallets[uint32(1)]
|
||||
assert isinstance(standard_wallet, Wallet)
|
||||
puzzle_hash = await standard_wallet.get_new_puzzlehash()
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
@@ -414,15 +409,15 @@ async def test_subscribe_for_coin_id_reorg(simulator_and_wallet, self_hostname):
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
|
||||
# Farm to create a coin that we'll track
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
for i in range(0, long_blocks):
|
||||
for _ in range(long_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash))
|
||||
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(zero_ph))
|
||||
|
||||
expected_height = uint32(long_blocks + 2 * num_blocks + 1)
|
||||
@@ -432,12 +427,12 @@ async def test_subscribe_for_coin_id_reorg(simulator_and_wallet, self_hostname):
|
||||
assert len(coin_records) > 0
|
||||
|
||||
for coin_rec in coin_records:
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([coin_rec.name], 0)
|
||||
msg = wallet_protocol.RegisterForCoinUpdates([coin_rec.name], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_coin(msg, fake_wallet_peer)
|
||||
assert msg_response is not None
|
||||
|
||||
fork_height = expected_height - num_blocks - 5
|
||||
req = ReorgProtocol(fork_height, expected_height + 5, zero_ph, None)
|
||||
fork_height = uint32(expected_height - num_blocks - 5)
|
||||
req = ReorgProtocol(fork_height, uint32(expected_height + 5), zero_ph, None)
|
||||
await full_node_api.reorg_from_index_to_new_index(req)
|
||||
|
||||
coin_records = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash(True, puzzle_hash)
|
||||
@@ -445,11 +440,11 @@ async def test_subscribe_for_coin_id_reorg(simulator_and_wallet, self_hostname):
|
||||
|
||||
all_messages = await get_all_messages_in_queue(incoming_queue)
|
||||
|
||||
coin_update_messages = []
|
||||
coin_update_messages: List[CoinStateUpdate] = []
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
coin_update_messages.append(data_response)
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
coin_update_messages.append(coin_state_update)
|
||||
|
||||
assert len(coin_update_messages) == 1
|
||||
update = coin_update_messages[0]
|
||||
@@ -464,20 +459,19 @@ async def test_subscribe_for_coin_id_reorg(simulator_and_wallet, self_hostname):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_hint(simulator_and_wallet, self_hostname):
|
||||
async def test_subscribe_for_hint(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, bt = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
incoming_queue, peer_id = await add_dummy_connection(fn_server, self_hostname, 12312, NodeType.WALLET)
|
||||
|
||||
wt: WalletTool = bt.get_pool_wallet_tool()
|
||||
wt = bt.get_pool_wallet_tool()
|
||||
ph = wt.get_new_puzzlehash()
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
await asyncio.sleep(6)
|
||||
@@ -486,13 +480,13 @@ async def test_subscribe_for_hint(simulator_and_wallet, self_hostname):
|
||||
hint_puzzle_hash = 32 * b"\2"
|
||||
amount = 1
|
||||
amount_bin = int_to_bytes(1)
|
||||
hint = 32 * b"\5"
|
||||
hint = bytes32(32 * b"\5")
|
||||
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
assert len(data_response.coin_states) == 0
|
||||
|
||||
condition_dict = {
|
||||
@@ -502,12 +496,7 @@ async def test_subscribe_for_hint(simulator_and_wallet, self_hostname):
|
||||
}
|
||||
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
|
||||
|
||||
tx: SpendBundle = wt.generate_signed_transaction(
|
||||
10,
|
||||
wt.get_new_puzzlehash(),
|
||||
coin_spent,
|
||||
condition_dic=condition_dict,
|
||||
)
|
||||
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
|
||||
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
|
||||
|
||||
await full_node_api.process_spend_bundles(bundles=[tx])
|
||||
@@ -518,20 +507,20 @@ async def test_subscribe_for_hint(simulator_and_wallet, self_hostname):
|
||||
|
||||
for message in all_messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
notified_state = data_response
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
notified_state = coin_state_update
|
||||
break
|
||||
|
||||
assert notified_state is not None
|
||||
assert notified_state.items[0].coin == Coin(coin_spent.name(), hint_puzzle_hash, amount)
|
||||
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
# we have already subscribed to this puzzle hash. The full node will
|
||||
# ignore the duplicate
|
||||
assert data_response.coin_states == []
|
||||
assert response.coin_states == []
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -543,14 +532,14 @@ async def test_subscribe_for_puzzle_hash_coin_hint_duplicates(
|
||||
|
||||
await wallet_server.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None)
|
||||
|
||||
wt: WalletTool = bt.get_pool_wallet_tool()
|
||||
wt = bt.get_pool_wallet_tool()
|
||||
ph = wt.get_new_puzzlehash()
|
||||
await full_node_api.farm_blocks_to_puzzlehash(4, ph)
|
||||
coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hashes(False, [ph])
|
||||
wallet_connection = full_node_server.all_connections[wallet_server.node_id]
|
||||
|
||||
# Create a coin which is hinted with its own destination puzzle hash
|
||||
tx: SpendBundle = wt.generate_signed_transaction(
|
||||
tx = wt.generate_signed_transaction(
|
||||
uint64(10),
|
||||
wt.get_new_puzzlehash(),
|
||||
coins[0].coin,
|
||||
@@ -570,25 +559,24 @@ async def test_subscribe_for_puzzle_hash_coin_hint_duplicates(
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_subscribe_for_hint_long_sync(wallet_two_node_simulator, self_hostname):
|
||||
async def test_subscribe_for_hint_long_sync(
|
||||
wallet_two_node_simulator: OldSimulatorsAndWallets, self_hostname: str
|
||||
) -> None:
|
||||
num_blocks = 4
|
||||
full_nodes, wallets, bt = wallet_two_node_simulator
|
||||
full_node_api = full_nodes[0]
|
||||
full_node_api_1 = full_nodes[1]
|
||||
|
||||
wallet_node, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
fn_server_1 = full_node_api_1.full_node.server
|
||||
|
||||
wsm: WalletStateManager = wallet_node.wallet_state_manager
|
||||
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
incoming_queue, peer_id = await add_dummy_connection(fn_server, self_hostname, 12312, NodeType.WALLET)
|
||||
incoming_queue_1, peer_id_1 = await add_dummy_connection(fn_server_1, self_hostname, 12313, NodeType.WALLET)
|
||||
|
||||
wt: WalletTool = bt.get_pool_wallet_tool()
|
||||
wt = bt.get_pool_wallet_tool()
|
||||
ph = wt.get_new_puzzlehash()
|
||||
for i in range(0, num_blocks):
|
||||
for _ in range(num_blocks):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
await asyncio.sleep(6)
|
||||
@@ -597,16 +585,16 @@ async def test_subscribe_for_hint_long_sync(wallet_two_node_simulator, self_host
|
||||
hint_puzzle_hash = 32 * b"\2"
|
||||
amount = 1
|
||||
amount_bin = int_to_bytes(1)
|
||||
hint = 32 * b"\5"
|
||||
hint = bytes32(32 * b"\5")
|
||||
|
||||
fake_wallet_peer = fn_server.all_connections[peer_id]
|
||||
fake_wallet_peer_1 = fn_server_1.all_connections[peer_id_1]
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates([hint], uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, fake_wallet_peer)
|
||||
msg_response_1 = await full_node_api_1.register_interest_in_puzzle_hash(msg, fake_wallet_peer_1)
|
||||
await full_node_api_1.register_interest_in_puzzle_hash(msg, fake_wallet_peer_1)
|
||||
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
data_response = RespondToCoinUpdates.from_bytes(msg_response.data)
|
||||
assert len(data_response.coin_states) == 0
|
||||
|
||||
condition_dict = {
|
||||
@@ -616,18 +604,13 @@ async def test_subscribe_for_hint_long_sync(wallet_two_node_simulator, self_host
|
||||
}
|
||||
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
|
||||
|
||||
tx: SpendBundle = wt.generate_signed_transaction(
|
||||
10,
|
||||
wt.get_new_puzzlehash(),
|
||||
coin_spent,
|
||||
condition_dic=condition_dict,
|
||||
)
|
||||
tx = wt.generate_signed_transaction(uint64(10), wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict)
|
||||
await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer)
|
||||
|
||||
await full_node_api.process_spend_bundles(bundles=[tx])
|
||||
|
||||
# Create more blocks than recent "short_sync_blocks_behind_threshold" so that node enters batch
|
||||
for i in range(0, 100):
|
||||
for _ in range(100):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
node1_height = full_node_api_1.full_node.blockchain.get_peak_height()
|
||||
@@ -640,13 +623,13 @@ async def test_subscribe_for_hint_long_sync(wallet_two_node_simulator, self_host
|
||||
all_messages = await get_all_messages_in_queue(incoming_queue)
|
||||
all_messages_1 = await get_all_messages_in_queue(incoming_queue_1)
|
||||
|
||||
def check_messages_for_hint(messages):
|
||||
def check_messages_for_hint(messages: List[Message]) -> None:
|
||||
notified_state = None
|
||||
|
||||
for message in messages:
|
||||
if message.type == ProtocolMessageTypes.coin_state_update.value:
|
||||
data_response: CoinStateUpdate = CoinStateUpdate.from_bytes(message.data)
|
||||
notified_state = data_response
|
||||
coin_state_update = CoinStateUpdate.from_bytes(message.data)
|
||||
notified_state = coin_state_update
|
||||
break
|
||||
|
||||
assert notified_state is not None
|
||||
@@ -657,24 +640,24 @@ async def test_subscribe_for_hint_long_sync(wallet_two_node_simulator, self_host
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_ph_subscribe_limits(simulator_and_wallet, self_hostname):
|
||||
async def test_ph_subscribe_limits(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
_, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
con = list(fn_server.all_connections.values())[0]
|
||||
phs = []
|
||||
phs.append(32 * b"\0")
|
||||
phs.append(32 * b"\1")
|
||||
phs.append(32 * b"\2")
|
||||
phs.append(32 * b"\3")
|
||||
phs.append(32 * b"\4")
|
||||
phs.append(32 * b"\5")
|
||||
phs.append(32 * b"\6")
|
||||
phs.append(bytes32(32 * b"\0"))
|
||||
phs.append(bytes32(32 * b"\1"))
|
||||
phs.append(bytes32(32 * b"\2"))
|
||||
phs.append(bytes32(32 * b"\3"))
|
||||
phs.append(bytes32(32 * b"\4"))
|
||||
phs.append(bytes32(32 * b"\5"))
|
||||
phs.append(bytes32(32 * b"\6"))
|
||||
full_node_api.full_node.config["max_subscribe_items"] = 2
|
||||
assert full_node_api.is_trusted(con) is False
|
||||
msg = wallet_protocol.RegisterForPhUpdates(phs, 0)
|
||||
msg = wallet_protocol.RegisterForPhUpdates(phs, uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_puzzle_hash(msg, con)
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_ph_update.value
|
||||
s = full_node_api.full_node.subscriptions
|
||||
@@ -698,24 +681,24 @@ async def test_ph_subscribe_limits(simulator_and_wallet, self_hostname):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_coin_subscribe_limits(simulator_and_wallet, self_hostname):
|
||||
async def test_coin_subscribe_limits(simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str) -> None:
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
full_node_api = full_nodes[0]
|
||||
wallet_node, server_2 = wallets[0]
|
||||
_, server_2 = wallets[0]
|
||||
fn_server = full_node_api.full_node.server
|
||||
await server_2.start_client(PeerInfo(self_hostname, fn_server.get_port()), None)
|
||||
con = list(fn_server.all_connections.values())[0]
|
||||
coins = []
|
||||
coins.append(32 * b"\0")
|
||||
coins.append(32 * b"\1")
|
||||
coins.append(32 * b"\2")
|
||||
coins.append(32 * b"\3")
|
||||
coins.append(32 * b"\4")
|
||||
coins.append(32 * b"\5")
|
||||
coins.append(32 * b"\6")
|
||||
coins.append(bytes32(32 * b"\0"))
|
||||
coins.append(bytes32(32 * b"\1"))
|
||||
coins.append(bytes32(32 * b"\2"))
|
||||
coins.append(bytes32(32 * b"\3"))
|
||||
coins.append(bytes32(32 * b"\4"))
|
||||
coins.append(bytes32(32 * b"\5"))
|
||||
coins.append(bytes32(32 * b"\6"))
|
||||
full_node_api.full_node.config["max_subscribe_items"] = 2
|
||||
assert full_node_api.is_trusted(con) is False
|
||||
msg = wallet_protocol.RegisterForCoinUpdates(coins, 0)
|
||||
msg = wallet_protocol.RegisterForCoinUpdates(coins, uint32(0))
|
||||
msg_response = await full_node_api.register_interest_in_coin(msg, con)
|
||||
assert msg_response.type == ProtocolMessageTypes.respond_to_coin_update.value
|
||||
s = full_node_api.full_node.subscriptions
|
||||
|
||||
@@ -166,7 +166,7 @@ async def test_notifications(
|
||||
assert len(notifications) == 1
|
||||
assert notifications[0].message == b"allow_larger"
|
||||
assert (
|
||||
await notification_manager_2.notification_store.get_notifications([n.coin_id for n in notifications])
|
||||
await notification_manager_2.notification_store.get_notifications([n.id for n in notifications])
|
||||
== notifications
|
||||
)
|
||||
|
||||
@@ -176,7 +176,7 @@ async def test_notifications(
|
||||
await notification_manager_2.notification_store.delete_all_notifications()
|
||||
assert len(await notification_manager_2.notification_store.get_all_notifications()) == 0
|
||||
await notification_manager_2.notification_store.add_notification(notifications[0])
|
||||
await notification_manager_2.notification_store.delete_notifications([n.coin_id for n in notifications])
|
||||
await notification_manager_2.notification_store.delete_notifications([n.id for n in notifications])
|
||||
assert len(await notification_manager_2.notification_store.get_all_notifications()) == 0
|
||||
|
||||
assert not await func(*notification_manager_2.most_recent_args)
|
||||
|
||||
Reference in New Issue
Block a user