def iter_all(self, tx_storage: 'TransactionStorage') -> Iterator[Transaction]: bfs = BFSWalk(tx_storage, is_dag_verifications=True, is_left_to_right=False) for tx in bfs.run(self.iter(tx_storage), skip_root=False): assert isinstance(tx, Transaction) if tx.get_metadata().first_block is not None: bfs.skip_neighbors(tx) else: yield tx
def add_voided_by(self, tx: Transaction, voided_hash: bytes) -> bool: """ Add a hash from `meta.voided_by` and its descendants (both from verification DAG and funds tree). """ assert tx.hash is not None assert tx.storage is not None meta = tx.get_metadata() if meta.voided_by and voided_hash in meta.voided_by: return False self.log.debug('add_voided_by', tx=tx.hash_hex, voided_hash=voided_hash.hex()) is_dag_verifications = True if meta.voided_by and bool(self.soft_voided_tx_ids & meta.voided_by): # If tx is soft voided, we can only walk through the DAG of funds. is_dag_verifications = False from hathor.transaction.storage.traversal import BFSWalk bfs = BFSWalk(tx.storage, is_dag_funds=True, is_dag_verifications=is_dag_verifications, is_left_to_right=True) check_list: List[Transaction] = [] for tx2 in bfs.run(tx, skip_root=False): assert tx2.storage is not None assert tx2.hash is not None meta2 = tx2.get_metadata() if tx2.is_block: assert isinstance(tx2, Block) self.consensus.block_algorithm.mark_as_voided(tx2) assert not meta2.voided_by or voided_hash not in meta2.voided_by if tx2.hash != tx.hash and meta2.conflict_with and not meta2.voided_by: check_list.extend( cast(Transaction, tx2.storage.get_transaction(h)) for h in meta2.conflict_with) if meta2.voided_by: meta2.voided_by.add(voided_hash) else: meta2.voided_by = {voided_hash} if meta2.conflict_with: assert isinstance(tx2, Transaction) self.mark_as_voided(tx2) # All voided transactions with conflicts must have their accumulated weight calculated. tx2.update_accumulated_weight(save_file=False) tx2.storage.save_transaction(tx2, only_metadata=True) tx2.storage.del_from_indexes(tx2, relax_assert=True) self.assert_valid_consensus(tx2) for tx2 in check_list: self.check_conflicts(tx2) return True
def update_accumulated_weight( self, *, stop_value: float = inf, save_file: bool = True) -> TransactionMetadata: """Calculates the tx's accumulated weight and update its metadata. It starts at the current transaction and does a BFS to the tips. In the end, updates the accumulated weight on metadata It stops calculating the accumulated weight when the value passes the `stop_value`. This may be used when the accumulated weight is being calculated to be compared to another value. In this case, we may stop calculating when we are already higher than `stop_value`. :param: stop_value: Threshold to stop calculating the accumulated weight. :return: transaction metadata :rtype: :py:class:`hathor.transaction.TransactionMetadata` """ assert self.storage is not None metadata = self.get_metadata() if metadata.accumulated_weight > stop_value: return metadata accumulated_weight = self.weight # TODO Another optimization is that, when we calculate the acc weight of a transaction, we # also partially calculate the acc weight of its descendants. If it were a DFS, when returning # to a vertex, the acc weight calculated would be <= the real acc weight. So, we might store it # as a pre-calculated value. Then, during the next DFS, if `cur + tx.acc_weight > stop_value`, # we might stop and avoid some visits. Question: how would we do it in the BFS? # TODO We can walk by the blocks first, because they have higher weight and this may # reduce the number of visits in the BFS. We need to specially handle when a transaction is not # directly verified by a block. from hathor.transaction.storage.traversal import BFSWalk bfs_walk = BFSWalk(self.storage, is_dag_funds=True, is_dag_verifications=True, is_left_to_right=True) for tx in bfs_walk.run(self, skip_root=True): accumulated_weight = sum_weights(accumulated_weight, tx.weight) if accumulated_weight > stop_value: break metadata.accumulated_weight = accumulated_weight if save_file: self.storage.save_transaction(self, only_metadata=True) return metadata
def remove_voided_by(self, tx: Transaction, voided_hash: bytes) -> bool: """ Remove a hash from `meta.voided_by` and its descendants (both from verification DAG and funds tree). """ from hathor.transaction.storage.traversal import BFSWalk assert tx.hash is not None assert tx.storage is not None meta = tx.get_metadata() if not meta.voided_by: return False if voided_hash not in meta.voided_by: return False self.log.debug('remove_voided_by', tx=tx.hash_hex, voided_hash=voided_hash.hex()) bfs = BFSWalk(tx.storage, is_dag_funds=True, is_dag_verifications=True, is_left_to_right=True) check_list: List[BaseTransaction] = [] for tx2 in bfs.run(tx, skip_root=False): assert tx2.storage is not None meta2 = tx2.get_metadata() if not (meta2.voided_by and voided_hash in meta2.voided_by): bfs.skip_neighbors(tx2) continue if meta2.voided_by: meta2.voided_by.discard(voided_hash) if meta2.voided_by == {tx2.hash}: check_list.append(tx2) if not meta2.voided_by: meta2.voided_by = None tx.storage.add_to_indexes(tx2) tx2.storage.save_transaction(tx2, only_metadata=True) self.assert_valid_consensus(tx2) from hathor.transaction import Transaction for tx2 in check_list: if not tx2.is_block: assert isinstance(tx2, Transaction) self.check_conflicts(tx2) return True
def add_voided_by(self, tx: 'Transaction', voided_hash: bytes) -> bool: """ Add a hash from `meta.voided_by` and its descendants (both from verification DAG and funds tree). """ assert tx.hash is not None assert tx.storage is not None meta = tx.get_metadata() if meta.voided_by and voided_hash in meta.voided_by: return False self.log.debug('add_voided_by tx={} voided_hash={}'.format(tx.hash.hex(), voided_hash.hex())) from hathor.transaction.storage.traversal import BFSWalk bfs = BFSWalk(tx.storage, is_dag_funds=True, is_dag_verifications=True, is_left_to_right=True) check_list: List['Transaction'] = [] for tx2 in bfs.run(tx, skip_root=False): assert tx2.storage is not None assert tx2.hash is not None meta2 = tx2.get_metadata() if tx2.is_block: assert isinstance(tx2, Block) self.consensus.block_algorithm.mark_as_voided(tx2) assert not meta2.voided_by or voided_hash not in meta2.voided_by if tx2.hash != tx.hash and meta2.conflict_with and not meta2.voided_by: check_list.extend(tx2.storage.get_transaction(h) for h in meta2.conflict_with) if meta2.voided_by: meta2.voided_by.add(voided_hash) else: meta2.voided_by = {voided_hash} if meta2.conflict_with: meta2.voided_by.add(tx2.hash) # All voided transactions with conflicts must have their accumulated weight calculated. tx2.update_accumulated_weight(save_file=False) tx2.storage.save_transaction(tx2, only_metadata=True) tx2.storage._del_from_cache(tx2, relax_assert=True) # XXX: accessing private method for tx2 in check_list: self.check_conflicts(tx2) return True
def remove_first_block_markers(self, block: 'Block') -> None: """ Remove all `meta.first_block` pointing to this block. """ assert block.storage is not None storage = block.storage from hathor.transaction.storage.traversal import BFSWalk bfs = BFSWalk(storage, is_dag_verifications=True, is_left_to_right=False) for tx in bfs.run(block, skip_root=True): if tx.is_block: bfs.skip_neighbors(tx) continue meta = tx.get_metadata() if meta.first_block != block.hash: bfs.skip_neighbors(tx) continue meta.first_block = None storage.save_transaction(tx, only_metadata=True)
def _score_block_dfs(self, block: BaseTransaction, used: Set[bytes], mark_as_best_chain: bool, newest_timestamp: int) -> float: """ Internal method to run a DFS. It is used by `calculate_score()`. """ assert block.storage is not None assert block.hash is not None assert block.is_block storage = block.storage from hathor.transaction import Block score = block.weight for parent in block.get_parents(): if parent.is_block: assert isinstance(parent, Block) if parent.timestamp <= newest_timestamp: meta = parent.get_metadata() x = meta.score else: x = self._score_block_dfs(parent, used, mark_as_best_chain, newest_timestamp) score = sum_weights(score, x) else: from hathor.transaction.storage.traversal import BFSWalk bfs = BFSWalk(storage, is_dag_verifications=True, is_left_to_right=False) for tx in bfs.run(parent, skip_root=False): assert tx.hash is not None assert not tx.is_block if tx.hash in used: bfs.skip_neighbors(tx) continue used.add(tx.hash) meta = tx.get_metadata() if meta.first_block: first_block = storage.get_transaction(meta.first_block) if first_block.timestamp <= newest_timestamp: bfs.skip_neighbors(tx) continue if mark_as_best_chain: assert meta.first_block is None meta.first_block = block.hash storage.save_transaction(tx, only_metadata=True) score = sum_weights(score, tx.weight) # Always save the score when it is calculated. meta = block.get_metadata() if not meta.score: meta.score = score storage.save_transaction(block, only_metadata=True) else: # The score of a block is immutable since the sub-DAG behind it is immutable as well. # Thus, if we have already calculated it, we just check the consistency of the calculation. # Unfortunately we may have to calculate it more than once when a new block arrives in a side # side because the `first_block` points only to the best chain. assert abs(meta.score - score) < 1e-10, \ 'hash={} meta.score={} score={}'.format(block.hash.hex(), meta.score, score) return score
def gen_walk(self, **kwargs): return BFSWalk(self.manager.tx_storage, **kwargs)