async def respond_header(self, response: wallet_protocol.RespondHeader): """ The full node responds to our RequestHeader call. We cannot finish this block until we have the required additions / removals for our wallets. """ while True: if self._shut_down: return # We loop, to avoid infinite recursion. At the end of each iteration, we might want to # process the next block, if it exists. block = response.header_block # If we already have, return if block.header_hash in self.wallet_state_manager.block_records: return if block.height < 1: return block_record = BlockRecord( block.header_hash, block.prev_header_hash, block.height, block.weight, None, None, response.header_block.header.data.total_iters, response.header_block.challenge.get_hash(), ) if self.wallet_state_manager.sync_mode: self.potential_blocks_received[uint32(block.height)].set() self.potential_header_hashes[block.height] = block.header_hash # Caches the block so we can finalize it when additions and removals arrive self.cached_blocks[block_record.header_hash] = ( block_record, block, response.transactions_filter, ) if block.prev_header_hash not in self.wallet_state_manager.block_records: # We do not have the previous block record, so wait for that. When the previous gets added to chain, # this method will get called again and we can continue. During sync, the previous blocks are already # requested. During normal operation, this might not be the case. self.future_block_hashes[ block.prev_header_hash] = block.header_hash lca = self.wallet_state_manager.block_records[ self.wallet_state_manager.lca] if (block_record.height - lca.height < self.short_sync_threshold and not self.wallet_state_manager.sync_mode): # Only requests the previous block if we are not in sync mode, close to the new block, # and don't have prev header_request = wallet_protocol.RequestHeader( uint32(block_record.height - 1), block_record.prev_header_hash, ) yield OutboundMessage( NodeType.FULL_NODE, Message("request_header", header_request), Delivery.RESPOND, ) return # If the block has transactions that we are interested in, fetch adds/deletes if response.transactions_filter is not None: ( additions, removals, ) = await self.wallet_state_manager.get_filter_additions_removals( block_record, response.transactions_filter) if len(additions) > 0 or len(removals) > 0: request_a = wallet_protocol.RequestAdditions( block.height, block.header_hash, additions) yield OutboundMessage( NodeType.FULL_NODE, Message("request_additions", request_a), Delivery.RESPOND, ) return # If we don't have any transactions in filter, don't fetch, and finish the block block_record = BlockRecord( block_record.header_hash, block_record.prev_header_hash, block_record.height, block_record.weight, [], [], block_record.total_iters, block_record.new_challenge_hash, ) respond_header_msg: Optional[ wallet_protocol.RespondHeader] = await self._block_finished( block_record, block, response.transactions_filter) if respond_header_msg is None: return else: response = respond_header_msg
async def test_request_additions(self, two_nodes, wallet_blocks): full_node_1, full_node_2, server_1, server_2 = two_nodes wallet_a, wallet_receiver, blocks = wallet_blocks await server_2.start_client(PeerInfo("localhost", uint16(server_1._port)), None) blocks_list = await get_block_path(full_node_1) blocks_new = bt.get_consecutive_blocks( test_constants, 5, seed=b"test_request_additions" ) # Request additinos for nonexisting block fails msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest) # Request additions for orphaned block fails for block in blocks_new: async for _ in full_node_1.respond_block(fnp.RespondBlock(block)): pass msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-1].height, blocks_new[-1].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RejectAdditionsRequest) # If there are no transactions, only cb and fees additions blocks_new = bt.get_consecutive_blocks( test_constants, 10, block_list=blocks_list, ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[-4].height, blocks_new[-4].header_hash, None ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 2 assert msgs[0].message.data.proofs is None # Add a block with transactions spend_bundles = [] puzzle_hashes = [wallet_a.get_new_puzzlehash(), wallet_a.get_new_puzzlehash()] for i in range(5): spend_bundles.append( wallet_a.generate_signed_transaction( 100, puzzle_hashes[i % 2], blocks_new[i - 8].get_coinbase(), ) ) height_with_transactions = len(blocks_new) + 1 agg = SpendBundle.aggregate(spend_bundles) dic_h = { height_with_transactions: ( best_solution_program(agg), agg.aggregated_signature, ) } blocks_new = bt.get_consecutive_blocks( test_constants, 5, block_list=blocks_new, transaction_data_at_height=dic_h ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] # If no puzzle hashes requested, respond all coins and NO proof msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, None, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) # One puzzle hash with change and fee (x3) = 9, minus two repeated ph = 7 + coinbase and fees = 9 assert len(msgs[0].message.data.coins) == 9 assert msgs[0].message.data.proofs is None additions_merkle_set = MerkleSet() for sb in spend_bundles: for coin in sb.additions(): if coin is not None: additions_merkle_set.add_already_hashed(coin.name()) # Ask for one coin and check both PoI ph_list = [puzzle_hashes[0]] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 1 assert len(msgs[0].message.data.coins[0][1]) == 3 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) coin_list_for_ph = [ coin for coin in blocks_new[height_with_transactions].additions() if coin.puzzle_hash == ph_list[0] ] assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, hash_coin_list(coin_list_for_ph), msgs[0].message.data.proofs[0][2], ) # Ask for one ph and check PoE ph_list = [token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 1 assert len(msgs[0].message.data.coins[0][1]) == 0 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 1 assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) assert msgs[0].message.data.proofs[0][2] is None # Ask for two puzzle_hashes ph_list = [puzzle_hashes[0], token_bytes(32)] msgs = [ _ async for _ in full_node_1.request_additions( wallet_protocol.RequestAdditions( blocks_new[height_with_transactions].height, blocks_new[height_with_transactions].header_hash, ph_list, ) ) ] assert len(msgs) == 1 assert isinstance(msgs[0].message.data, wallet_protocol.RespondAdditions) assert len(msgs[0].message.data.coins) == 2 assert len(msgs[0].message.data.coins[0][1]) == 3 assert msgs[0].message.data.proofs is not None assert len(msgs[0].message.data.proofs) == 2 assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[0], msgs[0].message.data.proofs[0][1], ) assert confirm_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, hash_coin_list(coin_list_for_ph), msgs[0].message.data.proofs[0][2], ) assert confirm_not_included_already_hashed( blocks_new[height_with_transactions].header.data.additions_root, ph_list[1], msgs[0].message.data.proofs[1][1], ) assert msgs[0].message.data.proofs[1][2] is None