{"action":"created","comment":{"url":"https://api.github.com/repos/raiden-network/raiden/pulls/comments/84030106","pull_request_review_id":4830121,"id":84030106,"diff_hunk":"@@ -281,442 +439,793 @@ def _run(self):  # noqa pylint: disable=method-hidden,too-many-locals\n             raiden.sign(mediated_transfer)\n             forward_channel.register_transfer(mediated_transfer)\n \n-            response = self.send_and_wait_valid(raiden, path, mediated_transfer)\n-\n-            # `next_hop` timedout\n-            if response is None:\n-                self.transfermanager.on_hashlock_result(hashlock, False)\n+            for response in self.send_and_iter_valid(raiden, path, mediated_transfer):\n+                valid_secretrequest = (\n+                    isinstance(response, SecretRequest) and\n+                    response.amount == amount and\n+                    response.hashlock == hashlock and\n+                    response.identifier == identifier\n+                )\n \n-            # someone down the line timedout / couldn't proceed\n-            elif isinstance(response, (RefundTransfer, TransferTimeout)):\n-                self.transfermanager.on_hashlock_result(hashlock, False)\n+                if valid_secretrequest:\n+                    # This node must reveal the Secret starting with the\n+                    # end-of-chain, the `next_hop` can not be trusted to reveal the\n+                    # secret to the other nodes.\n+                    revealsecret_message = RevealSecret(secret)\n+                    raiden.sign(revealsecret_message)\n+\n+                    # we cannot wait for ever since the `target` might\n+                    # intentionally _not_ send the Ack, blocking us from\n+                    # unlocking the asset.\n+                    wait = (\n+                        ESTIMATED_BLOCK_TIME * lock_timeout / .6\n+                    )\n \n-            # `target` received the MediatedTransfer\n-            elif response.sender == target and isinstance(response, SecretRequest):\n-                secret_message = Secret(\n-                    mediated_transfer.identifier,\n-                    secret,\n-                    mediated_transfer.asset,\n-                )\n-                raiden.sign(secret_message)\n-                raiden.send_async(target, secret_message)\n-\n-                # register the secret now and just incur with the additional\n-                # overhead of retrying until the `next_hop` receives the secret\n-                # forward_channel.register_secret(secret)\n-\n-                # wait until `next_hop` received the secret to syncronize our\n-                # state (otherwise we can send a new transfer with an invalid\n-                # locksroot while the secret is in transit that will incur into\n-                # additional retry/timeout latency)\n-                next_hop = path[1]\n-                while True:\n-                    response = self.response_message.wait()\n-                    # critical write section\n-                    self.response_message = AsyncResult()\n-                    # /critical write section\n-                    if isinstance(response, Secret) and response.sender == next_hop:\n-                        # critical read/write section\n-                        # The channel and it's queue must be locked, a transfer\n-                        # must not be created while we update the balance_proof.\n-                        if forward_channel.partner_state.balance_proof.is_unclaimed(hashlock):\n-                            forward_channel.release_lock(secret)\n-\n-                        raiden.send_async(next_hop, secret_message)\n-                        # /critical write section\n-\n-                        self.transfermanager.on_hashlock_result(hashlock, True)\n-                        self.done_result.set(True)\n-\n-                        return\n+                    raiden.send_and_wait(target, revealsecret_message, wait)\n \n-                    log.error(\n-                        'Invalid message ignoring. %s',\n-                        repr(response),\n+                    # target has acknowledged the RevealSecret, we can update\n+                    # the chain in the forward direction\n+                    assetmanager.handle_secret(\n+                        identifier,\n+                        secret,\n                     )\n-            else:\n-                log.error(\n-                    'Unexpected response %s',\n-                    repr(response),\n-                )\n-                self.transfermanager.on_hashlock_result(hashlock, False)\n+\n+                    # call the callbacks and de-register the task\n+                    transfermanager.on_hashlock_result(hashlock, True)\n+\n+                    # the transfer is done when the lock is unlocked and the Secret\n+                    # message is sent (doesn't imply the other nodes in the chain\n+                    # have unlocked/withdrawed)\n+                    self.done_result.set(True)\n+\n+                    return\n+\n+                # someone down the line timedout / couldn't proceed, try next\n+                # path, remove the hashlock from\n+                else:\n+                    # the initiator can unregister right away because it knowns\n+                    # no one else can reveal the secret\n+                    # stop listening for messages with the hashlock\n+                    transfermanager.on_hashlock_result(hashlock, False)\n+                    del assetmanager.hashlock_channel[hashlock]\n+                    break\n \n         if log.isEnabledFor(logging.DEBUG):\n             log.debug(\n                 'START MEDIATED TRANSFER FAILED initiator:%s target:%s',\n-                pex(self.address),\n+                pex(node_address),\n                 pex(self.target),\n             )\n \n-        self.done_result.set(False)  # all paths failed\n+        # all routes failed, consider:\n+        # - if the target is a good node to have a channel:\n+        #   - deposit/reopen/open a channel with target\n+        # - if the target has a direct channel with good nodes and there is\n+        #   sufficient funds to complete the transfer\n+        #   - open the required channels with these nodes\n+        self.done_result.set(False)\n \n-    def send_and_wait_valid(self, raiden, path, mediated_transfer):  # noqa pylint: disable=no-self-use\n+    def send_and_iter_valid(self, raiden, path, mediated_transfer):  # noqa pylint: disable=no-self-use\n         \"\"\" Send the `mediated_transfer` and wait for either a message from\n         `target` or the `next_hop`.\n-\n-        Validate the message received and discards the invalid ones. The most\n-        important case being next_hop sending a SecretRequest.\n         \"\"\"\n-        message_timeout = raiden.config['msg_timeout']\n         next_hop = path[1]\n         target = path[-1]\n \n-        current_time = time.time()\n-        limit_time = current_time + message_timeout\n-\n-        # this event is used by the transfermanager to notify the task that a\n-        # response was received\n-        self.response_message = AsyncResult()\n-\n-        raiden.send_async(next_hop, mediated_transfer)\n-\n-        while current_time <= limit_time:\n-            # wait for a response message (not the Ack for the transfer)\n-            response = self.response_message.wait(limit_time - current_time)\n-\n-            # reset so that a value can be received either because the current\n-            # result was invalid or because we will wait for the next message.\n-            #\n-            # critical write section\n-            self.response_message = AsyncResult()\n-            # /critical write section\n-\n-            if response is None:\n-                if log.isEnabledFor(logging.DEBUG):\n-                    log.debug(\n-                        'MEDIATED TRANSFER TIMED OUT hashlock:%s',\n-                        pex(mediated_transfer.lock.hashlock),\n-                    )\n-\n-                return None\n-\n-            if response.sender == next_hop:\n-                if isinstance(response, (RefundTransfer, TransferTimeout)):\n-                    return response\n-                else:\n-                    if log.isEnabledFor(logging.INFO):\n-                        log.info(\n-                            'Partner %s sent an invalid message',\n-                            pex(next_hop),\n-                        )\n+        response_iterator = self._send_and_wait_time(\n+            raiden,\n+            mediated_transfer.recipient,\n+            mediated_transfer,\n+            raiden.config['msg_timeout'],\n+        )\n \n-                    return None\n+        for response in response_iterator:\n+            refund_or_timeout = (\n+                isinstance(response, (RefundTransfer, TransferTimeout)) and\n+                response.sender == next_hop\n+            )\n \n-            if response.sender == target:\n-                if isinstance(response, SecretRequest):\n-                    return response\n-                else:\n-                    if log.isEnabledFor(logging.INFO):\n-                        log.info(\n-                            'target %s sent an invalid message',\n-                            pex(target),\n-                        )\n+            secret_request = (\n+                isinstance(response, SecretRequest) and\n+                response.sender == target\n+            )\n \n-                    return None\n+            timeout = response is TIMEOUT\n \n-            current_time = time.time()\n-\n-            if log.isEnabledFor(logging.ERROR):\n+            if refund_or_timeout or secret_request or timeout:\n+                yield response\n+            elif log.isEnabledFor(logging.ERROR):\n                 log.error(\n                     'Invalid message ignoring. %s',\n                     repr(response),\n                 )\n \n-        return None\n+        return\n \n \n-class MediateTransferTask(Task):  # pylint: disable=too-many-instance-attributes\n-    def __init__(self, transfermanager, originating_transfer, fee):\n+class MediateTransferTask(BaseMediatedTransferTask):\n+    def __init__(self, raiden, asset_address, originating_transfer, fee):\n         super(MediateTransferTask, self).__init__()\n \n-        self.address = transfermanager.assetmanager.raiden.address\n-        self.transfermanager = transfermanager\n-        self.fee = fee\n+        self.raiden = raiden\n+        self.asset_address = asset_address\n         self.originating_transfer = originating_transfer\n-\n-        hashlock = originating_transfer.lock.hashlock\n-        self.transfermanager.register_task_for_hashlock(self, hashlock)\n+        self.fee = fee\n \n     def __repr__(self):\n-        return '<{} {}>'.format(\n+        return '<{} {} asset:{}>'.format(\n             self.__class__.__name__,\n-            pex(self.address)\n+            pex(self.raiden.address),\n+            pex(self.asset_address),\n         )\n \n-    def _run(self):\n-        # pylint: disable=method-hidden,too-many-locals\n+    def _run(self):  # pylint: disable=method-hidden,too-many-locals\n+        raiden = self.raiden\n         fee = self.fee\n-        transfer = self.originating_transfer\n+        originating_transfer = self.originating_transfer\n+        hashlock = originating_transfer.lock.hashlock\n \n-        assetmanager = self.transfermanager.assetmanager\n-        raiden = assetmanager.raiden\n-        originating_channel = assetmanager.partneraddress_channel[transfer.sender]\n+        assetmanager = raiden.get_manager_by_asset_address(self.asset_address)\n+        transfermanager = assetmanager.transfermanager\n+        from_address = originating_transfer.sender\n+        originating_channel = assetmanager.partneraddress_channel[from_address]\n+        hashlock = originating_transfer.lock.hashlock\n \n-        assetmanager.register_channel_for_hashlock(\n-            originating_channel,\n-            transfer.lock.hashlock,\n-        )\n+        transfermanager.register_task_for_hashlock(self, hashlock)\n+        assetmanager.register_channel_for_hashlock(originating_channel, hashlock)\n \n-        lock_expiration = transfer.lock.expiration - raiden.config['reveal_timeout']\n+        lock_expiration = originating_transfer.lock.expiration - raiden.config['reveal_timeout']\n         lock_timeout = lock_expiration - raiden.chain.block_number()\n-        lock_hashlock = transfer.lock.hashlock\n \n         # there are no guarantees that the next_hop will follow the same route\n         routes = assetmanager.get_best_routes(\n-            transfer.lock.amount,\n-            transfer.target,\n+            originating_transfer.lock.amount,\n+            originating_transfer.target,\n             lock_timeout,\n         )\n \n         if log.isEnabledFor(logging.DEBUG):\n             log.debug(\n                 'MEDIATED TRANSFER initiator:%s node:%s target:%s',\n-                pex(transfer.initiator),\n-                pex(self.address),\n-                pex(transfer.target),\n+                pex(originating_transfer.initiator),\n+                pex(raiden.address),\n+                pex(originating_transfer.target),\n             )\n \n         for path, forward_channel in routes:\n-            next_hop = path[1]\n-\n             mediated_transfer = forward_channel.create_mediatedtransfer(\n-                transfer.initiator,\n-                transfer.target,\n+                originating_transfer.initiator,\n+                originating_transfer.target,\n                 fee,\n-                transfer.lock.amount,\n-                transfer.identifier,\n+                originating_transfer.lock.amount,\n+                originating_transfer.identifier,\n                 lock_expiration,\n-                lock_hashlock,\n+                hashlock,\n             )\n             raiden.sign(mediated_transfer)\n \n             if log.isEnabledFor(logging.DEBUG):\n                 log.debug(\n                     'MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',\n                     lpex(path),\n-                    pex(transfer.lock.hashlock),\n+                    pex(originating_transfer.lock.hashlock),\n                 )\n \n-            # Using assetmanager to register the interest because it outlives\n-            # this task, the secret handling will happend only _once_\n             assetmanager.register_channel_for_hashlock(\n                 forward_channel,\n-                transfer.lock.hashlock,\n+                originating_transfer.lock.hashlock,\n             )\n             forward_channel.register_transfer(mediated_transfer)\n \n-            response = self.send_and_wait_valid(raiden, path, mediated_transfer)\n+            for response in self.send_and_iter_valid(raiden, path, mediated_transfer):\n+                valid_refund = (\n+                    isinstance(response, RefundTransfer) and\n+                    response.lock.amount == originating_transfer.lock.amount\n+                )\n \n-            if response is None:\n-                timeout_message = forward_channel.create_timeouttransfer_for(transfer)\n-                raiden.send_async(transfer.sender, timeout_message)\n-                self.transfermanager.on_hashlock_result(transfer.lock.hashlock, False)\n-                return\n+                if isinstance(response, RevealSecret):\n+                    assetmanager.handle_secret(\n+                        originating_transfer.identifier,\n+                        response.secret,\n+                    )\n \n-            if isinstance(response, RefundTransfer):\n-                if response.lock.amount != transfer.lock.amount:\n-                    log.info(\n-                        'Partner %s sent an refund message with an invalid amount',\n-                        pex(next_hop),\n+                    self._wait_for_unlock_or_close(\n+                        raiden,\n+                        assetmanager,\n+                        originating_channel,\n+                        originating_transfer,\n+                    )\n+\n+                elif isinstance(response, Secret):\n+                    assetmanager.handle_secretmessage(response)\n+\n+                    # Secret might be from a different node, wait for the\n+                    # update from `from_address`\n+                    self._wait_for_unlock_or_close(\n+                        raiden,\n+                        assetmanager,\n+                        originating_channel,\n+                        originating_transfer,\n                     )\n-                    timeout_message = forward_channel.create_timeouttransfer_for(transfer)\n-                    raiden.send_async(transfer.sender, timeout_message)\n-                    self.transfermanager.on_hashlock_result(transfer.lock.hashlock, False)\n+\n+                    transfermanager.on_hashlock_result(hashlock, True)\n                     return\n-                else:\n+\n+                elif valid_refund:\n                     forward_channel.register_transfer(response)\n+                    break\n \n-            elif isinstance(response, Secret):\n-                # update all channels and propagate the secret (this doesnt claim the lock yet)\n-                assetmanager.handle_secret(transfer.identifier, response.secret)\n-\n-                # wait for the secret from `sender`\n-                while True:\n-                    response = self.response_message.wait()\n-                    # critical write section\n-                    self.response_message = AsyncResult()\n-                    # /critical write section\n-\n-                    # NOTE: this relies on the fact RaindenService dispatches\n-                    # messages based on the `hashlock` calculated from the\n-                    # secret, so we know this `response` message secret matches\n-                    # the secret from the `next_hop`\n-                    if isinstance(response, Secret) and response.sender == transfer.sender:\n-                        if originating_channel.our_state.balance_proof.is_unclaimed(lock_hashlock):\n-                            originating_channel.withdraw_lock(response.secret)\n-\n-                        self.transfermanager.on_hashlock_result(transfer.lock.hashlock, True)\n-                        return\n+                else:\n+                    timeout_message = originating_channel.create_timeouttransfer_for(\n+                        originating_transfer,\n+                    )\n+                    raiden.send_async(\n+                        originating_transfer.sender,\n+                        timeout_message,\n+                    )\n+\n+                    self._wait_expiration(\n+                        raiden,\n+                        originating_transfer,\n+                    )\n+                    transfermanager.on_hashlock_result(hashlock, False)\n+\n+                    return\n \n         # No suitable path avaiable (e.g. insufficient distributable, no active node)\n         # Send RefundTransfer to the originating node, this has the effect of\n         # backtracking in the graph search of the raiden network.\n-        from_address = transfer.sender\n-        from_channel = assetmanager.partneraddress_channel[from_address]\n \n-        refund_transfer = from_channel.create_refundtransfer_for(transfer)\n+        if log.isEnabledFor(logging.DEBUG):\n+            log.debug(\n+                'REFUND MEDIATED TRANSFER from=%s node:%s hashlock:%s',\n+                pex(from_address),\n+                pex(raiden.address),\n+                pex(originating_transfer.lock.hashlock),\n+            )\n+\n+        refund_transfer = originating_channel.create_refundtransfer_for(\n+            originating_transfer,\n+        )\n         raiden.sign(refund_transfer)\n-        from_channel.register_transfer(refund_transfer)\n \n+        originating_channel.register_transfer(refund_transfer)\n         raiden.send_async(from_address, refund_transfer)\n \n-        log.debug(\n-            'REFUND MEDIATED TRANSFER from=%s node:%s hashlock:%s',\n-            pex(from_address),\n-            pex(raiden.address),\n-            pex(transfer.lock.hashlock),\n+        self._wait_expiration(\n+            raiden,\n+            originating_transfer,\n+        )\n+        transfermanager.on_hashlock_result(hashlock, False)\n+\n+    def send_and_iter_valid(self, raiden, path, mediated_transfer):\n+        response_iterator = self._send_and_wait_time(\n+            raiden,\n+            mediated_transfer.recipient,\n+            mediated_transfer,\n+            raiden.config['msg_timeout'],\n         )\n \n-        self.transfermanager.on_hashlock_result(transfer.lock.hashlock, False)\n-        return\n+        for response in response_iterator:\n \n-    def send_and_wait_valid(self, raiden, path, mediated_transfer):\n-        message_timeout = raiden.config['msg_timeout']\n-        next_hop = path[1]\n+            timeout = response is TIMEOUT\n+            secret = isinstance(response, (Secret, RevealSecret))\n \n-        current_time = time.time()\n-        limit_time = current_time + message_timeout\n+            refund_or_timeout = (\n+                isinstance(response, (RefundTransfer, TransferTimeout)) and\n+                response.sender == path[0]\n+            )\n \n-        self.response_message = AsyncResult()\n-        raiden.send_async(next_hop, mediated_transfer)\n+            if timeout or secret or refund_or_timeout:\n+                yield response\n \n-        while current_time <= limit_time:\n-            response = self.response_message.wait(limit_time - current_time)\n+            elif log.isEnabledFor(logging.ERROR):\n+                log.error(\n+                    'Partner sent an invalid message. %s',\n+                    repr(response),\n+                )\n \n-            # critical write section\n-            self.response_message = AsyncResult()  # reset so that a new value can be received\n-            # /critical write section\n \n-            current_time = time.time()\n+class EndMediatedTransferTask(BaseMediatedTransferTask):\n+    \"\"\" Task that request a secret for a registered transfer. \"\"\"\n \n-            if response is None:\n+    def __init__(self, raiden, asset_address, originating_transfer):\n+        super(EndMediatedTransferTask, self).__init__()\n+\n+        self.raiden = raiden\n+        self.asset_address = asset_address\n+        self.originating_transfer = originating_transfer\n+\n+    def __repr__(self):\n+        return '<{} {} asset:{}>'.format(\n+            self.__class__.__name__,\n+            pex(self.raiden.address),\n+            pex(self.asset_address),\n+        )\n+\n+    def _run(self):  # pylint: disable=method-hidden\n+        raiden = self.raiden\n+        originating_transfer = self.originating_transfer\n+        hashlock = originating_transfer.lock.hashlock\n+\n+        assetmanager = raiden.get_manager_by_asset_address(self.asset_address)\n+        transfermanager = assetmanager.transfermanager\n+        originating_channel = assetmanager.get_channel_by_partner_address(\n+            originating_transfer.sender,\n+        )\n+\n+        transfermanager.register_task_for_hashlock(self, hashlock)\n+        assetmanager.register_channel_for_hashlock(originating_channel, hashlock)\n+\n+        if log.isEnabledFor(logging.DEBUG):\n+            log.debug(\n+                'END MEDIATED TRANSFER %s -> %s msghash:%s hashlock:%s',\n+                pex(originating_transfer.target),\n+                pex(originating_transfer.initiator),\n+                pex(originating_transfer.hash),\n+                pex(originating_transfer.lock.hashlock),\n+            )\n+\n+        secret_request = SecretRequest(\n+            originating_transfer.identifier,\n+            originating_transfer.lock.hashlock,\n+            originating_transfer.lock.amount,\n+        )\n+        raiden.sign(secret_request)\n+\n+        # If the transfer timed out in the initiator a new hashlock will be\n+        # created and this task will not receive a secret, this is fine because\n+        # the task will eventually exit once a blocktimeout happens and a new\n+        # task will be created for the new hashlock\n+        valid_messages_iterator = self.send_secretrequest_and_iter_valid(\n+            raiden,\n+            originating_transfer,\n+            secret_request,\n+        )\n+\n+        for response in valid_messages_iterator:\n+\n+            # at this point a Secret message is not valid\n+            if isinstance(response, RevealSecret):\n+                assetmanager.handle_secret(\n+                    originating_transfer.identifier,\n+                    response.secret,\n+                )\n+\n+                self._wait_for_unlock_or_close(\n+                    raiden,\n+                    assetmanager,\n+                    originating_channel,\n+                    originating_transfer,\n+                )\n+                transfermanager.on_hashlock_result(hashlock, True)\n+                return\n+\n+            elif response is TIMEOUT:\n+                # this task timeouts on a blocknumber, at this point all the other\n+                # nodes have timedout\n+                transfermanager.on_hashlock_result(originating_transfer.lock.hashlock, False)\n+                break\n+\n+    def send_secretrequest_and_iter_valid(self, raiden, originating_transfer, secret_request):\n+        # pylint: disable=invalid-name\n+\n+        # keep this task alive up to the expiration block\n+        response_iterator = self._send_and_wait_block(\n+            raiden,\n+            originating_transfer.initiator,\n+            secret_request,\n+            originating_transfer.lock.expiration,\n+        )\n+\n+        # a Secret message is not valid here since the secret needs to first be\n+        # revealed to the target\n+        for response in response_iterator:\n+\n+            if isinstance(response, RevealSecret):\n+                yield response\n+                break\n+\n+            elif response is TIMEOUT:\n+                if log.isEnabledFor(logging.ERROR):\n+                    log.error(\n+                        'SECRETREQUEST TIMED OUT node:%s msghash:%s hashlock:%s',\n+                        pex(raiden.address),\n+                        pex(secret_request.hash),\n+                        pex(originating_transfer.lock.hashlock),\n+                    )\n+\n+                yield response\n+\n+            elif log.isEnabledFor(logging.ERROR):\n                 log.error(\n-                    'MEDIATED TRANSFER TIMED OUT node:%s timeout:%s msghash:%s hashlock:%s',\n-                    pex(raiden.address),\n-                    message_timeout,\n-                    pex(mediated_transfer.hash),\n-                    pex(mediated_transfer.lock.hashlock),\n+                    'INVALID MESSAGE RECEIVED %s',\n+                    repr(response),\n                 )\n-                return None\n \n-            if isinstance(response, Secret):\n-                if sha3(response.secret) != mediated_transfer.lock.hashlock:\n-                    log.error('Secret doesnt match the hashlock, ignoring.')\n-                    continue\n \n-                return response\n+class StartExchangeTask(BaseMediatedTransferTask):\n+    \"\"\" Initiator task, responsible to choose a random secret, initiate the\n+    asset exchange by sending a mediated transfer to the counterparty and\n+    revealing the secret once the exchange can be complete.\n+    \"\"\"\n \n-            if isinstance(response, RefundTransfer):\n-                if response.sender != next_hop:\n-                    log.error('Invalid message supplied to the task. %s', repr(response))\n-                    continue\n+    def __init__(self, identifier, raiden, from_asset, from_amount, to_asset, to_amount, target):\n+        # pylint: disable=too-many-arguments\n \n-                return response\n+        super(StartExchangeTask, self).__init__()\n+\n+        self.identifier = identifier\n+        self.raiden = raiden\n+        self.from_asset = from_asset\n+        self.from_amount = from_amount\n+        self.to_asset = to_asset\n+        self.to_amount = to_amount\n+        self.target = target\n+\n+    def __repr__(self):\n+        return '<{} {} from_asset:{} to_asset:{}>'.format(\n+            self.__class__.__name__,\n+            pex(self.raiden.address),\n+            pex(self.from_asset),\n+            pex(self.to_asset),\n+        )\n+\n+    def _run(self):  # pylint: disable=method-hidden,too-many-locals\n+        identifier = self.identifier\n+        raiden = self.raiden\n+        from_asset = self.from_asset\n+        from_amount = self.from_amount\n+        to_asset = self.to_asset\n+        to_amount = self.to_amount\n+        target = self.target\n+\n+        from_assetmanager = raiden.get_manager_by_asset_address(from_asset)\n+        to_assetmanager = raiden.get_manager_by_asset_address(to_asset)\n+\n+        from_transfermanager = from_assetmanager.transfermanager\n+\n+        from_routes = from_assetmanager.get_best_routes(\n+            from_amount,\n+            target,\n+            lock_timeout=None,\n+        )\n+        fee = 0\n+\n+        for path, from_channel in from_routes:\n+            # for each new path a new secret must be used\n+            secret = sha3(hex(random.getrandbits(256)))\n+            hashlock = sha3(secret)\n+\n+            from_transfermanager.register_task_for_hashlock(self, hashlock)\n+            from_assetmanager.register_channel_for_hashlock(from_channel, hashlock)\n+\n+            lock_expiration = (\n+                raiden.chain.block_number() +\n+                from_channel.settle_timeout -\n+                raiden.config['reveal_timeout']\n+            )\n+\n+            from_mediated_transfer = from_channel.create_mediatedtransfer(\n+                raiden.address,\n+                target,\n+                fee,\n+                from_amount,\n+                identifier,\n+                lock_expiration,\n+                hashlock,\n+            )\n+            raiden.sign(from_mediated_transfer)\n+            from_channel.register_transfer(from_mediated_transfer)\n+\n+            # wait for the SecretRequest and MediatedTransfer\n+            to_mediated_transfer = self.send_and_wait_valid_state(\n+                raiden,\n+                path,\n+                from_mediated_transfer,\n+                to_asset,\n+                to_amount,\n+            )\n+\n+            if to_mediated_transfer is None:\n+                # the initiator can deregister right away since it knows the\n+                # secret wont be revealed\n+                from_transfermanager.on_hashlock_result(hashlock, False)\n+                del from_assetmanager.hashlock_channel[hashlock]\n+\n+            elif isinstance(to_mediated_transfer, MediatedTransfer):\n+                to_hop = to_mediated_transfer.sender\n+\n+                # reveal the secret to the `to_hop` and `target`\n+                self.reveal_secret(\n+                    self.raiden,\n+                    secret,\n+                    last_node=to_hop,\n+                    exchange_node=target,\n+                )\n+\n+                to_channel = to_assetmanager.get_channel_by_partner_address(\n+                    to_mediated_transfer.sender\n+                )\n+\n+                # now the secret can be revealed forward (`from_hop`)\n+                from_assetmanager.handle_secret(identifier, secret)\n+                to_assetmanager.handle_secret(identifier, secret)\n+\n+                self._wait_for_unlock_or_close(\n+                    raiden,\n+                    to_assetmanager,\n+                    to_channel,\n+                    to_mediated_transfer,\n+                )\n+\n+                from_transfermanager.on_hashlock_result(hashlock, True)\n+                self.done_result.set(True)\n+\n+    def send_and_wait_valid_state(self, raiden, path, from_asset_transfer,  # noqa\n+                                  to_asset, to_amount):\n+        \"\"\" Start the exchange by sending the first mediated transfer to the\n+        taker and wait for mediated transfer for the exchagend asset.\n+\n+        This method will validate the messages received, discards the invalid","path":"raiden/tasks.py","position":1092,"original_position":1092,"commit_id":"070bd2297e90a0105288f5eaea3db5c06787164a","original_commit_id":"070bd2297e90a0105288f5eaea3db5c06787164a","user":{"login":"LefterisJP","id":1658405,"avatar_url":"https://avatars.githubusercontent.com/u/1658405?v=3","gravatar_id":"","url":"https://api.github.com/users/LefterisJP","html_url":"https://github.com/LefterisJP","followers_url":"https://api.github.com/users/LefterisJP/followers","following_url":"https://api.github.com/users/LefterisJP/following{/other_user}","gists_url":"https://api.github.com/users/LefterisJP/gists{/gist_id}","starred_url":"https://api.github.com/users/LefterisJP/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LefterisJP/subscriptions","organizations_url":"https://api.github.com/users/LefterisJP/orgs","repos_url":"https://api.github.com/users/LefterisJP/repos","events_url":"https://api.github.com/users/LefterisJP/events{/privacy}","received_events_url":"https://api.github.com/users/LefterisJP/received_events","type":"User","site_admin":false},"body":"`discards` -> `discard`","created_at":"2016-10-19T09:27:14Z","updated_at":"2016-10-19T09:40:56Z","html_url":"https://github.com/raiden-network/raiden/pull/161#discussion_r84030106","pull_request_url":"https://api.github.com/repos/raiden-network/raiden/pulls/161","_links":{"self":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/comments/84030106"},"html":{"href":"https://github.com/raiden-network/raiden/pull/161#discussion_r84030106"},"pull_request":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/161"}}},"pull_request":{"url":"https://api.github.com/repos/raiden-network/raiden/pulls/161","id":89122806,"html_url":"https://github.com/raiden-network/raiden/pull/161","diff_url":"https://github.com/raiden-network/raiden/pull/161.diff","patch_url":"https://github.com/raiden-network/raiden/pull/161.patch","issue_url":"https://api.github.com/repos/raiden-network/raiden/issues/161","number":161,"state":"open","locked":false,"title":"Asset Exchange","user":{"login":"hackaugusto","id":310139,"avatar_url":"https://avatars.githubusercontent.com/u/310139?v=3","gravatar_id":"","url":"https://api.github.com/users/hackaugusto","html_url":"https://github.com/hackaugusto","followers_url":"https://api.github.com/users/hackaugusto/followers","following_url":"https://api.github.com/users/hackaugusto/following{/other_user}","gists_url":"https://api.github.com/users/hackaugusto/gists{/gist_id}","starred_url":"https://api.github.com/users/hackaugusto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hackaugusto/subscriptions","organizations_url":"https://api.github.com/users/hackaugusto/orgs","repos_url":"https://api.github.com/users/hackaugusto/repos","events_url":"https://api.github.com/users/hackaugusto/events{/privacy}","received_events_url":"https://api.github.com/users/hackaugusto/received_events","type":"User","site_admin":false},"body":"- Added tasks for asset exchanges\r\n- Checking values for SecretRequest\r\n- Better state transitions for mediated tasks\r\n\r\nPlease review after #156","created_at":"2016-10-13T01:29:37Z","updated_at":"2016-10-19T09:40:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"449eb3a79787f16d05fc6670e7dd2144f34f2edd","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/raiden-network/raiden/pulls/161/commits","review_comments_url":"https://api.github.com/repos/raiden-network/raiden/pulls/161/comments","review_comment_url":"https://api.github.com/repos/raiden-network/raiden/pulls/comments{/number}","comments_url":"https://api.github.com/repos/raiden-network/raiden/issues/161/comments","statuses_url":"https://api.github.com/repos/raiden-network/raiden/statuses/070bd2297e90a0105288f5eaea3db5c06787164a","head":{"label":"hackaugusto:exchange","ref":"exchange","sha":"070bd2297e90a0105288f5eaea3db5c06787164a","user":{"login":"hackaugusto","id":310139,"avatar_url":"https://avatars.githubusercontent.com/u/310139?v=3","gravatar_id":"","url":"https://api.github.com/users/hackaugusto","html_url":"https://github.com/hackaugusto","followers_url":"https://api.github.com/users/hackaugusto/followers","following_url":"https://api.github.com/users/hackaugusto/following{/other_user}","gists_url":"https://api.github.com/users/hackaugusto/gists{/gist_id}","starred_url":"https://api.github.com/users/hackaugusto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hackaugusto/subscriptions","organizations_url":"https://api.github.com/users/hackaugusto/orgs","repos_url":"https://api.github.com/users/hackaugusto/repos","events_url":"https://api.github.com/users/hackaugusto/events{/privacy}","received_events_url":"https://api.github.com/users/hackaugusto/received_events","type":"User","site_admin":false},"repo":{"id":52306496,"name":"raiden","full_name":"hackaugusto/raiden","owner":{"login":"hackaugusto","id":310139,"avatar_url":"https://avatars.githubusercontent.com/u/310139?v=3","gravatar_id":"","url":"https://api.github.com/users/hackaugusto","html_url":"https://github.com/hackaugusto","followers_url":"https://api.github.com/users/hackaugusto/followers","following_url":"https://api.github.com/users/hackaugusto/following{/other_user}","gists_url":"https://api.github.com/users/hackaugusto/gists{/gist_id}","starred_url":"https://api.github.com/users/hackaugusto/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hackaugusto/subscriptions","organizations_url":"https://api.github.com/users/hackaugusto/orgs","repos_url":"https://api.github.com/users/hackaugusto/repos","events_url":"https://api.github.com/users/hackaugusto/events{/privacy}","received_events_url":"https://api.github.com/users/hackaugusto/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/hackaugusto/raiden","description":"Raiden Network","fork":true,"url":"https://api.github.com/repos/hackaugusto/raiden","forks_url":"https://api.github.com/repos/hackaugusto/raiden/forks","keys_url":"https://api.github.com/repos/hackaugusto/raiden/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hackaugusto/raiden/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hackaugusto/raiden/teams","hooks_url":"https://api.github.com/repos/hackaugusto/raiden/hooks","issue_events_url":"https://api.github.com/repos/hackaugusto/raiden/issues/events{/number}","events_url":"https://api.github.com/repos/hackaugusto/raiden/events","assignees_url":"https://api.github.com/repos/hackaugusto/raiden/assignees{/user}","branches_url":"https://api.github.com/repos/hackaugusto/raiden/branches{/branch}","tags_url":"https://api.github.com/repos/hackaugusto/raiden/tags","blobs_url":"https://api.github.com/repos/hackaugusto/raiden/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hackaugusto/raiden/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hackaugusto/raiden/git/refs{/sha}","trees_url":"https://api.github.com/repos/hackaugusto/raiden/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hackaugusto/raiden/statuses/{sha}","languages_url":"https://api.github.com/repos/hackaugusto/raiden/languages","stargazers_url":"https://api.github.com/repos/hackaugusto/raiden/stargazers","contributors_url":"https://api.github.com/repos/hackaugusto/raiden/contributors","subscribers_url":"https://api.github.com/repos/hackaugusto/raiden/subscribers","subscription_url":"https://api.github.com/repos/hackaugusto/raiden/subscription","commits_url":"https://api.github.com/repos/hackaugusto/raiden/commits{/sha}","git_commits_url":"https://api.github.com/repos/hackaugusto/raiden/git/commits{/sha}","comments_url":"https://api.github.com/repos/hackaugusto/raiden/comments{/number}","issue_comment_url":"https://api.github.com/repos/hackaugusto/raiden/issues/comments{/number}","contents_url":"https://api.github.com/repos/hackaugusto/raiden/contents/{+path}","compare_url":"https://api.github.com/repos/hackaugusto/raiden/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hackaugusto/raiden/merges","archive_url":"https://api.github.com/repos/hackaugusto/raiden/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hackaugusto/raiden/downloads","issues_url":"https://api.github.com/repos/hackaugusto/raiden/issues{/number}","pulls_url":"https://api.github.com/repos/hackaugusto/raiden/pulls{/number}","milestones_url":"https://api.github.com/repos/hackaugusto/raiden/milestones{/number}","notifications_url":"https://api.github.com/repos/hackaugusto/raiden/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hackaugusto/raiden/labels{/name}","releases_url":"https://api.github.com/repos/hackaugusto/raiden/releases{/id}","deployments_url":"https://api.github.com/repos/hackaugusto/raiden/deployments","created_at":"2016-02-22T21:17:14Z","updated_at":"2016-02-22T21:17:15Z","pushed_at":"2016-10-19T06:54:26Z","git_url":"git://github.com/hackaugusto/raiden.git","ssh_url":"git@github.com:hackaugusto/raiden.git","clone_url":"https://github.com/hackaugusto/raiden.git","svn_url":"https://github.com/hackaugusto/raiden","homepage":null,"size":1776,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"raiden-network:master","ref":"master","sha":"0bfc8a70e9d535a07e21148cd7069e7528e0bf37","user":{"login":"raiden-network","id":19666847,"avatar_url":"https://avatars.githubusercontent.com/u/19666847?v=3","gravatar_id":"","url":"https://api.github.com/users/raiden-network","html_url":"https://github.com/raiden-network","followers_url":"https://api.github.com/users/raiden-network/followers","following_url":"https://api.github.com/users/raiden-network/following{/other_user}","gists_url":"https://api.github.com/users/raiden-network/gists{/gist_id}","starred_url":"https://api.github.com/users/raiden-network/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raiden-network/subscriptions","organizations_url":"https://api.github.com/users/raiden-network/orgs","repos_url":"https://api.github.com/users/raiden-network/repos","events_url":"https://api.github.com/users/raiden-network/events{/privacy}","received_events_url":"https://api.github.com/users/raiden-network/received_events","type":"Organization","site_admin":false},"repo":{"id":50646767,"name":"raiden","full_name":"raiden-network/raiden","owner":{"login":"raiden-network","id":19666847,"avatar_url":"https://avatars.githubusercontent.com/u/19666847?v=3","gravatar_id":"","url":"https://api.github.com/users/raiden-network","html_url":"https://github.com/raiden-network","followers_url":"https://api.github.com/users/raiden-network/followers","following_url":"https://api.github.com/users/raiden-network/following{/other_user}","gists_url":"https://api.github.com/users/raiden-network/gists{/gist_id}","starred_url":"https://api.github.com/users/raiden-network/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raiden-network/subscriptions","organizations_url":"https://api.github.com/users/raiden-network/orgs","repos_url":"https://api.github.com/users/raiden-network/repos","events_url":"https://api.github.com/users/raiden-network/events{/privacy}","received_events_url":"https://api.github.com/users/raiden-network/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/raiden-network/raiden","description":"Raiden Network","fork":false,"url":"https://api.github.com/repos/raiden-network/raiden","forks_url":"https://api.github.com/repos/raiden-network/raiden/forks","keys_url":"https://api.github.com/repos/raiden-network/raiden/keys{/key_id}","collaborators_url":"https://api.github.com/repos/raiden-network/raiden/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/raiden-network/raiden/teams","hooks_url":"https://api.github.com/repos/raiden-network/raiden/hooks","issue_events_url":"https://api.github.com/repos/raiden-network/raiden/issues/events{/number}","events_url":"https://api.github.com/repos/raiden-network/raiden/events","assignees_url":"https://api.github.com/repos/raiden-network/raiden/assignees{/user}","branches_url":"https://api.github.com/repos/raiden-network/raiden/branches{/branch}","tags_url":"https://api.github.com/repos/raiden-network/raiden/tags","blobs_url":"https://api.github.com/repos/raiden-network/raiden/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/raiden-network/raiden/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/raiden-network/raiden/git/refs{/sha}","trees_url":"https://api.github.com/repos/raiden-network/raiden/git/trees{/sha}","statuses_url":"https://api.github.com/repos/raiden-network/raiden/statuses/{sha}","languages_url":"https://api.github.com/repos/raiden-network/raiden/languages","stargazers_url":"https://api.github.com/repos/raiden-network/raiden/stargazers","contributors_url":"https://api.github.com/repos/raiden-network/raiden/contributors","subscribers_url":"https://api.github.com/repos/raiden-network/raiden/subscribers","subscription_url":"https://api.github.com/repos/raiden-network/raiden/subscription","commits_url":"https://api.github.com/repos/raiden-network/raiden/commits{/sha}","git_commits_url":"https://api.github.com/repos/raiden-network/raiden/git/commits{/sha}","comments_url":"https://api.github.com/repos/raiden-network/raiden/comments{/number}","issue_comment_url":"https://api.github.com/repos/raiden-network/raiden/issues/comments{/number}","contents_url":"https://api.github.com/repos/raiden-network/raiden/contents/{+path}","compare_url":"https://api.github.com/repos/raiden-network/raiden/compare/{base}...{head}","merges_url":"https://api.github.com/repos/raiden-network/raiden/merges","archive_url":"https://api.github.com/repos/raiden-network/raiden/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/raiden-network/raiden/downloads","issues_url":"https://api.github.com/repos/raiden-network/raiden/issues{/number}","pulls_url":"https://api.github.com/repos/raiden-network/raiden/pulls{/number}","milestones_url":"https://api.github.com/repos/raiden-network/raiden/milestones{/number}","notifications_url":"https://api.github.com/repos/raiden-network/raiden/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/raiden-network/raiden/labels{/name}","releases_url":"https://api.github.com/repos/raiden-network/raiden/releases{/id}","deployments_url":"https://api.github.com/repos/raiden-network/raiden/deployments","created_at":"2016-01-29T07:40:47Z","updated_at":"2016-10-17T08:41:16Z","pushed_at":"2016-10-19T09:10:46Z","git_url":"git://github.com/raiden-network/raiden.git","ssh_url":"git@github.com:raiden-network/raiden.git","clone_url":"https://github.com/raiden-network/raiden.git","svn_url":"https://github.com/raiden-network/raiden","homepage":null,"size":1850,"stargazers_count":138,"watchers_count":138,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":28,"mirror_url":null,"open_issues_count":66,"forks":28,"open_issues":66,"watchers":138,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/161"},"html":{"href":"https://github.com/raiden-network/raiden/pull/161"},"issue":{"href":"https://api.github.com/repos/raiden-network/raiden/issues/161"},"comments":{"href":"https://api.github.com/repos/raiden-network/raiden/issues/161/comments"},"review_comments":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/161/comments"},"review_comment":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/raiden-network/raiden/pulls/161/commits"},"statuses":{"href":"https://api.github.com/repos/raiden-network/raiden/statuses/070bd2297e90a0105288f5eaea3db5c06787164a"}}}}
{"actor":{"display_login":"LefterisJP"}}