async def add_full_block(self, block: FullBlock, block_record: BlockRecord) -> None: self.block_cache.put(block.header_hash, block) cursor_1 = await self.db.execute( "INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?, ?)", ( block.header_hash.hex(), block.height, int(block.is_transaction_block()), int(block.is_fully_compactified()), bytes(block), ), ) await cursor_1.close() cursor_2 = await self.db.execute( "INSERT OR REPLACE INTO block_records VALUES(?, ?, ?, ?,?, ?, ?)", ( block.header_hash.hex(), block.prev_header_hash.hex(), block.height, bytes(block_record), None if block_record.sub_epoch_summary_included is None else bytes(block_record.sub_epoch_summary_included), False, block.is_transaction_block(), ), ) await cursor_2.close() await self.db.commit()
async def add_full_block(self, block: FullBlock, block_record: BlockRecord) -> None: cached = self.block_cache.get(block.header_hash) if cached is not None: # Since write to db can fail, we remove from cache here to avoid potential inconsistency # Adding to cache only from reading self.block_cache.put(block.header_hash, None) cursor_1 = await self.db.execute( "INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?, ?)", ( block.header_hash.hex(), block.height, int(block.is_transaction_block()), int(block.is_fully_compactified()), bytes(block), ), ) await cursor_1.close() cursor_2 = await self.db.execute( "INSERT OR REPLACE INTO block_records VALUES(?, ?, ?, ?,?, ?, ?)", ( block.header_hash.hex(), block.prev_header_hash.hex(), block.height, bytes(block_record), None if block_record.sub_epoch_summary_included is None else bytes(block_record.sub_epoch_summary_included), False, block.is_transaction_block(), ), ) await cursor_2.close()
async def add_full_block( self, header_hash: bytes32, block: FullBlock, block_record: BlockRecord, in_main_chain: bool ) -> None: self.block_cache.put(header_hash, block) if self.db_wrapper.db_version == 2: ses: Optional[bytes] = ( None if block_record.sub_epoch_summary_included is None else bytes(block_record.sub_epoch_summary_included) ) await self.db.execute( "INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?, ?, ?, ?, ?)", ( header_hash, block.prev_header_hash, block.height, ses, int(block.is_fully_compactified()), in_main_chain, # in_main_chain self.compress(block), bytes(block_record), ), ) else: await self.db.execute( "INSERT OR REPLACE INTO full_blocks VALUES(?, ?, ?, ?, ?)", ( header_hash.hex(), block.height, int(block.is_transaction_block()), int(block.is_fully_compactified()), bytes(block), ), ) await self.db.execute( "INSERT OR REPLACE INTO block_records VALUES(?, ?, ?, ?,?, ?, ?)", ( header_hash.hex(), block.prev_header_hash.hex(), block.height, bytes(block_record), None if block_record.sub_epoch_summary_included is None else bytes(block_record.sub_epoch_summary_included), False, block.is_transaction_block(), ), )
def get_block_header(block: FullBlock, tx_addition_coins: List[Coin], removals_names: List[bytes32]) -> HeaderBlock: # Create filter byte_array_tx: List[bytes32] = [] addition_coins = tx_addition_coins + list( block.get_included_reward_coins()) if block.is_transaction_block(): for coin in addition_coins: byte_array_tx.append(bytearray(coin.puzzle_hash)) for name in removals_names: byte_array_tx.append(bytearray(name)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter: bytes = bytes(bip158.GetEncoded()) return HeaderBlock( block.finished_sub_slots, block.reward_chain_block, block.challenge_chain_sp_proof, block.challenge_chain_ip_proof, block.reward_chain_sp_proof, block.reward_chain_ip_proof, block.infused_challenge_chain_ip_proof, block.foliage, block.foliage_transaction_block, encoded_filter, block.transactions_info, )
async def get_tx_removals_and_additions( self, block: FullBlock, npc_result: Optional[NPCResult] = None ) -> Tuple[List[bytes32], List[Coin]]: if block.is_transaction_block(): if block.transactions_generator is not None: if npc_result is None: block_generator: Optional[ BlockGenerator] = await self.get_block_generator(block) assert block_generator is not None npc_result = get_name_puzzle_conditions( block_generator, self.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.constants.COST_PER_BYTE, safe_mode=False, rust_checker=block.height > self.constants.RUST_CONDITION_CHECKER, ) tx_removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) return tx_removals, tx_additions else: return [], [] else: return [], []
def run_and_get_removals_and_additions( block: FullBlock, max_cost: int, cost_per_byte: int, rust_checker: bool, safe_mode=False) -> Tuple[List[bytes32], List[Coin]]: removals: List[bytes32] = [] additions: List[Coin] = [] assert len(block.transactions_generator_ref_list) == 0 if not block.is_transaction_block(): return [], [] if block.transactions_generator is not None: npc_result = get_name_puzzle_conditions( BlockGenerator(block.transactions_generator, []), max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode, rust_checker=rust_checker, ) # build removals list for npc in npc_result.npc_list: removals.append(npc.coin_name) additions.extend(additions_for_npc(npc_result.npc_list)) rewards = block.get_included_reward_coins() additions.extend(rewards) return removals, additions
def get_block_header(block: FullBlock, tx_addition_coins: List[Coin], removals_names: List[bytes32]) -> HeaderBlock: # Create filter byte_array_tx: List[bytes32] = [] addition_coins = tx_addition_coins + list( block.get_included_reward_coins()) if block.is_transaction_block(): for coin in addition_coins: # TODO: address hint error and remove ignore # error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32" # [arg-type] byte_array_tx.append(bytearray( coin.puzzle_hash)) # type: ignore[arg-type] for name in removals_names: # TODO: address hint error and remove ignore # error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32" # [arg-type] byte_array_tx.append(bytearray(name)) # type: ignore[arg-type] bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded_filter: bytes = bytes(bip158.GetEncoded()) return HeaderBlock( block.finished_sub_slots, block.reward_chain_block, block.challenge_chain_sp_proof, block.challenge_chain_ip_proof, block.reward_chain_sp_proof, block.reward_chain_ip_proof, block.infused_challenge_chain_ip_proof, block.foliage, block.foliage_transaction_block, encoded_filter, block.transactions_info, )
async def get_removals_and_additions(self, block: FullBlock) -> Tuple[List[bytes32], List[Coin]]: if block.is_transaction_block(): if block.transactions_generator is not None: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) assert block_generator is not None npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False) removals, additions = block_removals_and_additions(block, npc_result.npc_list) return removals, additions else: return [], list(block.get_included_reward_coins()) else: return [], []
async def get_tx_removals_and_additions( self, block: FullBlock, npc_result: Optional[NPCResult] = None ) -> Tuple[List[bytes32], List[Coin]]: if block.is_transaction_block(): if block.transactions_generator is not None: if npc_result is None: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) assert block_generator is not None npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False) tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) return tx_removals, tx_additions else: return [], [] else: return [], []
def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]: pool_amount = calculate_pool_reward(block.height) farmer_amount = calculate_base_farmer_reward(block.height) if block.is_transaction_block(): assert block.transactions_info is not None farmer_amount = uint64(farmer_amount + block.transactions_info.fees) pool_coin: Coin = create_pool_coin( block.height, block.foliage.foliage_block_data.pool_target.puzzle_hash, pool_amount, constants.GENESIS_CHALLENGE) farmer_coin: Coin = create_farmer_coin( block.height, block.foliage.foliage_block_data.farmer_reward_puzzle_hash, farmer_amount, constants.GENESIS_CHALLENGE, ) return pool_coin, farmer_coin
async def new_block(self, block: FullBlock, tx_additions: List[Coin], tx_removals: List[bytes32]): """ Only called for blocks which are blocks (and thus have rewards and transactions) """ if block.is_transaction_block() is False: return assert block.foliage_transaction_block is not None for coin in tx_additions: record: CoinRecord = CoinRecord( coin, block.height, uint32(0), False, False, block.foliage_transaction_block.timestamp, ) await self._add_coin_record(record, False) included_reward_coins = block.get_included_reward_coins() if block.height == 0: assert len(included_reward_coins) == 0 else: assert len(included_reward_coins) >= 2 for coin in included_reward_coins: reward_coin_r: CoinRecord = CoinRecord( coin, block.height, uint32(0), False, True, block.foliage_transaction_block.timestamp, ) await self._add_coin_record(reward_coin_r, False) total_amount_spent: int = 0 for coin_name in tx_removals: total_amount_spent += await self._set_spent( coin_name, block.height) # Sanity check, already checked in block_body_validation assert sum([a.amount for a in tx_additions]) <= total_amount_spent
async def new_block(self, block: FullBlock, additions: List[Coin], removals: List[bytes32]): """ Only called for blocks which are blocks (and thus have rewards and transactions) """ if block.is_transaction_block() is False: return assert block.foliage_transaction_block is not None for coin in additions: record: CoinRecord = CoinRecord( coin, block.height, uint32(0), False, False, block.foliage_transaction_block.timestamp, ) await self._add_coin_record(record) included_reward_coins = block.get_included_reward_coins() if block.height == 0: assert len(included_reward_coins) == 0 else: assert len(included_reward_coins) >= 2 for coin in included_reward_coins: reward_coin_r: CoinRecord = CoinRecord( coin, block.height, uint32(0), False, True, block.foliage_transaction_block.timestamp, ) await self._add_coin_record(reward_coin_r) for coin_name in removals: await self._set_spent(coin_name, block.height)
async def receive_block( self, block: FullBlock, pre_validation_result: Optional[PreValidationResult] = None, fork_point_with_peak: Optional[uint32] = None, summaries_to_check: List[SubEpochSummary] = None, # passed only on long sync ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]: """ 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 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. """ genesis: bool = block.height == 0 if self.contains_block(block.header_hash): 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, ) if not genesis and (self.block_record(block.prev_header_hash).height + 1) != block.height: return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None npc_result: Optional[NPCResult] = None if pre_validation_result is None: if block.height == 0: prev_b: Optional[BlockRecord] = None else: prev_b = self.block_record(block.prev_header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( self.constants, len(block.finished_sub_slots) > 0, prev_b, self ) if block.is_transaction_block(): if block.transactions_generator is not None: try: block_generator: Optional[BlockGenerator] = await self.get_block_generator(block) except ValueError: 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, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False ) removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) else: removals, tx_additions = [], [] header_block = get_block_header(block, tx_additions, removals) else: npc_result = None header_block = get_block_header(block, [], []) required_iters, error = validate_finished_header_block( self.constants, self, header_block, False, difficulty, sub_slot_iters, ) if error is not None: return ReceiveBlockResult.INVALID_BLOCK, error.code, None else: npc_result = pre_validation_result.npc_result required_iters = pre_validation_result.required_iters assert pre_validation_result.error is None assert required_iters is not None error_code, _ = await validate_block_body( self.constants, self, self.block_store, self.coin_store, self.get_peak(), block, block.height, npc_result, fork_point_with_peak, self.get_block_generator, ) if error_code is not None: return ReceiveBlockResult.INVALID_BLOCK, error_code, None block_record = block_to_block_record( self.constants, self, required_iters, block, None, ) # Always add the block to the database async with self.block_store.db_wrapper.lock: try: # 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(block, block_record) fork_height, peak_height, records = await self._reconsider_peak( block_record, genesis, fork_point_with_peak, npc_result ) await self.block_store.db_wrapper.commit_transaction() # Then update the memory cache. It is important that this task is not cancelled and does not throw self.add_block_record(block_record) for fetched_block_record in records: self.__height_to_hash[fetched_block_record.height] = fetched_block_record.header_hash if fetched_block_record.sub_epoch_summary_included is not None: self.__sub_epoch_summaries[ fetched_block_record.height ] = fetched_block_record.sub_epoch_summary_included if peak_height is not None: self._peak_height = peak_height self.block_store.cache_block(block) except BaseException: await self.block_store.db_wrapper.rollback_transaction() raise if fork_height is not None: return ReceiveBlockResult.NEW_PEAK, None, fork_height else: return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None