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
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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)
Beispiel #7
0
    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)