From 78ce6048a8d5e97acec0b2905811bce08a5dbb21 Mon Sep 17 00:00:00 2001 From: Yostra Date: Mon, 27 Sep 2021 18:41:28 -0400 Subject: [PATCH 01/11] full node changes --- chia/consensus/blockchain.py | 140 +++++++++++------- chia/full_node/coin_store.py | 19 ++- chia/full_node/full_node.py | 35 +++-- chia/full_node/full_node_api.py | 56 ++++++- chia/protocols/protocol_message_types.py | 2 + chia/protocols/wallet_protocol.py | 14 ++ tests/core/fixtures.py | 4 +- tests/core/full_node/ram_db.py | 4 +- tests/core/full_node/test_block_store.py | 7 +- tests/core/full_node/test_coin_store.py | 7 +- tests/core/full_node/test_hint_store.py | 95 ++++++++++++ .../simple_sync/test_simple_sync_protocol.py | 75 +++++++++- 12 files changed, 378 insertions(+), 80 deletions(-) create mode 100644 tests/core/full_node/test_hint_store.py diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 6cf56ccd7e58..6d962610be2c 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -6,6 +6,8 @@ from enum import Enum from typing import Dict, List, Optional, Set, Tuple, Union +from clvm.casts import int_from_bytes + from chia.consensus.block_body_validation import validate_block_body from chia.consensus.block_header_validation import validate_finished_header_block, validate_unfinished_header_block from chia.consensus.block_record import BlockRecord @@ -18,12 +20,14 @@ from chia.consensus.multiprocess_validation import PreValidationResult, pre_validate_blocks_multiprocessing 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.types.blockchain_format.coin import Coin from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary from chia.types.blockchain_format.vdf import VDFInfo from chia.types.coin_record import CoinRecord +from chia.types.condition_opcodes import ConditionOpcode from chia.types.end_of_slot_bundle import EndOfSubSlotBundle from chia.types.full_block import FullBlock from chia.types.generator_types import BlockGenerator, GeneratorArg @@ -31,6 +35,7 @@ from chia.types.unfinished_block import UnfinishedBlock from chia.types.unfinished_header_block import UnfinishedHeaderBlock from chia.types.weight_proof import SubEpochChallengeSegment +from chia.util.byte_types import hexstr_to_bytes from chia.util.errors import Err from chia.util.generator_tools import get_block_header, tx_removals_and_additions from chia.util.ints import uint16, uint32, uint64, uint128 @@ -83,12 +88,11 @@ class Blockchain(BlockchainInterface): # Lock to prevent simultaneous reads and writes lock: asyncio.Lock compact_proof_lock: asyncio.Lock + hint_store: HintStore @staticmethod async def create( - coin_store: CoinStore, - block_store: BlockStore, - consensus_constants: ConsensusConstants, + coin_store: CoinStore, block_store: BlockStore, consensus_constants: ConsensusConstants, hint_store: HintStore ): """ Initializes a blockchain with the BlockRecords from disk, assuming they have all been @@ -112,6 +116,7 @@ async def create( self._shut_down = False await self._load_chain_from_store() self._seen_compact_proofs = set() + self.hint_store = hint_store return self def shut_down(self): @@ -164,7 +169,9 @@ async def receive_block( block: FullBlock, pre_validation_result: Optional[PreValidationResult] = None, fork_point_with_peak: Optional[uint32] = None, - ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32], List[CoinRecord]]: + ) -> Tuple[ + ReceiveBlockResult, Optional[Err], Optional[uint32], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]] + ]: """ This method must be called under the blockchain lock Adds a new block into the blockchain, if it's valid and connected to the current @@ -174,18 +181,13 @@ async def receive_block( """ genesis: bool = block.height == 0 if self.contains_block(block.header_hash): - return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None, [] + return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None, ([], {}) if not self.contains_block(block.prev_header_hash) and not genesis: - return ( - ReceiveBlockResult.DISCONNECTED_BLOCK, - Err.INVALID_PREV_BLOCK_HASH, - None, - [], - ) + return (ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ([], {})) if not genesis and (self.block_record(block.prev_header_hash).height + 1) != block.height: - return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None, [] + return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None, ([], {}) npc_result: Optional[NPCResult] = None if pre_validation_result is None: @@ -202,7 +204,7 @@ async def receive_block( try: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) except ValueError: - return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None, [] + return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None, ([], {}) assert block_generator is not None and block.transactions_info is not None npc_result = get_name_puzzle_conditions( block_generator, @@ -228,7 +230,7 @@ async def receive_block( ) if error is not None: - return ReceiveBlockResult.INVALID_BLOCK, error.code, None, [] + return ReceiveBlockResult.INVALID_BLOCK, error.code, None, ([], {}) else: npc_result = pre_validation_result.npc_result required_iters = pre_validation_result.required_iters @@ -247,7 +249,7 @@ async def receive_block( self.get_block_generator, ) if error_code is not None: - return ReceiveBlockResult.INVALID_BLOCK, error_code, None, [] + return ReceiveBlockResult.INVALID_BLOCK, error_code, None, ([], {}) block_record = block_to_block_record( self.constants, @@ -263,7 +265,7 @@ async def receive_block( # Perform the DB operations to update the state, and rollback if something goes wrong await self.block_store.db_wrapper.begin_transaction() await self.block_store.add_full_block(header_hash, block, block_record) - fork_height, peak_height, records, coin_record_change = await self._reconsider_peak( + fork_height, peak_height, records, (coin_record_change, hint_changes) = await self._reconsider_peak( block_record, genesis, fork_point_with_peak, npc_result ) await self.block_store.db_wrapper.commit_transaction() @@ -286,9 +288,24 @@ async def receive_block( if fork_height is not None: # new coin records added assert coin_record_change is not None - return ReceiveBlockResult.NEW_PEAK, None, fork_height, coin_record_change + return ReceiveBlockResult.NEW_PEAK, None, fork_height, (coin_record_change, hint_changes) else: - return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None, [] + return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None, ([], {}) + + def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[str, str]]: + h_list = [] + for npc in npc_result.npc_list: + for opcode, conditions in npc.conditions: + if opcode == ConditionOpcode.CREATE_COIN: + for condition in conditions: + if len(condition.vars) > 2: + puzzle_hash, amount_bin = condition.vars[0], condition.vars[1] + amount = int_from_bytes(amount_bin) + coin_id = Coin(npc.coin_name, puzzle_hash, amount).name().hex() + h_list.append((coin_id, condition.vars[2].hex())) + else: + pass + return h_list async def _reconsider_peak( self, @@ -296,7 +313,9 @@ async def _reconsider_peak( genesis: bool, fork_point_with_peak: Optional[uint32], npc_result: Optional[NPCResult], - ) -> Tuple[Optional[uint32], Optional[uint32], List[BlockRecord], List[CoinRecord]]: + ) -> Tuple[ + Optional[uint32], Optional[uint32], List[BlockRecord], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]] + ]: """ When a new block is added, this is called, to check if the new block is the new peak of the chain. This also handles reorgs by reverting blocks which are not in the heaviest chain. @@ -305,6 +324,8 @@ async def _reconsider_peak( """ peak = self.get_peak() lastest_coin_state: Dict[bytes32, CoinRecord] = {} + hint_coin_state: Dict[bytes32, List[CoinRecord]] = {} + if genesis: if peak is None: block: Optional[FullBlock] = await self.block_store.get_full_block(block_record.header_hash) @@ -326,8 +347,8 @@ async def _reconsider_peak( else: added, _ = [], [] await self.block_store.set_peak(block_record.header_hash) - return uint32(0), uint32(0), [block_record], added - return None, None, [], [] + return uint32(0), uint32(0), [block_record], (added, {}) + return None, None, [], ([], {}) assert peak is not None if block_record.weight > peak.weight: @@ -372,46 +393,63 @@ async def _reconsider_peak( records_to_add = [] for fetched_full_block, fetched_block_record in reversed(blocks_to_add): records_to_add.append(fetched_block_record) - if fetched_block_record.is_transaction_block: + if fetched_full_block.is_transaction_block(): if fetched_block_record.header_hash == block_record.header_hash: - tx_removals, tx_additions = await self.get_tx_removals_and_additions( + tx_removals, tx_additions, npc_res = await self.get_tx_removals_and_additions( fetched_full_block, npc_result ) else: - tx_removals, tx_additions = await self.get_tx_removals_and_additions(fetched_full_block, None) - if fetched_full_block.is_transaction_block(): - assert fetched_full_block.foliage_transaction_block is not None - added_rec = await self.coin_store.new_block( - fetched_full_block.height, - fetched_full_block.foliage_transaction_block.timestamp, - fetched_full_block.get_included_reward_coins(), - tx_additions, - tx_removals, + tx_removals, tx_additions, npc_res = await self.get_tx_removals_and_additions( + fetched_full_block, None ) - removed_rec: List[Optional[CoinRecord]] = [ - await self.coin_store.get_coin_record(name) for name in tx_removals - ] - - # Set additions first, than removals in order to handle ephemeral coin state - # Add in height order is also required - record: Optional[CoinRecord] - for record in added_rec: - assert record - lastest_coin_state[record.name] = record - for record in removed_rec: - assert record - lastest_coin_state[record.name] = record + + assert fetched_full_block.foliage_transaction_block is not None + added_rec = await self.coin_store.new_block( + fetched_full_block.height, + fetched_full_block.foliage_transaction_block.timestamp, + fetched_full_block.get_included_reward_coins(), + tx_additions, + tx_removals, + ) + removed_rec: List[Optional[CoinRecord]] = [ + await self.coin_store.get_coin_record(name) for name in tx_removals + ] + + # Set additions first, than removals in order to handle ephemeral coin state + # Add in height order is also required + record: Optional[CoinRecord] + for record in added_rec: + assert record + lastest_coin_state[record.name] = record + for record in removed_rec: + assert record + lastest_coin_state[record.name] = record + + if npc_res is not None: + hint_list = self.get_hint_list(npc_res) + await self.hint_store.add_hints(hint_list) + # There can be multiple coins for the same hint + for coin_name, hint in hint_list: + key = hexstr_to_bytes(hint) + if key not in hint_coin_state: + hint_coin_state[key] = [] + hint_coin_state[key].append(lastest_coin_state[hexstr_to_bytes(coin_name)]) # Changes the peak to be the new peak await self.block_store.set_peak(block_record.header_hash) - return uint32(max(fork_height, 0)), block_record.height, records_to_add, list(lastest_coin_state.values()) + return ( + uint32(max(fork_height, 0)), + block_record.height, + records_to_add, + (list(lastest_coin_state.values()), hint_coin_state), + ) # This is not a heavier block than the heaviest we have seen, so we don't change the coin set - return None, None, [], list(lastest_coin_state.values()) + return None, None, [], ([], {}) async def get_tx_removals_and_additions( self, block: FullBlock, npc_result: Optional[NPCResult] = None - ) -> Tuple[List[bytes32], List[Coin]]: + ) -> Tuple[List[bytes32], List[Coin], Optional[NPCResult]]: if block.is_transaction_block(): if block.transactions_generator is not None: if npc_result is None: @@ -424,11 +462,11 @@ async def get_tx_removals_and_additions( safe_mode=False, ) tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) - return tx_removals, tx_additions + return tx_removals, tx_additions, npc_result else: - return [], [] + return [], [], None else: - return [], [] + return [], [], None def get_next_difficulty(self, header_hash: bytes32, new_slot: bool) -> uint64: assert self.contains_block(header_hash) diff --git a/chia/full_node/coin_store.py b/chia/full_node/coin_store.py index 62b20821ce81..84ebd50fb2eb 100644 --- a/chia/full_node/coin_store.py +++ b/chia/full_node/coin_store.py @@ -242,6 +242,13 @@ async def get_coin_records_by_names( return list(coins) + def row_to_coin_state(self, row): + coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), uint64.from_bytes(row[7])) + spent_h = None + if row[3]: + spent_h = row[2] + return CoinState(coin, spent_h, row[1]) + async def get_coin_states_by_puzzle_hashes( self, include_spent_coins: bool, @@ -265,11 +272,7 @@ async def get_coin_states_by_puzzle_hashes( await cursor.close() for row in rows: - coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), uint64.from_bytes(row[7])) - spent_h = None - if row[3]: - spent_h = row[2] - coins.add(CoinState(coin, spent_h, row[1])) + coins.add(self.row_to_coin_state(row)) return list(coins) @@ -323,11 +326,7 @@ async def get_coin_state_by_ids( await cursor.close() for row in rows: - coin = Coin(bytes32(bytes.fromhex(row[6])), bytes32(bytes.fromhex(row[5])), uint64.from_bytes(row[7])) - spent_h = None - if row[3]: - spent_h = row[2] - coins.add(CoinState(coin, spent_h, row[1])) + coins.add(self.row_to_coin_state(row)) return list(coins) async def rollback_to_block(self, block_index: int) -> List[CoinRecord]: diff --git a/chia/full_node/full_node.py b/chia/full_node/full_node.py index def6e44c6981..93e09a9554be 100644 --- a/chia/full_node/full_node.py +++ b/chia/full_node/full_node.py @@ -24,6 +24,7 @@ from chia.full_node.bundle_tools import detect_potential_template_generator from chia.full_node.coin_store import CoinStore from chia.full_node.full_node_store import FullNodeStore +from chia.full_node.hint_store import HintStore from chia.full_node.mempool_manager import MempoolManager from chia.full_node.signage_point import SignagePoint from chia.full_node.sync_store import SyncStore @@ -145,10 +146,11 @@ def sql_trace_callback(req: str): self.db_wrapper = DBWrapper(self.connection) self.block_store = await BlockStore.create(self.db_wrapper) self.sync_store = await SyncStore.create() + self.hint_store = await HintStore.create(self.db_wrapper) self.coin_store = await CoinStore.create(self.db_wrapper) self.log.info("Initializing blockchain from disk") start_time = time.time() - self.blockchain = await Blockchain.create(self.coin_store, self.block_store, self.constants) + self.blockchain = await Blockchain.create(self.coin_store, self.block_store, self.constants, self.hint_store) self.mempool_manager = MempoolManager(self.coin_store, self.constants) self.weight_proof_handler = None self._init_weight_proof = asyncio.create_task(self.initialize_weight_proof()) @@ -859,11 +861,17 @@ def get_peers_with_peak(self, peak_hash: bytes32) -> List: return peers_with_peak async def update_wallets( - self, height: uint32, fork_height: uint32, peak_hash: bytes32, state_update: List[CoinRecord] + self, + height: uint32, + fork_height: uint32, + peak_hash: bytes32, + state_update: Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]], ): changes_for_peer: Dict[bytes32, Set[CoinState]] = {} - for coin_record in state_update: + states, hint_state = state_update + + for coin_record in states: if coin_record.name in self.coin_subscriptions: subscribed_peers = self.coin_subscriptions[coin_record.name] for peer in subscribed_peers: @@ -878,6 +886,15 @@ async def update_wallets( changes_for_peer[peer] = set() changes_for_peer[peer].add(coin_record.coin_state) + for hint, records in hint_state.items(): + if hint in self.ph_subscriptions: + subscribed_peers = self.ph_subscriptions[hint] + for peer in subscribed_peers: + if peer not in changes_for_peer: + changes_for_peer[peer] = set() + for record in records: + changes_for_peer[peer].add(record.coin_state) + for peer, changes in changes_for_peer.items(): if peer not in self.server.all_connections: continue @@ -892,7 +909,7 @@ async def receive_block_batch( peer: ws.WSChiaConnection, fork_point: Optional[uint32], wp_summaries: Optional[List[SubEpochSummary]] = None, - ) -> Tuple[bool, bool, Optional[uint32], List[CoinRecord]]: + ) -> Tuple[bool, bool, Optional[uint32], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]]]: advanced_peak = False fork_height: Optional[uint32] = uint32(0) @@ -902,7 +919,7 @@ async def receive_block_batch( blocks_to_validate = all_blocks[i:] break if len(blocks_to_validate) == 0: - return True, False, fork_height, [] + return True, False, fork_height, ([], {}) pre_validate_start = time.time() pre_validation_results: Optional[ @@ -914,13 +931,13 @@ async def receive_block_batch( else: self.log.debug(f"Block pre-validation time: {pre_validate_end - pre_validate_start:0.2f} seconds") if pre_validation_results is None: - return False, False, None, [] + return False, False, None, ([], {}) for i, block in enumerate(blocks_to_validate): if pre_validation_results[i].error is not None: self.log.error( f"Invalid block from peer: {peer.get_peer_logging()} {Err(pre_validation_results[i].error)}" ) - return False, advanced_peak, fork_height, [] + return False, advanced_peak, fork_height, ([], {}) for i, block in enumerate(blocks_to_validate): assert pre_validation_results[i].required_iters is not None @@ -932,7 +949,7 @@ async def receive_block_batch( elif result == ReceiveBlockResult.INVALID_BLOCK or result == ReceiveBlockResult.DISCONNECTED_BLOCK: if error is not None: self.log.error(f"Error: {error}, Invalid block from peer: {peer.get_peer_logging()} ") - return False, advanced_peak, fork_height, [] + return False, advanced_peak, fork_height, ([], {}) block_record = self.blockchain.block_record(block.header_hash) if block_record.sub_epoch_summary_included is not None: if self.weight_proof_handler is not None: @@ -1046,7 +1063,7 @@ async def peak_post_processing( record: BlockRecord, fork_height: uint32, peer: Optional[ws.WSChiaConnection], - coin_changes: List[CoinRecord], + coin_changes: Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]], ): """ Must be called under self.blockchain.lock. This updates the internal state of the full node with the diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 039964aa4f9f..4e71fe4e5352 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -18,12 +18,19 @@ from chia.protocols import farmer_protocol, full_node_protocol, introducer_protocol, timelord_protocol, wallet_protocol from chia.protocols.full_node_protocol import RejectBlock, RejectBlocks from chia.protocols.protocol_message_types import ProtocolMessageTypes -from chia.protocols.wallet_protocol import PuzzleSolutionResponse, RejectHeaderBlocks, RejectHeaderRequest, CoinState +from chia.protocols.wallet_protocol import ( + PuzzleSolutionResponse, + RejectHeaderBlocks, + RejectHeaderRequest, + CoinState, + RespondSESInfo, +) from chia.server.outbound_message import Message, make_msg from chia.types.blockchain_format.coin import Coin, hash_coin_list from chia.types.blockchain_format.pool_target import PoolTarget from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary from chia.types.coin_record import CoinRecord from chia.types.end_of_slot_bundle import EndOfSubSlotBundle from chia.types.full_block import FullBlock @@ -1057,7 +1064,7 @@ async def request_block_header(self, request: wallet_protocol.RequestBlockHeader return msg block: Optional[FullBlock] = await self.full_node.block_store.get_full_block(header_hash) if block is not None: - tx_removals, tx_additions = await self.full_node.blockchain.get_tx_removals_and_additions(block) + tx_removals, tx_additions, _ = await self.full_node.blockchain.get_tx_removals_and_additions(block) header_block = get_block_header(block, tx_additions, tx_removals) msg = make_msg( ProtocolMessageTypes.respond_block_header, @@ -1331,8 +1338,11 @@ async def register_interest_in_puzzle_hash( if peer.peer_node_id not in self.full_node.peer_sub_counter: self.full_node.peer_sub_counter[peer.peer_node_id] = 0 + hints = [] # Add peer to the "Subscribed" dictionary for puzzle_hash in request.puzzle_hashes: + ph_hints = await self.full_node.hint_store.get_hints(puzzle_hash) + hints.extend(ph_hints) if puzzle_hash not in self.full_node.ph_subscriptions: self.full_node.ph_subscriptions[puzzle_hash] = set() if ( @@ -1348,6 +1358,12 @@ async def register_interest_in_puzzle_hash( include_spent_coins=True, puzzle_hashes=request.puzzle_hashes, start_height=request.min_height ) + if len(hints) > 0: + hint_states = await self.full_node.coin_store.get_coin_state_by_ids( + include_spent_coins=True, coin_ids=hints, start_height=request.min_height + ) + states.extend(hint_states) + response = wallet_protocol.RespondToPhUpdates(request.puzzle_hashes, request.min_height, states) msg = make_msg(ProtocolMessageTypes.respond_to_ph_update, response) return msg @@ -1391,3 +1407,39 @@ async def request_children(self, request: wallet_protocol.RequestChildren) -> Op response = wallet_protocol.RespondChildren(states) msg = make_msg(ProtocolMessageTypes.respond_children, response) return msg + + @api_request + async def request_ses_hashes(self, request: wallet_protocol.RequestSESInfo): + ses_height = self.full_node.blockchain.get_ses_heights() + start_height = request.start_height + end_height = request.end_height + start_ses_hash = None + end_ses_hash = None + ses_hash_heights = [] + ses_reward_hashes = [] + + for idx, ses_start_height in enumerate(ses_height): + if idx == len(ses_height) - 1: + break + + next_ses_height = ses_height[idx + 1] + # start_ses_hash + if ses_start_height <= start_height < next_ses_height: + ses_hash_heights.append([ses_start_height, next_ses_height]) + ses: SubEpochSummary = self.full_node.blockchain.get_ses(ses_start_height) + ses_reward_hashes.append(ses.reward_chain_hash) + if ses_start_height < end_height < next_ses_height: + break + else: + if idx == len(ses_height) - 2: + break + # else add extra ses as request start <-> end spans two ses + next_next_height = ses_height[idx + 2] + ses_hash_heights.append([next_ses_height, next_next_height]) + ses: SubEpochSummary = self.full_node.blockchain.get_ses(next_ses_height) + ses_reward_hashes.append(ses.reward_chain_hash) + break + + response = RespondSESInfo(ses_reward_hashes, ses_hash_heights) + msg = make_msg(ProtocolMessageTypes.respond_ses_hashes, response) + return msg diff --git a/chia/protocols/protocol_message_types.py b/chia/protocols/protocol_message_types.py index b24ec7194e5e..7596f4554745 100644 --- a/chia/protocols/protocol_message_types.py +++ b/chia/protocols/protocol_message_types.py @@ -95,3 +95,5 @@ class ProtocolMessageTypes(Enum): respond_to_coin_update = 73 request_children = 74 respond_children = 75 + request_ses_hashes = 76 + respond_ses_hashes = 77 diff --git a/chia/protocols/wallet_protocol.py b/chia/protocols/wallet_protocol.py index 9b6aed0d2096..efb0650aa774 100644 --- a/chia/protocols/wallet_protocol.py +++ b/chia/protocols/wallet_protocol.py @@ -212,3 +212,17 @@ class RequestChildren(Streamable): @streamable class RespondChildren(Streamable): coin_states: List[CoinState] + + +@dataclass(frozen=True) +@streamable +class RequestSESInfo(Streamable): + start_height: uint32 + end_height: uint32 + + +@dataclass(frozen=True) +@streamable +class RespondSESInfo(Streamable): + reward_chain_hash: List[bytes32] + heights: List[List[uint32]] diff --git a/tests/core/fixtures.py b/tests/core/fixtures.py index 004f19b08b86..ae5191e868a3 100644 --- a/tests/core/fixtures.py +++ b/tests/core/fixtures.py @@ -10,6 +10,7 @@ from chia.consensus.constants import ConsensusConstants 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.types.full_block import FullBlock from chia.util.db_wrapper import DBWrapper from chia.util.path import mkdir @@ -29,7 +30,8 @@ async def create_blockchain(constants: ConsensusConstants): wrapper = DBWrapper(connection) coin_store = await CoinStore.create(wrapper) store = await BlockStore.create(wrapper) - bc1 = await Blockchain.create(coin_store, store, constants) + hint_store = await HintStore.create(wrapper) + bc1 = await Blockchain.create(coin_store, store, constants, hint_store) assert bc1.get_peak() is None return bc1, connection, db_path diff --git a/tests/core/full_node/ram_db.py b/tests/core/full_node/ram_db.py index d71435649df6..2aaecb043f88 100644 --- a/tests/core/full_node/ram_db.py +++ b/tests/core/full_node/ram_db.py @@ -6,6 +6,7 @@ from chia.consensus.constants import ConsensusConstants 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.util.db_wrapper import DBWrapper @@ -14,5 +15,6 @@ async def create_ram_blockchain(consensus_constants: ConsensusConstants) -> Tupl db_wrapper = DBWrapper(connection) block_store = await BlockStore.create(db_wrapper) coin_store = await CoinStore.create(db_wrapper) - blockchain = await Blockchain.create(coin_store, block_store, consensus_constants) + hint_store = await HintStore.create(db_wrapper) + blockchain = await Blockchain.create(coin_store, block_store, consensus_constants, hint_store) return connection, blockchain diff --git a/tests/core/full_node/test_block_store.py b/tests/core/full_node/test_block_store.py index 63fe3e9306cc..8733c499f71c 100644 --- a/tests/core/full_node/test_block_store.py +++ b/tests/core/full_node/test_block_store.py @@ -10,6 +10,7 @@ from chia.consensus.blockchain import Blockchain 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.util.db_wrapper import DBWrapper from tests.setup_nodes import bt, test_constants @@ -44,7 +45,8 @@ async def test_block_store(self): # Use a different file for the blockchain coin_store_2 = await CoinStore.create(db_wrapper_2) store_2 = await BlockStore.create(db_wrapper_2) - bc = await Blockchain.create(coin_store_2, store_2, test_constants) + hint_store = await HintStore.create(db_wrapper_2) + bc = await Blockchain.create(coin_store_2, store_2, test_constants, hint_store) store = await BlockStore.create(db_wrapper) await BlockStore.create(db_wrapper_2) @@ -105,7 +107,8 @@ async def test_deadlock(self): store = await BlockStore.create(wrapper) coin_store_2 = await CoinStore.create(wrapper_2) store_2 = await BlockStore.create(wrapper_2) - bc = await Blockchain.create(coin_store_2, store_2, test_constants) + hint_store = await HintStore.create(wrapper_2) + bc = await Blockchain.create(coin_store_2, store_2, test_constants, hint_store) block_records = [] for block in blocks: await bc.receive_block(block) diff --git a/tests/core/full_node/test_coin_store.py b/tests/core/full_node/test_coin_store.py index 71afe2c81be2..64b5f998b653 100644 --- a/tests/core/full_node/test_coin_store.py +++ b/tests/core/full_node/test_coin_store.py @@ -12,6 +12,7 @@ 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.types.blockchain_format.coin import Coin from chia.types.coin_record import CoinRecord @@ -263,7 +264,8 @@ async def test_basic_reorg(self, cache_size: uint32): blocks = bt.get_consecutive_blocks(initial_block_count) coin_store = await CoinStore.create(db_wrapper, cache_size=uint32(cache_size)) store = await BlockStore.create(db_wrapper) - b: Blockchain = await Blockchain.create(coin_store, store, test_constants) + hint_store = await HintStore.create(db_wrapper) + b: Blockchain = await Blockchain.create(coin_store, store, test_constants, hint_store) try: records: List[Optional[CoinRecord]] = [] @@ -327,7 +329,8 @@ async def test_get_puzzle_hash(self, cache_size: uint32): ) coin_store = await CoinStore.create(db_wrapper, cache_size=uint32(cache_size)) store = await BlockStore.create(db_wrapper) - b: Blockchain = await Blockchain.create(coin_store, store, test_constants) + hint_store = await HintStore.create(db_wrapper) + b: Blockchain = await Blockchain.create(coin_store, store, test_constants, hint_store) for block in blocks: res, err, _, _ = await b.receive_block(block) assert err is None diff --git a/tests/core/full_node/test_hint_store.py b/tests/core/full_node/test_hint_store.py new file mode 100644 index 000000000000..835a125bef48 --- /dev/null +++ b/tests/core/full_node/test_hint_store.py @@ -0,0 +1,95 @@ +import asyncio +import logging +from pathlib import Path +from typing import List, Optional, Set, Tuple + +import aiosqlite +import pytest +import tempfile + +from clvm.casts import int_to_bytes + +from chia.consensus.blockchain import Blockchain, ReceiveBlockResult +from chia.full_node.hint_store import HintStore +from chia.types.blockchain_format.coin import Coin +from chia.types.condition_opcodes import ConditionOpcode +from chia.types.condition_with_args import ConditionWithArgs +from chia.types.full_block import FullBlock +from chia.types.spend_bundle import SpendBundle +from tests.core.full_node.test_coin_store import DBConnection +from tests.wallet_tools import WalletTool +from tests.setup_nodes import bt +from tests.core.fixtures import empty_blockchain # noqa: F401 + + +@pytest.fixture(scope="module") +def event_loop(): + loop = asyncio.get_event_loop() + yield loop + + +log = logging.getLogger(__name__) + + + +class TestHintStore: + @pytest.mark.asyncio + async def test_basic_store(self): + async with DBConnection() as db_wrapper: + hint_store = await HintStore.create(db_wrapper) + hint_0 = 32*b"\0" + hint_1 = 32*b"\1" + not_existing_hint = 32*b"\3" + + coin_id_0 = 32*b"\4" + coin_id_1 = 32*b"\5" + coin_id_2 = 32*b'\6' + + hints = [(coin_id_0.hex(), hint_0.hex()), (coin_id_1.hex(), hint_0.hex()), (coin_id_2.hex(), hint_1.hex())] + await hint_store.add_hints(hints) + await db_wrapper.commit_transaction() + coins_for_hint_0 = await hint_store.get_hints(hint_0) + + assert coin_id_0 in coins_for_hint_0 + assert coin_id_1 in coins_for_hint_0 + + coins_for_hint_1 = await hint_store.get_hints(hint_1) + assert coin_id_2 in coins_for_hint_1 + + coins_for_non_hint = await hint_store.get_hints(not_existing_hint) + assert coins_for_non_hint == [] + + @pytest.mark.asyncio + async def test_hints_in_blockchain(self, empty_blockchain): + blockchain: Blockchain = empty_blockchain + + blocks = bt.get_consecutive_blocks( + 5, + block_list_input=[], + guarantee_transaction_block=True, + farmer_reward_puzzle_hash=bt.pool_ph, + pool_reward_puzzle_hash=bt.pool_ph, + ) + for block in blocks: + await blockchain.receive_block(block) + + wt: WalletTool = bt.get_pool_wallet_tool() + puzzle_hash = 32*b"\0" + amount = int_to_bytes(1) + hint = 32*b"\5" + coin_spent = list(blocks[-1].get_included_reward_coins())[0] + condition_dict = {ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount, hint])]} + tx: SpendBundle = wt.generate_signed_transaction( + 10, wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict, + ) + + blocks = bt.get_consecutive_blocks( + 10, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx + ) + + for block in blocks: + await blockchain.receive_block(block) + + get_hint = await blockchain.hint_store.get_hints(hint) + + assert get_hint[0] == Coin(coin_spent.name(), puzzle_hash, 1).name() diff --git a/tests/wallet/simple_sync/test_simple_sync_protocol.py b/tests/wallet/simple_sync/test_simple_sync_protocol.py index 97930dc76e58..fd5ba8490ccd 100644 --- a/tests/wallet/simple_sync/test_simple_sync_protocol.py +++ b/tests/wallet/simple_sync/test_simple_sync_protocol.py @@ -3,24 +3,30 @@ from typing import List, Optional import pytest +from clvm.casts import int_to_bytes from colorlog import logging from chia.consensus.block_rewards import calculate_pool_reward, calculate_base_farmer_reward -from chia.protocols import wallet_protocol +from chia.protocols import wallet_protocol, full_node_protocol +from chia.protocols.full_node_protocol import RespondTransaction from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.protocols.wallet_protocol import RespondToCoinUpdates, CoinStateUpdate, RespondToPhUpdates from chia.server.outbound_message import NodeType from chia.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol from chia.types.blockchain_format.coin import Coin 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 uint16, uint32, uint64 from chia.wallet.wallet import Wallet from chia.wallet.wallet_state_manager import WalletStateManager from tests.connection_utils import add_dummy_connection -from tests.setup_nodes import self_hostname, setup_simulators_and_wallets +from tests.setup_nodes import self_hostname, setup_simulators_and_wallets, bt from tests.time_out_assert import time_out_assert from tests.wallet.cc_wallet.test_cc_wallet import tx_in_pool +from tests.wallet_tools import WalletTool def wallet_height_at_least(wallet_node, h): @@ -452,3 +458,68 @@ async def test_subscribe_for_coin_id_reorg(self, wallet_node_simulator): second_coin = coin_states[1] assert second_coin.spent_height is None assert second_coin.created_height is None + + @pytest.mark.asyncio + async def test_subscribe_for_hint(self, wallet_node_simulator): + num_blocks = 4 + full_nodes, wallets = wallet_node_simulator + 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, uint16(fn_server._port)), None) + incoming_queue, peer_id = await add_dummy_connection(fn_server, 12312, NodeType.WALLET) + + + wt: WalletTool = bt.get_pool_wallet_tool() + ph = wt.get_new_puzzlehash() + for i in range(0, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + await asyncio.sleep(6) + coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hashes(False, [ph]) + coin_spent = coins[0].coin + puzzle_hash = 32*b"\0" + amount = 1 + amount_bin = int_to_bytes(1) + hint = 32*b"\5" + + fake_wallet_peer = fn_server.all_connections[peer_id] + msg = wallet_protocol.RegisterForPhUpdates([hint], 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) + assert len(data_response.coin_states) == 0 + + condition_dict = {ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount_bin, hint])]} + tx: SpendBundle = wt.generate_signed_transaction( + 10, wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict, + ) + await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer) + + await time_out_assert( + 15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx.name() + ) + + for i in range(0, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash)) + + all_messages = await self.get_all_messages_in_queue(incoming_queue) + + notified_state = None + + 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 + break + + assert notified_state is not None + assert notified_state.items[0].coin == Coin(coin_spent.name(), puzzle_hash, amount) + + msg = wallet_protocol.RegisterForPhUpdates([hint], 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) + assert len(data_response.coin_states) == 1 From 9af8b7fd4183f0fc4dc7d7020e75f9959f238568 Mon Sep 17 00:00:00 2001 From: Yostra Date: Mon, 27 Sep 2021 18:43:23 -0400 Subject: [PATCH 02/11] hint store --- chia/full_node/hint_store.py | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 chia/full_node/hint_store.py diff --git a/chia/full_node/hint_store.py b/chia/full_node/hint_store.py new file mode 100644 index 000000000000..fffb48a991e8 --- /dev/null +++ b/chia/full_node/hint_store.py @@ -0,0 +1,41 @@ +from typing import List, Tuple +import aiosqlite +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.util.byte_types import hexstr_to_bytes +from chia.util.db_wrapper import DBWrapper +import logging + +log = logging.getLogger(__name__) + + +class HintStore: + coin_record_db: aiosqlite.Connection + db_wrapper: DBWrapper + + @classmethod + async def create(cls, db_wrapper: DBWrapper): + self = cls() + self.db_wrapper = db_wrapper + self.coin_record_db = db_wrapper.db + # the coin_id is unique, same hint can be used for multiple coins + await self.coin_record_db.execute(("CREATE TABLE IF NOT EXISTS hints(coin_id text PRIMARY KEY, hint text)")) + await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS hint_index on hints(hint)") + await self.coin_record_db.commit() + return self + + async def get_hints(self, hint: bytes) -> List[bytes32]: + cursor = await self.coin_record_db.execute("SELECT * from hints WHERE hint=?", (hint.hex(),)) + rows = await cursor.fetchall() + await cursor.close() + coin_ids = [] + for row in rows: + coin_id = row[0] + coin_ids.append(hexstr_to_bytes(row[0])) + return coin_ids + + async def add_hints(self, coin_hint_list: List[Tuple[str, str]]) -> None: + cursor = await self.coin_record_db.executemany( + "INSERT INTO hints VALUES(?, ?)", + coin_hint_list, + ) + await cursor.close() From 4c70fe97c955f80c6824869e85439d6d788e1744 Mon Sep 17 00:00:00 2001 From: Yostra Date: Mon, 27 Sep 2021 22:27:04 -0400 Subject: [PATCH 03/11] finish coin state tracking, mypy, flake8 --- chia/consensus/blockchain.py | 27 +++++++------ chia/full_node/full_node.py | 27 ++++++++++--- chia/full_node/full_node_api.py | 8 ++-- chia/full_node/hint_store.py | 10 ++--- tests/core/full_node/test_hint_store.py | 38 +++++++++---------- .../simple_sync/test_simple_sync_protocol.py | 20 ++++++---- 6 files changed, 74 insertions(+), 56 deletions(-) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 6d962610be2c..a7d22f03f5c5 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -35,7 +35,6 @@ from chia.types.unfinished_block import UnfinishedBlock from chia.types.unfinished_header_block import UnfinishedHeaderBlock from chia.types.weight_proof import SubEpochChallengeSegment -from chia.util.byte_types import hexstr_to_bytes from chia.util.errors import Err from chia.util.generator_tools import get_block_header, tx_removals_and_additions from chia.util.ints import uint16, uint32, uint64, uint128 @@ -170,7 +169,10 @@ async def receive_block( pre_validation_result: Optional[PreValidationResult] = None, fork_point_with_peak: Optional[uint32] = None, ) -> Tuple[ - ReceiveBlockResult, Optional[Err], Optional[uint32], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]] + ReceiveBlockResult, + Optional[Err], + Optional[uint32], + Tuple[List[CoinRecord], Dict[bytes, Dict[bytes32, CoinRecord]]], ]: """ This method must be called under the blockchain lock @@ -292,17 +294,17 @@ async def receive_block( else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None, ([], {}) - def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[str, str]]: + def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[bytes, bytes]]: h_list = [] for npc in npc_result.npc_list: for opcode, conditions in npc.conditions: if opcode == ConditionOpcode.CREATE_COIN: for condition in conditions: - if len(condition.vars) > 2: + if len(condition.vars) > 2 and condition.vars[2] != b"": puzzle_hash, amount_bin = condition.vars[0], condition.vars[1] amount = int_from_bytes(amount_bin) - coin_id = Coin(npc.coin_name, puzzle_hash, amount).name().hex() - h_list.append((coin_id, condition.vars[2].hex())) + coin_id = Coin(npc.coin_name, puzzle_hash, amount).name() + h_list.append((coin_id, condition.vars[2])) else: pass return h_list @@ -314,7 +316,10 @@ async def _reconsider_peak( fork_point_with_peak: Optional[uint32], npc_result: Optional[NPCResult], ) -> Tuple[ - Optional[uint32], Optional[uint32], List[BlockRecord], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]] + Optional[uint32], + Optional[uint32], + List[BlockRecord], + Tuple[List[CoinRecord], Dict[bytes, Dict[bytes32, CoinRecord]]], ]: """ When a new block is added, this is called, to check if the new block is the new peak of the chain. @@ -324,7 +329,7 @@ async def _reconsider_peak( """ peak = self.get_peak() lastest_coin_state: Dict[bytes32, CoinRecord] = {} - hint_coin_state: Dict[bytes32, List[CoinRecord]] = {} + hint_coin_state: Dict[bytes32, Dict[bytes32, CoinRecord]] = {} if genesis: if peak is None: @@ -430,10 +435,10 @@ async def _reconsider_peak( await self.hint_store.add_hints(hint_list) # There can be multiple coins for the same hint for coin_name, hint in hint_list: - key = hexstr_to_bytes(hint) + key = hint if key not in hint_coin_state: - hint_coin_state[key] = [] - hint_coin_state[key].append(lastest_coin_state[hexstr_to_bytes(coin_name)]) + hint_coin_state[key] = {} + hint_coin_state[key][coin_name] = lastest_coin_state[coin_name] # Changes the peak to be the new peak await self.block_store.set_peak(block_record.header_hash) diff --git a/chia/full_node/full_node.py b/chia/full_node/full_node.py index 93e09a9554be..8b9e25db8050 100644 --- a/chia/full_node/full_node.py +++ b/chia/full_node/full_node.py @@ -865,7 +865,7 @@ async def update_wallets( height: uint32, fork_height: uint32, peak_hash: bytes32, - state_update: Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]], + state_update: Tuple[List[CoinRecord], Dict[bytes, Dict[bytes32, CoinRecord]]], ): changes_for_peer: Dict[bytes32, Set[CoinState]] = {} @@ -892,7 +892,7 @@ async def update_wallets( for peer in subscribed_peers: if peer not in changes_for_peer: changes_for_peer[peer] = set() - for record in records: + for record in records.values(): changes_for_peer[peer].add(record.coin_state) for peer, changes in changes_for_peer.items(): @@ -909,7 +909,7 @@ async def receive_block_batch( peer: ws.WSChiaConnection, fork_point: Optional[uint32], wp_summaries: Optional[List[SubEpochSummary]] = None, - ) -> Tuple[bool, bool, Optional[uint32], Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]]]: + ) -> Tuple[bool, bool, Optional[uint32], Tuple[List[CoinRecord], Dict[bytes, Dict[bytes, CoinRecord]]]]: advanced_peak = False fork_height: Optional[uint32] = uint32(0) @@ -939,11 +939,26 @@ async def receive_block_batch( ) return False, advanced_peak, fork_height, ([], {}) + # Dicts because deduping + all_coin_changes: Dict[bytes32, CoinRecord] = {} + all_hint_changes: Dict[bytes, Dict[bytes32, CoinRecord]] = {} + for i, block in enumerate(blocks_to_validate): assert pre_validation_results[i].required_iters is not None result, error, fork_height, coin_changes = await self.blockchain.receive_block( block, pre_validation_results[i], None if advanced_peak else fork_point ) + coin_record_list, hint_records = coin_changes + + # Update all changes + for record in coin_record_list: + all_coin_changes[record.name] = record + for hint, list_of_records in hint_records.items(): + if hint not in all_hint_changes: + all_hint_changes[hint] = {} + for record in list_of_records: + all_hint_changes[hint][record.name] = record + if result == ReceiveBlockResult.NEW_PEAK: advanced_peak = True elif result == ReceiveBlockResult.INVALID_BLOCK or result == ReceiveBlockResult.DISCONNECTED_BLOCK: @@ -960,7 +975,7 @@ async def receive_block_batch( f"Total time for {len(blocks_to_validate)} blocks: {time.time() - pre_validate_start}, " f"advanced: {advanced_peak}" ) - return True, advanced_peak, fork_height, coin_changes + return True, advanced_peak, fork_height, (list(all_coin_changes.values()), all_hint_changes) async def _finish_sync(self): """ @@ -1063,7 +1078,7 @@ async def peak_post_processing( record: BlockRecord, fork_height: uint32, peer: Optional[ws.WSChiaConnection], - coin_changes: Tuple[List[CoinRecord], Dict[bytes, List[CoinRecord]]], + coin_changes: Tuple[List[CoinRecord], Dict[bytes, Dict[bytes32, CoinRecord]]], ): """ Must be called under self.blockchain.lock. This updates the internal state of the full node with the @@ -1276,7 +1291,7 @@ async def respond_block( ) # This recursion ends here, we cannot recurse again because transactions_generator is not None return await self.respond_block(block_response, peer) - coin_changes: List[CoinRecord] = [] + coin_changes: Tuple[List[CoinRecord], Dict[bytes, Dict[bytes32, CoinRecord]]] = ([], {}) async with self.blockchain.lock: # After acquiring the lock, check again, because another asyncio thread might have added it if self.blockchain.contains_block(header_hash): diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 4e71fe4e5352..bbdc6e18f47f 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -1410,11 +1410,11 @@ async def request_children(self, request: wallet_protocol.RequestChildren) -> Op @api_request async def request_ses_hashes(self, request: wallet_protocol.RequestSESInfo): + """Returns the start and end height of a sub-epoch for the height specified in request""" + ses_height = self.full_node.blockchain.get_ses_heights() start_height = request.start_height end_height = request.end_height - start_ses_hash = None - end_ses_hash = None ses_hash_heights = [] ses_reward_hashes = [] @@ -1436,8 +1436,8 @@ async def request_ses_hashes(self, request: wallet_protocol.RequestSESInfo): # else add extra ses as request start <-> end spans two ses next_next_height = ses_height[idx + 2] ses_hash_heights.append([next_ses_height, next_next_height]) - ses: SubEpochSummary = self.full_node.blockchain.get_ses(next_ses_height) - ses_reward_hashes.append(ses.reward_chain_hash) + nex_ses: SubEpochSummary = self.full_node.blockchain.get_ses(next_ses_height) + ses_reward_hashes.append(nex_ses.reward_chain_hash) break response = RespondSESInfo(ses_reward_hashes, ses_hash_heights) diff --git a/chia/full_node/hint_store.py b/chia/full_node/hint_store.py index fffb48a991e8..9ed300452cc7 100644 --- a/chia/full_node/hint_store.py +++ b/chia/full_node/hint_store.py @@ -1,7 +1,6 @@ from typing import List, Tuple import aiosqlite from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.util.byte_types import hexstr_to_bytes from chia.util.db_wrapper import DBWrapper import logging @@ -18,22 +17,21 @@ async def create(cls, db_wrapper: DBWrapper): self.db_wrapper = db_wrapper self.coin_record_db = db_wrapper.db # the coin_id is unique, same hint can be used for multiple coins - await self.coin_record_db.execute(("CREATE TABLE IF NOT EXISTS hints(coin_id text PRIMARY KEY, hint text)")) + await self.coin_record_db.execute(("CREATE TABLE IF NOT EXISTS hints(coin_id blob PRIMARY KEY, hint blob)")) await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS hint_index on hints(hint)") await self.coin_record_db.commit() return self async def get_hints(self, hint: bytes) -> List[bytes32]: - cursor = await self.coin_record_db.execute("SELECT * from hints WHERE hint=?", (hint.hex(),)) + cursor = await self.coin_record_db.execute("SELECT * from hints WHERE hint=?", (hint,)) rows = await cursor.fetchall() await cursor.close() coin_ids = [] for row in rows: - coin_id = row[0] - coin_ids.append(hexstr_to_bytes(row[0])) + coin_ids.append(row[0]) return coin_ids - async def add_hints(self, coin_hint_list: List[Tuple[str, str]]) -> None: + async def add_hints(self, coin_hint_list: List[Tuple[bytes, bytes]]) -> None: cursor = await self.coin_record_db.executemany( "INSERT INTO hints VALUES(?, ?)", coin_hint_list, diff --git a/tests/core/full_node/test_hint_store.py b/tests/core/full_node/test_hint_store.py index 835a125bef48..6a675e10be46 100644 --- a/tests/core/full_node/test_hint_store.py +++ b/tests/core/full_node/test_hint_store.py @@ -1,25 +1,17 @@ import asyncio import logging -from pathlib import Path -from typing import List, Optional, Set, Tuple - -import aiosqlite import pytest -import tempfile - from clvm.casts import int_to_bytes -from chia.consensus.blockchain import Blockchain, ReceiveBlockResult +from chia.consensus.blockchain import Blockchain from chia.full_node.hint_store import HintStore from chia.types.blockchain_format.coin import Coin from chia.types.condition_opcodes import ConditionOpcode from chia.types.condition_with_args import ConditionWithArgs -from chia.types.full_block import FullBlock from chia.types.spend_bundle import SpendBundle from tests.core.full_node.test_coin_store import DBConnection from tests.wallet_tools import WalletTool from tests.setup_nodes import bt -from tests.core.fixtures import empty_blockchain # noqa: F401 @pytest.fixture(scope="module") @@ -31,21 +23,20 @@ def event_loop(): log = logging.getLogger(__name__) - class TestHintStore: @pytest.mark.asyncio async def test_basic_store(self): async with DBConnection() as db_wrapper: hint_store = await HintStore.create(db_wrapper) - hint_0 = 32*b"\0" - hint_1 = 32*b"\1" - not_existing_hint = 32*b"\3" + hint_0 = 32 * b"\0" + hint_1 = 32 * b"\1" + not_existing_hint = 32 * b"\3" - coin_id_0 = 32*b"\4" - coin_id_1 = 32*b"\5" - coin_id_2 = 32*b'\6' + coin_id_0 = 32 * b"\4" + coin_id_1 = 32 * b"\5" + coin_id_2 = 32 * b"\6" - hints = [(coin_id_0.hex(), hint_0.hex()), (coin_id_1.hex(), hint_0.hex()), (coin_id_2.hex(), hint_1.hex())] + hints = [(coin_id_0, hint_0), (coin_id_1, hint_0), (coin_id_2, hint_1)] await hint_store.add_hints(hints) await db_wrapper.commit_transaction() coins_for_hint_0 = await hint_store.get_hints(hint_0) @@ -74,13 +65,18 @@ async def test_hints_in_blockchain(self, empty_blockchain): await blockchain.receive_block(block) wt: WalletTool = bt.get_pool_wallet_tool() - puzzle_hash = 32*b"\0" + puzzle_hash = 32 * b"\0" amount = int_to_bytes(1) - hint = 32*b"\5" + hint = 32 * b"\5" coin_spent = list(blocks[-1].get_included_reward_coins())[0] - condition_dict = {ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount, hint])]} + condition_dict = { + ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount, hint])] + } tx: SpendBundle = wt.generate_signed_transaction( - 10, wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict, + 10, + wt.get_new_puzzlehash(), + coin_spent, + condition_dic=condition_dict, ) blocks = bt.get_consecutive_blocks( diff --git a/tests/wallet/simple_sync/test_simple_sync_protocol.py b/tests/wallet/simple_sync/test_simple_sync_protocol.py index fd5ba8490ccd..4a6d10775fe9 100644 --- a/tests/wallet/simple_sync/test_simple_sync_protocol.py +++ b/tests/wallet/simple_sync/test_simple_sync_protocol.py @@ -471,7 +471,6 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): await server_2.start_client(PeerInfo(self_hostname, uint16(fn_server._port)), None) incoming_queue, peer_id = await add_dummy_connection(fn_server, 12312, NodeType.WALLET) - wt: WalletTool = bt.get_pool_wallet_tool() ph = wt.get_new_puzzlehash() for i in range(0, num_blocks): @@ -480,10 +479,10 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): await asyncio.sleep(6) coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hashes(False, [ph]) coin_spent = coins[0].coin - puzzle_hash = 32*b"\0" + puzzle_hash = 32 * b"\0" amount = 1 amount_bin = int_to_bytes(1) - hint = 32*b"\5" + hint = 32 * b"\5" fake_wallet_peer = fn_server.all_connections[peer_id] msg = wallet_protocol.RegisterForPhUpdates([hint], 0) @@ -492,15 +491,20 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): data_response: RespondToPhUpdates = RespondToCoinUpdates.from_bytes(msg_response.data) assert len(data_response.coin_states) == 0 - condition_dict = {ConditionOpcode.CREATE_COIN: [ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount_bin, hint])]} + condition_dict = { + ConditionOpcode.CREATE_COIN: [ + ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount_bin, hint]) + ] + } tx: SpendBundle = wt.generate_signed_transaction( - 10, wt.get_new_puzzlehash(), coin_spent, condition_dic=condition_dict, + 10, + wt.get_new_puzzlehash(), + coin_spent, + condition_dic=condition_dict, ) await full_node_api.respond_transaction(RespondTransaction(tx), fake_wallet_peer) - await time_out_assert( - 15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx.name() - ) + await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx.name()) for i in range(0, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash)) From 071ceb6b1d76663f8b56a98d276b39a8efd5f307 Mon Sep 17 00:00:00 2001 From: Yostra Date: Tue, 28 Sep 2021 13:32:12 -0400 Subject: [PATCH 04/11] type hints --- chia/consensus/blockchain.py | 8 ++++---- chia/full_node/hint_store.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index a7d22f03f5c5..96755209810c 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -294,7 +294,7 @@ async def receive_block( else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None, ([], {}) - def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[bytes, bytes]]: + def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[bytes32, bytes]]: h_list = [] for npc in npc_result.npc_list: for opcode, conditions in npc.conditions: @@ -431,14 +431,14 @@ async def _reconsider_peak( lastest_coin_state[record.name] = record if npc_res is not None: - hint_list = self.get_hint_list(npc_res) + hint_list: List[Tuple[bytes32, bytes]] = self.get_hint_list(npc_res) await self.hint_store.add_hints(hint_list) # There can be multiple coins for the same hint - for coin_name, hint in hint_list: + for coin_id, hint in hint_list: key = hint if key not in hint_coin_state: hint_coin_state[key] = {} - hint_coin_state[key][coin_name] = lastest_coin_state[coin_name] + hint_coin_state[key][coin_id] = lastest_coin_state[coin_id] # Changes the peak to be the new peak await self.block_store.set_peak(block_record.header_hash) diff --git a/chia/full_node/hint_store.py b/chia/full_node/hint_store.py index 9ed300452cc7..da0f31917705 100644 --- a/chia/full_node/hint_store.py +++ b/chia/full_node/hint_store.py @@ -31,7 +31,7 @@ async def get_hints(self, hint: bytes) -> List[bytes32]: coin_ids.append(row[0]) return coin_ids - async def add_hints(self, coin_hint_list: List[Tuple[bytes, bytes]]) -> None: + async def add_hints(self, coin_hint_list: List[Tuple[bytes32, bytes]]) -> None: cursor = await self.coin_record_db.executemany( "INSERT INTO hints VALUES(?, ?)", coin_hint_list, From 490b71bb37b8b2e9dc22d55472920df74bdbdf36 Mon Sep 17 00:00:00 2001 From: Yostra Date: Tue, 28 Sep 2021 13:35:33 -0400 Subject: [PATCH 05/11] bump protocol version --- chia/protocols/shared_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/protocols/shared_protocol.py b/chia/protocols/shared_protocol.py index 27342a1e61d9..d7c0e6cd94a8 100644 --- a/chia/protocols/shared_protocol.py +++ b/chia/protocols/shared_protocol.py @@ -5,7 +5,7 @@ from chia.util.ints import uint8, uint16 from chia.util.streamable import Streamable, streamable -protocol_version = "0.0.32" +protocol_version = "0.0.33" """ Handshake when establishing a connection between two servers. From 54c0eba8fe2ece6e99927c4191b0a4fa65bae258 Mon Sep 17 00:00:00 2001 From: Yostra Date: Tue, 28 Sep 2021 20:47:11 -0400 Subject: [PATCH 06/11] change wallet tool for testing hint list --- tests/wallet_tools.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/wallet_tools.py b/tests/wallet_tools.py index 7e540975cca6..22f53f3c8993 100644 --- a/tests/wallet_tools.py +++ b/tests/wallet_tools.py @@ -86,7 +86,13 @@ def make_solution(self, condition_dic: Dict[ConditionOpcode, List[ConditionWithA for con_list in condition_dic.values(): for cvp in con_list: - ret.append([cvp.opcode.value] + cvp.vars) + if cvp.opcode == ConditionOpcode.CREATE_COIN and len(cvp.vars) > 2: + formatted = [] + formatted.extend(cvp.vars) + formatted[2] = cvp.vars[2:] + ret.append([cvp.opcode.value] + formatted) + else: + ret.append([cvp.opcode.value] + cvp.vars) return solution_for_conditions(Program.to(ret)) def generate_unsigned_transaction( From 898994c5ef1b4c7ce58aa301cbb6d72d5387e2d8 Mon Sep 17 00:00:00 2001 From: Yostra Date: Tue, 28 Sep 2021 20:48:38 -0400 Subject: [PATCH 07/11] mypy --- tests/wallet_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/wallet_tools.py b/tests/wallet_tools.py index 22f53f3c8993..82fac970824c 100644 --- a/tests/wallet_tools.py +++ b/tests/wallet_tools.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple, Any from blspy import AugSchemeMPL, G2Element, PrivateKey @@ -87,7 +87,7 @@ def make_solution(self, condition_dic: Dict[ConditionOpcode, List[ConditionWithA for con_list in condition_dic.values(): for cvp in con_list: if cvp.opcode == ConditionOpcode.CREATE_COIN and len(cvp.vars) > 2: - formatted = [] + formatted: List[Any] = [] formatted.extend(cvp.vars) formatted[2] = cvp.vars[2:] ret.append([cvp.opcode.value] + formatted) From 89cdda8a301fc8a26dfa02ccc01dbd3fe689b1da Mon Sep 17 00:00:00 2001 From: Yostra Date: Wed, 29 Sep 2021 14:16:17 -0400 Subject: [PATCH 08/11] add check for state, future proof hint db for multiple hints per coin --- chia/full_node/hint_store.py | 11 ++++++----- .../simple_sync/test_simple_sync_protocol.py | 15 ++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/chia/full_node/hint_store.py b/chia/full_node/hint_store.py index da0f31917705..c2e231be8802 100644 --- a/chia/full_node/hint_store.py +++ b/chia/full_node/hint_store.py @@ -16,8 +16,9 @@ async def create(cls, db_wrapper: DBWrapper): self = cls() self.db_wrapper = db_wrapper self.coin_record_db = db_wrapper.db - # the coin_id is unique, same hint can be used for multiple coins - await self.coin_record_db.execute(("CREATE TABLE IF NOT EXISTS hints(coin_id blob PRIMARY KEY, hint blob)")) + await self.coin_record_db.execute( + "CREATE TABLE IF NOT EXISTS hints(id INTEGER PRIMARY KEY AUTOINCREMENT, coin_id blob, hint blob)" + ) await self.coin_record_db.execute("CREATE INDEX IF NOT EXISTS hint_index on hints(hint)") await self.coin_record_db.commit() return self @@ -28,12 +29,12 @@ async def get_hints(self, hint: bytes) -> List[bytes32]: await cursor.close() coin_ids = [] for row in rows: - coin_ids.append(row[0]) + coin_ids.append(row[1]) return coin_ids async def add_hints(self, coin_hint_list: List[Tuple[bytes32, bytes]]) -> None: cursor = await self.coin_record_db.executemany( - "INSERT INTO hints VALUES(?, ?)", - coin_hint_list, + "INSERT INTO hints VALUES(?, ?, ?)", + [(None,) + record for record in coin_hint_list], ) await cursor.close() diff --git a/tests/wallet/simple_sync/test_simple_sync_protocol.py b/tests/wallet/simple_sync/test_simple_sync_protocol.py index 4a6d10775fe9..abf850b5b022 100644 --- a/tests/wallet/simple_sync/test_simple_sync_protocol.py +++ b/tests/wallet/simple_sync/test_simple_sync_protocol.py @@ -10,7 +10,7 @@ from chia.protocols import wallet_protocol, full_node_protocol from chia.protocols.full_node_protocol import RespondTransaction from chia.protocols.protocol_message_types import ProtocolMessageTypes -from chia.protocols.wallet_protocol import RespondToCoinUpdates, CoinStateUpdate, RespondToPhUpdates +from chia.protocols.wallet_protocol import RespondToCoinUpdates, CoinStateUpdate, RespondToPhUpdates, CoinState from chia.server.outbound_message import NodeType from chia.simulator.simulator_protocol import FarmNewBlockProtocol, ReorgProtocol from chia.types.blockchain_format.coin import Coin @@ -479,7 +479,7 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): await asyncio.sleep(6) coins = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hashes(False, [ph]) coin_spent = coins[0].coin - puzzle_hash = 32 * b"\0" + hint_puzzle_hash = 32 * b"\2" amount = 1 amount_bin = int_to_bytes(1) hint = 32 * b"\5" @@ -493,7 +493,7 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): condition_dict = { ConditionOpcode.CREATE_COIN: [ - ConditionWithArgs(ConditionOpcode.CREATE_COIN, [puzzle_hash, amount_bin, hint]) + ConditionWithArgs(ConditionOpcode.CREATE_COIN, [hint_puzzle_hash, amount_bin, hint]) ] } tx: SpendBundle = wt.generate_signed_transaction( @@ -507,7 +507,7 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): await time_out_assert(15, tx_in_pool, True, full_node_api.full_node.mempool_manager, tx.name()) for i in range(0, num_blocks): - await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash)) + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) all_messages = await self.get_all_messages_in_queue(incoming_queue) @@ -520,10 +520,15 @@ async def test_subscribe_for_hint(self, wallet_node_simulator): break assert notified_state is not None - assert notified_state.items[0].coin == Coin(coin_spent.name(), puzzle_hash, amount) + assert notified_state.items[0].coin == Coin(coin_spent.name(), hint_puzzle_hash, amount) msg = wallet_protocol.RegisterForPhUpdates([hint], 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) assert len(data_response.coin_states) == 1 + coin_records: List[CoinRecord] = await full_node_api.full_node.coin_store.get_coin_records_by_puzzle_hash( + True, hint_puzzle_hash + ) + assert len(coin_records) == 1 + assert data_response.coin_states[0] == coin_records[0].coin_state From 0bb2a5ee15ece782c92e9792653e3daf788afe81 Mon Sep 17 00:00:00 2001 From: Yostra Date: Wed, 29 Sep 2021 14:20:43 -0400 Subject: [PATCH 09/11] get hint rename --- chia/full_node/full_node_api.py | 10 +++++----- chia/full_node/hint_store.py | 2 +- tests/core/full_node/test_hint_store.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index bbdc6e18f47f..2b96d86194e6 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -1338,11 +1338,11 @@ async def register_interest_in_puzzle_hash( if peer.peer_node_id not in self.full_node.peer_sub_counter: self.full_node.peer_sub_counter[peer.peer_node_id] = 0 - hints = [] + hint_coin_ids = [] # Add peer to the "Subscribed" dictionary for puzzle_hash in request.puzzle_hashes: - ph_hints = await self.full_node.hint_store.get_hints(puzzle_hash) - hints.extend(ph_hints) + ph_hint_coins = await self.full_node.hint_store.get_coin_ids(puzzle_hash) + hint_coin_ids.extend(ph_hint_coins) if puzzle_hash not in self.full_node.ph_subscriptions: self.full_node.ph_subscriptions[puzzle_hash] = set() if ( @@ -1358,9 +1358,9 @@ async def register_interest_in_puzzle_hash( include_spent_coins=True, puzzle_hashes=request.puzzle_hashes, start_height=request.min_height ) - if len(hints) > 0: + if len(hint_coin_ids) > 0: hint_states = await self.full_node.coin_store.get_coin_state_by_ids( - include_spent_coins=True, coin_ids=hints, start_height=request.min_height + include_spent_coins=True, coin_ids=hint_coin_ids, start_height=request.min_height ) states.extend(hint_states) diff --git a/chia/full_node/hint_store.py b/chia/full_node/hint_store.py index c2e231be8802..9d431e3902f1 100644 --- a/chia/full_node/hint_store.py +++ b/chia/full_node/hint_store.py @@ -23,7 +23,7 @@ async def create(cls, db_wrapper: DBWrapper): await self.coin_record_db.commit() return self - async def get_hints(self, hint: bytes) -> List[bytes32]: + async def get_coin_ids(self, hint: bytes) -> List[bytes32]: cursor = await self.coin_record_db.execute("SELECT * from hints WHERE hint=?", (hint,)) rows = await cursor.fetchall() await cursor.close() diff --git a/tests/core/full_node/test_hint_store.py b/tests/core/full_node/test_hint_store.py index 6a675e10be46..e7064a074a89 100644 --- a/tests/core/full_node/test_hint_store.py +++ b/tests/core/full_node/test_hint_store.py @@ -39,15 +39,15 @@ async def test_basic_store(self): hints = [(coin_id_0, hint_0), (coin_id_1, hint_0), (coin_id_2, hint_1)] await hint_store.add_hints(hints) await db_wrapper.commit_transaction() - coins_for_hint_0 = await hint_store.get_hints(hint_0) + coins_for_hint_0 = await hint_store.get_coin_ids(hint_0) assert coin_id_0 in coins_for_hint_0 assert coin_id_1 in coins_for_hint_0 - coins_for_hint_1 = await hint_store.get_hints(hint_1) + coins_for_hint_1 = await hint_store.get_coin_ids(hint_1) assert coin_id_2 in coins_for_hint_1 - coins_for_non_hint = await hint_store.get_hints(not_existing_hint) + coins_for_non_hint = await hint_store.get_coin_ids(not_existing_hint) assert coins_for_non_hint == [] @pytest.mark.asyncio @@ -86,6 +86,6 @@ async def test_hints_in_blockchain(self, empty_blockchain): for block in blocks: await blockchain.receive_block(block) - get_hint = await blockchain.hint_store.get_hints(hint) + get_hint = await blockchain.hint_store.get_coin_ids(hint) assert get_hint[0] == Coin(coin_spent.name(), puzzle_hash, 1).name() From 2ee6c840505e1d12c50d966d19949b4aa59815a9 Mon Sep 17 00:00:00 2001 From: Yostra Date: Wed, 29 Sep 2021 14:22:27 -0400 Subject: [PATCH 10/11] clean --- chia/consensus/blockchain.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 96755209810c..ac5cc45ce388 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -305,8 +305,6 @@ def get_hint_list(self, npc_result: NPCResult) -> List[Tuple[bytes32, bytes]]: amount = int_from_bytes(amount_bin) coin_id = Coin(npc.coin_name, puzzle_hash, amount).name() h_list.append((coin_id, condition.vars[2])) - else: - pass return h_list async def _reconsider_peak( From 21c94294bd6c896f443ccc931058a5d6523b7f1a Mon Sep 17 00:00:00 2001 From: Mariano Sorgente <3069354+mariano54@users.noreply.github.com> Date: Wed, 29 Sep 2021 16:26:45 -0400 Subject: [PATCH 11/11] Update chia/consensus/blockchain.py Co-authored-by: Arvid Norberg --- chia/consensus/blockchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index ac5cc45ce388..04cddb47e8b1 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -418,7 +418,7 @@ async def _reconsider_peak( await self.coin_store.get_coin_record(name) for name in tx_removals ] - # Set additions first, than removals in order to handle ephemeral coin state + # Set additions first, then removals in order to handle ephemeral coin state # Add in height order is also required record: Optional[CoinRecord] for record in added_rec: