async def receive_block( self, block_record: HeaderBlockRecord, pre_validation_result: Optional[PreValidationResult] = None, trusted: bool = False, fork_point_with_peak: Optional[uint32] = None, ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]: """ Adds a new block into the blockchain, if it's valid and connected to the current blockchain, regardless of whether it is the child of a head, or another block. Returns a header if block is added to head. Returns an error if the block is invalid. Also returns the fork height, in the case of a new peak. """ block = block_record.header genesis: bool = block.height == 0 if self.contains_sub_block(block.header_hash): return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None if not self.contains_sub_block(block.prev_header_hash) and not genesis: return ( ReceiveBlockResult.DISCONNECTED_BLOCK, Err.INVALID_PREV_BLOCK_HASH, None, ) if block.height == 0: prev_sb: Optional[SubBlockRecord] = None else: prev_sb = self.sub_block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_sub_slot_iters_and_difficulty( self.constants, block, prev_sb, self) if trusted is False and pre_validation_result is None: required_iters, error = validate_finished_header_block( self.constants, self, block, False, difficulty, sub_slot_iters) elif trusted: unfinished_header_block = UnfinishedHeaderBlock( block.finished_sub_slots, block.reward_chain_sub_block.get_unfinished(), block.challenge_chain_sp_proof, block.reward_chain_sp_proof, block.foliage_sub_block, block.foliage_block, block.transactions_filter, ) required_iters, val_error = validate_unfinished_header_block( self.constants, self, unfinished_header_block, False, difficulty, sub_slot_iters, False, True) error = ValidationError( Err(val_error)) if val_error is not None else None else: assert pre_validation_result is not None required_iters = pre_validation_result.required_iters error = (ValidationError(Err(pre_validation_result.error)) if pre_validation_result.error is not None else None) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code, None assert required_iters is not None sub_block = block_to_sub_block_record( self.constants, self, required_iters, None, block, ) # Always add the block to the database await self.block_store.add_block_record(block_record, sub_block) self.add_sub_block(sub_block) self.clean_sub_block_record(sub_block.height - self.constants.SUB_BLOCKS_CACHE_SIZE) fork_height: Optional[uint32] = await self._reconsider_peak( sub_block, genesis, fork_point_with_peak) if fork_height is not None: self.log.info( f"💰 Updated wallet peak to sub height {sub_block.height}, weight {sub_block.weight}, " ) return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None
async def add_spendbundle( self, new_spend: SpendBundle, cost_result: CostResult, spend_name: bytes32, validate_signature=True, ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: """ Tries to add spendbundle to either self.mempools or to_pool if it's specified. Returns true if it's added in any of pools, Returns error if it fails. """ start_time = time.time() if self.peak is None: return None, MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED npc_list = cost_result.npc_list cost = cost_result.cost log.debug(f"Cost: {cost}") if cost > self.constants.MAX_BLOCK_COST_CLVM: return None, MempoolInclusionStatus.FAILED, Err.BLOCK_COST_EXCEEDS_MAX if cost_result.error is not None: return None, MempoolInclusionStatus.FAILED, Err(cost_result.error) # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = additions_for_npc(npc_list) additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount > self.constants.MAX_COIN_AMOUNT: return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Check for duplicate inputs removal_counter = collections.Counter(name for name in removal_names) for k, v in removal_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DOUBLE_SPEND # Skip if already added if spend_name in self.mempool.spends: return uint64(cost), MempoolInclusionStatus.SUCCESS, None removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = await self.coin_store.get_coin_record(name) if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] # TODO(straya): what timestamp to use here? removal_record = CoinRecord( removal_coin, uint32(self.peak.height + 1), # In mempool, so will be included in next height uint32(0), False, False, uint64(int(time.time())), ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN_UNSPENT if addition_amount > removal_amount: print(addition_amount, removal_amount) return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.RESERVE_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ConditionOpcode.RESERVE_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee if fees < assert_fee_sum: return ( None, MempoolInclusionStatus.FAILED, Err.RESERVE_FEE_CONDITION_FAILED, ) if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN fees_per_cost: float = fees / cost # If pool is at capacity check the fee, if not then accept even without the fee if self.mempool.at_full_capacity(): if fees == 0: return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE if fees_per_cost < self.mempool.get_min_fee_rate(): return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE # Check removals against UnspentDB + DiffStore + Mempool + SpendBundle # Use this information later when constructing a block fail_reason, conflicts = await self.check_removals(removal_record_dict) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, MempoolItem] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: sb: MempoolItem = self.mempool.removals[conflicting.name()] conflicting_pool_items[sb.name] = sb for item in conflicting_pool_items.values(): if item.fee_per_cost >= fees_per_cost: self.add_to_potential_tx_set(new_spend, spend_name, cost_result) return ( uint64(cost), MempoolInclusionStatus.PENDING, Err.MEMPOOL_CONFLICT, ) elif fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason if tmp_error: return None, MempoolInclusionStatus.FAILED, tmp_error # Verify conditions, create hash_key list for aggsig check pks: List[G1Element] = [] msgs: List[bytes32] = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: log.warning("Mempool rejecting transaction because of wrong puzzle_hash") log.warning(f"{npc.puzzle_hash} != {coin_record.coin.puzzle_hash}") return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH chialisp_height = ( self.peak.prev_transaction_block_height if not self.peak.is_transaction_block else self.peak.height ) error = mempool_check_conditions_dict(coin_record, new_spend, npc.condition_dict, uint32(chialisp_height)) if error: if error is Err.ASSERT_HEIGHT_NOW_EXCEEDS_FAILED or error is Err.ASSERT_HEIGHT_AGE_EXCEEDS_FAILED: self.add_to_potential_tx_set(new_spend, spend_name, cost_result) return uint64(cost), MempoolInclusionStatus.PENDING, error break if validate_signature: for pk, message in pkm_pairs_for_conditions_dict(npc.condition_dict, npc.coin_name): pks.append(pk) msgs.append(message) if error: return None, MempoolInclusionStatus.FAILED, error if validate_signature: # Verify aggregated signature if not AugSchemeMPL.aggregate_verify(pks, msgs, new_spend.aggregated_signature): log.warning(f"Aggsig validation error {pks} {msgs} {new_spend}") return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE # Remove all conflicting Coins and SpendBundles if fail_reason: mempool_item: MempoolItem for mempool_item in conflicting_pool_items.values(): self.mempool.remove_spend(mempool_item) removals: List[Coin] = [coin for coin in removal_coin_dict.values()] new_item = MempoolItem(new_spend, uint64(fees), cost_result, spend_name, additions, removals) self.mempool.add_to_pool(new_item, additions, removal_coin_dict) log.info(f"add_spendbundle took {time.time() - start_time} seconds") return uint64(cost), MempoolInclusionStatus.SUCCESS, None
async def fetch_blocks_and_validate( self, peer: WSChiaConnection, height_start: uint32, height_end: uint32, fork_point_with_peak: uint32, ) -> Tuple[bool, bool]: """ Returns whether the blocks validated, and whether the peak was advanced """ if self.wallet_state_manager is None: return False, False self.log.info(f"Requesting blocks {height_start}-{height_end}") request = RequestHeaderBlocks(uint32(height_start), uint32(height_end)) res: Optional[RespondHeaderBlocks] = await peer.request_header_blocks( request) if res is None or not isinstance(res, RespondHeaderBlocks): raise ValueError("Peer returned no response") header_blocks: List[HeaderBlock] = res.header_blocks advanced_peak = False if header_blocks is None: raise ValueError(f"No response from peer {peer}") if (self.full_node_peer is not None and peer.peer_host == self.full_node_peer.host or peer.peer_host == "127.0.0.1"): trusted = True pre_validation_results: Optional[List[PreValidationResult]] = None else: trusted = False pre_validation_results = await self.wallet_state_manager.blockchain.pre_validate_blocks_multiprocessing( header_blocks) if pre_validation_results is None: return False, advanced_peak assert len(header_blocks) == len(pre_validation_results) for i in range(len(header_blocks)): header_block = header_blocks[i] if not trusted and pre_validation_results is not None and pre_validation_results[ i].error is not None: raise ValidationError(Err(pre_validation_results[i].error)) fork_point_with_old_peak = None if advanced_peak else fork_point_with_peak if header_block.is_transaction_block: # Find additions and removals ( additions, removals, ) = await self.wallet_state_manager.get_filter_additions_removals( header_block, header_block.transactions_filter, fork_point_with_old_peak) # Get Additions added_coins = await self.get_additions(peer, header_block, additions) if added_coins is None: raise ValueError("Failed to fetch additions") # Get removals removed_coins = await self.get_removals( peer, header_block, added_coins, removals) if removed_coins is None: raise ValueError("Failed to fetch removals") header_block_record = HeaderBlockRecord( header_block, added_coins, removed_coins) else: header_block_record = HeaderBlockRecord(header_block, [], []) start_t = time.time() if trusted: ( result, error, fork_h, ) = await self.wallet_state_manager.blockchain.receive_block( header_block_record, None, trusted, fork_point_with_old_peak) else: assert pre_validation_results is not None ( result, error, fork_h, ) = await self.wallet_state_manager.blockchain.receive_block( header_block_record, pre_validation_results[i], trusted, fork_point_with_old_peak) self.log.debug( f"Time taken to validate {header_block.height} with fork " f"{fork_point_with_old_peak}: {time.time() - start_t}") if result == ReceiveBlockResult.NEW_PEAK: advanced_peak = True self.wallet_state_manager.state_changed("new_block") elif result == ReceiveBlockResult.INVALID_BLOCK: raise ValueError("Value error peer sent us invalid block") return True, advanced_peak
async def validate_block_body( constants: ConsensusConstants, sub_blocks: Dict[bytes32, SubBlockRecord], sub_height_to_hash: Dict[uint32, bytes32], block_store: BlockStore, coin_store: CoinStore, peak: Optional[SubBlockRecord], block: Union[FullBlock, UnfinishedBlock], sub_height: uint32, height: Optional[uint32], cached_cost_result: Optional[CostResult] = None, ) -> Optional[Err]: """ This assumes the header block has been completely validated. Validates the transactions and body of the block. Returns None if everything validates correctly, or an Err if something does not validate. """ if isinstance(block, FullBlock): assert sub_height == block.sub_block_height if height is not None: assert height == block.height assert block.is_block() else: assert not block.is_block() # 1. For non block sub-blocks, foliage block, transaction filter, transactions info, and generator must be empty # If it is a sub block but not a block, there is no body to validate. Check that all fields are None if block.foliage_sub_block.foliage_block_hash is None: if (block.foliage_block is not None or block.transactions_info is not None or block.transactions_generator is not None): return Err.NOT_BLOCK_BUT_HAS_DATA return None # This means the sub-block is valid # 2. For blocks, foliage block, transaction filter, transactions info must not be empty if block.foliage_block is None or block.foliage_block.filter_hash is None or block.transactions_info is None: return Err.IS_BLOCK_BUT_NO_DATA # keeps track of the reward coins that need to be incorporated expected_reward_coins: Set[Coin] = set() # 3. The transaction info hash in the Foliage block must match the transaction info if block.foliage_block.transactions_info_hash != std_hash( block.transactions_info): return Err.INVALID_TRANSACTIONS_INFO_HASH # 4. The foliage block hash in the foliage sub block must match the foliage block if block.foliage_sub_block.foliage_block_hash != std_hash( block.foliage_block): return Err.INVALID_FOLIAGE_BLOCK_HASH # 5. The prev generators root must be valid # TODO(straya): implement prev generators # 6. The generator root must be the tree-hash of the generator (or zeroes if no generator) if block.transactions_generator is not None: if block.transactions_generator.get_tree_hash( ) != block.transactions_info.generator_root: return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT else: if block.transactions_info.generator_root != bytes([0] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT # 7. The reward claims must be valid for the previous sub-blocks, and current block fees if sub_height > 0: # Add reward claims for all sub-blocks from the prev prev block, until the prev block (including the latter) prev_block = sub_blocks[block.foliage_block.prev_block_hash] assert prev_block.fees is not None pool_coin = create_pool_coin( prev_block.sub_block_height, prev_block.pool_puzzle_hash, calculate_pool_reward(prev_block.height), ) farmer_coin = create_farmer_coin( prev_block.sub_block_height, prev_block.farmer_puzzle_hash, uint64( calculate_base_farmer_reward(prev_block.height) + prev_block.fees), ) # Adds the previous block expected_reward_coins.add(pool_coin) expected_reward_coins.add(farmer_coin) # For the second block in the chain, don't go back further if prev_block.sub_block_height > 0: curr_sb = sub_blocks[prev_block.prev_hash] curr_height = curr_sb.height while not curr_sb.is_block: expected_reward_coins.add( create_pool_coin( curr_sb.sub_block_height, curr_sb.pool_puzzle_hash, calculate_pool_reward(curr_height), )) expected_reward_coins.add( create_farmer_coin( curr_sb.sub_block_height, curr_sb.farmer_puzzle_hash, calculate_base_farmer_reward(curr_height), )) curr_sb = sub_blocks[curr_sb.prev_hash] if set(block.transactions_info.reward_claims_incorporated ) != expected_reward_coins: return Err.INVALID_REWARD_COINS removals: List[bytes32] = [] coinbase_additions: List[Coin] = list(expected_reward_coins) additions: List[Coin] = [] npc_list: List[NPC] = [] removals_puzzle_dic: Dict[bytes32, bytes32] = {} cost: uint64 = uint64(0) if block.transactions_generator is not None: # Get List of names removed, puzzles hashes for removed coins and conditions crated if cached_cost_result is not None: result: CostResult = cached_cost_result else: result = calculate_cost_of_program( block.transactions_generator, constants.CLVM_COST_RATIO_CONSTANT) cost = result.cost npc_list = result.npc_list # 8. Check that cost <= MAX_BLOCK_COST_CLVM if cost > constants.MAX_BLOCK_COST_CLVM: return Err.BLOCK_COST_EXCEEDS_MAX if result.error is not None: return Err(result.error) for npc in npc_list: removals.append(npc.coin_name) removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash additions = additions_for_npc(npc_list) # 9. Check that the correct cost is in the transactions info if block.transactions_info.cost != cost: return Err.INVALID_BLOCK_COST additions_dic: Dict[bytes32, Coin] = {} # 10. Check additions for max coin amount for coin in additions + coinbase_additions: additions_dic[coin.name()] = coin if coin.amount >= constants.MAX_COIN_AMOUNT: return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM # 11. Validate addition and removal roots root_error = validate_block_merkle_roots( block.foliage_block.additions_root, block.foliage_block.removals_root, additions + coinbase_additions, removals, ) if root_error: return root_error # 12. The additions and removals must result in the correct filter byte_array_tx: List[bytes32] = [] for coin in additions + coinbase_additions: byte_array_tx.append(bytearray(coin.puzzle_hash)) for coin_name in removals: byte_array_tx.append(bytearray(coin_name)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter = bytes(bip158.GetEncoded()) filter_hash = std_hash(encoded_filter) if filter_hash != block.foliage_block.filter_hash: return Err.INVALID_TRANSACTIONS_FILTER_HASH # 13. Check for duplicate outputs in additions addition_counter = collections.Counter( _.name() for _ in additions + coinbase_additions) for k, v in addition_counter.items(): if v > 1: return Err.DUPLICATE_OUTPUT # 14. Check for duplicate spends inside block removal_counter = collections.Counter(removals) for k, v in removal_counter.items(): if v > 1: return Err.DOUBLE_SPEND # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block) if peak is None or sub_height == 0: fork_sub_h: int = -1 else: fork_sub_h = find_fork_point_in_chain( sub_blocks, peak, sub_blocks[block.prev_header_hash]) if fork_sub_h == -1: coin_store_reorg_height = -1 else: last_sb_in_common = sub_blocks[sub_height_to_hash[uint32(fork_sub_h)]] if last_sb_in_common.is_block: coin_store_reorg_height = last_sb_in_common.height else: coin_store_reorg_height = last_sb_in_common.height - 1 # Get additions and removals since (after) fork_h but not including this block additions_since_fork: Dict[bytes32, Tuple[Coin, uint32]] = {} removals_since_fork: Set[bytes32] = set() coinbases_since_fork: Dict[bytes32, uint32] = {} if sub_height > 0: curr: Optional[FullBlock] = await block_store.get_full_block( block.prev_header_hash) assert curr is not None while curr.sub_block_height > fork_sub_h: removals_in_curr, additions_in_curr = curr.tx_removals_and_additions( ) for c_name in removals_in_curr: removals_since_fork.add(c_name) for c in additions_in_curr: additions_since_fork[c.name()] = (c, curr.sub_block_height) for coinbase_coin in curr.get_included_reward_coins(): additions_since_fork[coinbase_coin.name()] = ( coinbase_coin, curr.sub_block_height) coinbases_since_fork[ coinbase_coin.name()] = curr.sub_block_height if curr.sub_block_height == 0: break curr = await block_store.get_full_block(curr.prev_header_hash) assert curr is not None removal_coin_records: Dict[bytes32, CoinRecord] = {} for rem in removals: if rem in additions_dic: # Ephemeral coin rem_coin: Coin = additions_dic[rem] new_unspent: CoinRecord = CoinRecord( rem_coin, sub_height, uint32(0), False, False, block.foliage_block.timestamp, ) removal_coin_records[new_unspent.name] = new_unspent else: unspent = await coin_store.get_coin_record(rem) if unspent is not None and unspent.confirmed_block_index <= coin_store_reorg_height: # Spending something in the current chain, confirmed before fork # (We ignore all coins confirmed after fork) if unspent.spent == 1 and unspent.spent_block_index <= coin_store_reorg_height: # Check for coins spent in an ancestor block return Err.DOUBLE_SPEND removal_coin_records[unspent.name] = unspent else: # This coin is not in the current heaviest chain, so it must be in the fork if rem not in additions_since_fork: # Check for spending a coin that does not exist in this fork # TODO: fix this, there is a consensus bug here return Err.UNKNOWN_UNSPENT new_coin, confirmed_height = additions_since_fork[rem] new_coin_record: CoinRecord = CoinRecord( new_coin, confirmed_height, uint32(0), False, (rem in coinbases_since_fork), block.foliage_block.timestamp, ) removal_coin_records[new_coin_record.name] = new_coin_record # This check applies to both coins created before fork (pulled from coin_store), # and coins created after fork (additions_since_fork)> if rem in removals_since_fork: # This coin was spent in the fork return Err.DOUBLE_SPEND removed = 0 for unspent in removal_coin_records.values(): removed += unspent.coin.amount added = 0 for coin in additions: added += coin.amount # 16. Check that the total coin amount for added is <= removed if removed < added: return Err.MINTING_COIN fees = removed - added assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.ASSERT_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ ConditionOpcode.ASSERT_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee # 17. Check that the assert fee sum <= fees if fees < assert_fee_sum: return Err.ASSERT_FEE_CONDITION_FAILED # 18. Check that the computed fees are equal to the fees in the block header if block.transactions_info.fees != fees: return Err.INVALID_BLOCK_FEE_AMOUNT # 19. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes for unspent in removal_coin_records.values(): if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]: return Err.WRONG_PUZZLE_HASH # 20. Verify conditions # create hash_key list for aggsig check pairs_pks = [] pairs_msgs = [] for npc in npc_list: assert height is not None unspent = removal_coin_records[npc.coin_name] error = blockchain_check_conditions_dict( unspent, removal_coin_records, npc.condition_dict, height, block.foliage_block.timestamp, ) if error: return error for pk, m in pkm_pairs_for_conditions_dict(npc.condition_dict, npc.coin_name): pairs_pks.append(pk) pairs_msgs.append(m) # 21. Verify aggregated signature # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster if not block.transactions_info.aggregated_signature: return Err.BAD_AGGREGATE_SIGNATURE if len(pairs_pks) == 0: if len( pairs_msgs ) != 0 or block.transactions_info.aggregated_signature != G2Element.infinity( ): return Err.BAD_AGGREGATE_SIGNATURE else: # noinspection PyTypeChecker validates = AugSchemeMPL.aggregate_verify( pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature) if not validates: return Err.BAD_AGGREGATE_SIGNATURE return None
async def fetch_blocks_and_validate(self, peer: WSChiaConnection, sub_height_start: uint32, sub_height_end: uint32): if self.wallet_state_manager is None: return self.log.info(f"Requesting blocks {sub_height_start}-{sub_height_end}") request = RequestHeaderBlocks(uint32(sub_height_start), uint32(sub_height_end)) res: Optional[RespondHeaderBlocks] = await peer.request_header_blocks(request) if res is None or not isinstance(res, RespondHeaderBlocks): raise ValueError("Peer returned no response") header_blocks: List[HeaderBlock] = res.header_blocks if header_blocks is None: raise ValueError(f"No response from peer {peer}") if ( self.full_node_peer is not None and peer.peer_host == self.full_node_peer.host or peer.peer_host == "127.0.0.1" ): trusted = True pre_validation_results: Optional[List[PreValidationResult]] = None else: trusted = False pre_validation_results = await self.wallet_state_manager.blockchain.pre_validate_blocks_multiprocessing( header_blocks ) if pre_validation_results is None: return False assert len(header_blocks) == len(pre_validation_results) for i in range(len(header_blocks)): header_block = header_blocks[i] if not trusted and pre_validation_results is not None and pre_validation_results[i].error is not None: raise ValidationError(Err(pre_validation_results[i].error)) if header_block.is_block: # Find additions and removals (additions, removals,) = await self.wallet_state_manager.get_filter_additions_removals( header_block, header_block.transactions_filter ) # Get Additions added_coins = await self.get_additions(peer, header_block, additions) if added_coins is None: raise ValueError("Failed to fetch additions") # Get removals removed_coins = await self.get_removals(peer, header_block, added_coins, removals) if removed_coins is None: raise ValueError("Failed to fetch removals") header_block_record = HeaderBlockRecord(header_block, added_coins, removed_coins) else: header_block_record = HeaderBlockRecord(header_block, [], []) if trusted: ( result, error, fork_h, ) = await self.wallet_state_manager.blockchain.receive_block(header_block_record, None, trusted) else: assert pre_validation_results is not None (result, error, fork_h,) = await self.wallet_state_manager.blockchain.receive_block( header_block_record, pre_validation_results[i], trusted ) if result == ReceiveBlockResult.NEW_PEAK: self.wallet_state_manager.state_changed("new_block") elif result == ReceiveBlockResult.INVALID_BLOCK: raise ValueError("Value error peer sent us invalid block")