Exemplo n.º 1
0
    def propagate_tx(self,
                     tx: BaseTransaction,
                     fails_silently: bool = True) -> bool:
        """Push a new transaction to the network. It is used by both the wallet and the mining modules.

        :return: True if the transaction was accepted
        :rtype: bool
        """
        if tx.storage:
            assert tx.storage == self.tx_storage, 'Invalid tx storage'
        else:
            tx.storage = self.tx_storage
        return self.on_new_tx(tx, fails_silently=fails_silently)
Exemplo n.º 2
0
    def on_new_tx(self,
                  tx: BaseTransaction,
                  *,
                  conn: Optional[HathorProtocol] = None,
                  quiet: bool = False,
                  fails_silently: bool = True,
                  propagate_to_peers: bool = True,
                  skip_block_weight_verification: bool = False,
                  sync_checkpoints: bool = False,
                  partial: bool = False) -> bool:
        """ New method for adding transactions or blocks that steps the validation state machine.

        :param tx: transaction to be added
        :param conn: optionally specify the protocol instance where this tx was received from
        :param quiet: if True will not log when a new tx is accepted
        :param fails_silently: if False will raise an exception when tx cannot be added
        :param propagate_to_peers: if True will relay the tx to other peers if it is accepted
        :param skip_block_weight_verification: if True will not check the tx PoW
        :param sync_checkpoints: if True and also partial=True, will try to validate as a checkpoint and set the proper
                                 validation state, this is used for adding txs from the sync-checkpoints phase
        :param partial: if True will accept txs that can't be fully validated yet (because of missing parent/input) but
                        will run a basic validation of what can be validated (PoW and other basic fields)
        """
        assert tx.hash is not None
        if self.tx_storage.transaction_exists(tx.hash):
            if not fails_silently:
                raise InvalidNewTransaction(
                    'Transaction already exists {}'.format(tx.hash_hex))
            self.log.warn('on_new_tx(): Transaction already exists',
                          tx=tx.hash_hex)
            return False

        if tx.timestamp - self.reactor.seconds(
        ) > settings.MAX_FUTURE_TIMESTAMP_ALLOWED:
            if not fails_silently:
                raise InvalidNewTransaction(
                    'Ignoring transaction in the future {} (timestamp={})'.
                    format(tx.hash_hex, tx.timestamp))
            self.log.warn('on_new_tx(): Ignoring transaction in the future',
                          tx=tx.hash_hex,
                          future_timestamp=tx.timestamp)
            return False

        tx.storage = self.tx_storage

        try:
            metadata = tx.get_metadata()
        except TransactionDoesNotExist:
            if not fails_silently:
                raise InvalidNewTransaction('missing parent')
            self.log.warn('on_new_tx(): missing parent', tx=tx.hash_hex)
            return False

        if metadata.validation.is_invalid():
            if not fails_silently:
                raise InvalidNewTransaction('previously marked as invalid')
            self.log.warn('on_new_tx(): previously marked as invalid',
                          tx=tx.hash_hex)
            return False

        # if partial=False (the default) we don't even try to partially validate transactions
        if not partial or (metadata.validation.is_fully_connected()
                           or tx.can_validate_full()):
            if isinstance(tx, Transaction) and self.tx_storage.is_tx_needed(
                    tx.hash):
                tx._height_cache = self.tx_storage.needed_index_height(tx.hash)

            if not metadata.validation.is_fully_connected():
                try:
                    tx.validate_full(sync_checkpoints=sync_checkpoints)
                except HathorError as e:
                    if not fails_silently:
                        raise InvalidNewTransaction(
                            'full validation failed') from e
                    self.log.warn('on_new_tx(): full validation failed',
                                  tx=tx.hash_hex,
                                  exc_info=True)
                    return False

            # The method below adds the tx as a child of the parents
            # This needs to be called right before the save because we were adding the children
            # in the tx parents even if the tx was invalid (failing the verifications above)
            # then I would have a children that was not in the storage
            tx.update_initial_metadata()
            self.tx_storage.save_transaction(tx, add_to_indexes=True)
            try:
                self.consensus_algorithm.update(tx)
            except HathorError as e:
                if not fails_silently:
                    raise InvalidNewTransaction(
                        'consensus update failed') from e
                self.log.warn('on_new_tx(): consensus update failed',
                              tx=tx.hash_hex)
                return False
            else:
                assert tx.validate_full(skip_block_weight_verification=True)
                self.tx_fully_validated(tx)
        elif sync_checkpoints:
            metadata.children = self.tx_storage.children_from_deps(tx.hash)
            try:
                tx.validate_checkpoint(self.checkpoints)
            except HathorError:
                if not fails_silently:
                    raise InvalidNewTransaction('checkpoint validation failed')
                self.log.warn('on_new_tx(): checkpoint validation failed',
                              tx=tx.hash_hex,
                              exc_info=True)
                return False
            self.tx_storage.save_transaction(tx)
            self.tx_storage.add_to_deps_index(tx.hash,
                                              tx.get_all_dependencies())
            self.tx_storage.add_needed_deps(tx)
        else:
            if isinstance(tx, Block) and not tx.has_basic_block_parent():
                if not fails_silently:
                    raise InvalidNewTransaction(
                        'block parent needs to be at least basic-valid')
                self.log.warn(
                    'on_new_tx(): block parent needs to be at least basic-valid',
                    tx=tx.hash_hex)
                return False
            if not tx.validate_basic():
                if not fails_silently:
                    raise InvalidNewTransaction('basic validation failed')
                self.log.warn('on_new_tx(): basic validation failed',
                              tx=tx.hash_hex)
                return False

            # The method below adds the tx as a child of the parents
            # This needs to be called right before the save because we were adding the children
            # in the tx parents even if the tx was invalid (failing the verifications above)
            # then I would have a children that was not in the storage
            tx.update_initial_metadata()
            self.tx_storage.save_transaction(tx)
            self.tx_storage.add_to_deps_index(tx.hash,
                                              tx.get_all_dependencies())
            self.tx_storage.add_needed_deps(tx)

        if tx.is_transaction:
            self.tx_storage.remove_from_needed_index(tx.hash)

        try:
            self.step_validations([tx])
        except (AssertionError, HathorError) as e:
            if not fails_silently:
                raise InvalidNewTransaction('step validations failed') from e
            self.log.warn('on_new_tx(): step validations failed',
                          tx=tx.hash_hex,
                          exc_info=True)
            return False

        if not quiet:
            ts_date = datetime.datetime.fromtimestamp(tx.timestamp)
            now = datetime.datetime.fromtimestamp(self.reactor.seconds())
            if tx.is_block:
                self.log.info('new block',
                              tx=tx,
                              ts_date=ts_date,
                              time_from_now=tx.get_time_from_now(now))
            else:
                self.log.info('new tx',
                              tx=tx,
                              ts_date=ts_date,
                              time_from_now=tx.get_time_from_now(now))

        if propagate_to_peers:
            # Propagate to our peers.
            self.connections.send_tx_to_peers(tx)

        return True