Exemple #1
0
 def __repr__(self):
     return '<ChainState block:{} networks:{} qty_transfers:{} chain_id:{}>'.format(
         self.block_number,
         lpex(self.identifiers_to_paymentnetworks.keys()),
         len(self.payment_mapping.secrethashes_to_task),
         self.chain_id,
     )
Exemple #2
0
 def __repr__(self):
     return '<ChainState block:{} networks:{} qty_transfers:{} chain_id:{}>'.format(
         self.block_number,
         lpex(self.identifiers_to_paymentnetworks.keys()),
         len(self.payment_mapping.secrethashes_to_task),
         self.chain_id,
     )
Exemple #3
0
    def send_and_wait_valid(self, raiden, path, mediated_transfer):  # pylint: disable=no-self-use
        """ Send the `mediated_transfer` and wait for either a message from
        `target` or the `next_hop`.

        Validate the message received and discards the invalid ones. The most
        important case being next_hop sending a SecretRequest.
        """
        message_timeout = raiden.config['msg_timeout']
        next_hop = path[1]
        target = path[-1]

        transfer_details = 'path:{} hash:{}'.format(
            lpex(path),
            pex(mediated_transfer.hash),
        )
        log.debug('MEDIATED TRANSFER STARTED {}'.format(transfer_details))

        current_time = time.time()
        limit_time = current_time + message_timeout

        self.event = AsyncResult()
        raiden.send(next_hop, mediated_transfer)

        while current_time <= limit_time:
            response = self.event.wait(limit_time - current_time)

            # critical write section
            self.event = AsyncResult(
            )  # reset so that a new value can be received
            # /critical write section

            current_time = time.time()

            if response is None:
                log.debug(
                    'MEDIATED TRANSFER TIMED OUT {}'.format(transfer_details))
                return None

            if response.sender == next_hop:
                if isinstance(response, (RefundTransfer, TransferTimeout)):
                    return response
                else:
                    log.info('Partner {} sent an invalid message'.format(
                        pex(next_hop)))
                    return None

            if response.sender == target:
                if isinstance(response, SecretRequest):
                    return response
                else:
                    log.info('target {} sent an invalid message'.format(
                        pex(target)))
                    return None

            log.error('Invalid message ignoring. {}'.format(repr(response)))

        return None
Exemple #4
0
    def send_and_wait_valid(self, raiden, path, mediated_transfer):
        message_timeout = raiden.config['msg_timeout']
        next_hop = path[1]

        transfer_details = 'path:{} hash:{} initiator:{}'.format(
            lpex(path),
            pex(mediated_transfer.hash),
            pex(mediated_transfer.initiator),
        )
        log.debug('MEDIATED TRANSFER {}'.format(transfer_details))

        current_time = time.time()
        limit_time = current_time + message_timeout

        self.event = AsyncResult()
        raiden.send(next_hop, mediated_transfer)

        while current_time <= limit_time:
            response = self.event.wait(limit_time - current_time)

            # critical write section
            self.event = AsyncResult(
            )  # reset so that a new value can be received
            # /critical write section

            current_time = time.time()

            if response is None:
                log.error('MEDIATED TRANSFER TIMED OUT {} timeout:{}'.format(
                    transfer_details,
                    message_timeout,
                ))
                return None

            if isinstance(response, Secret):
                if response.hashlock != mediated_transfer.lock.hashlock:
                    log.error('Secret doesnt match the hashlock, ignoring.')
                    continue

                return response

            if response.target != raiden.address or response.sender != next_hop:
                log.error('Invalid message supplied to the task. {}'.format(
                    repr(response)))
                continue

            if isinstance(response, RefundTransfer):
                return response

            log.error('Partner sent an invalid message. {}'.format(
                repr(response)))

        return None
Exemple #5
0
 def __repr__(self) -> str:
     return (
         "ChainState(block_number={} block_hash={} networks={} qty_transfers={} chain_id={})"
     ).format(
         self.block_number,
         to_hex(self.block_hash),
         # pylint: disable=E1101
         lpex(self.identifiers_to_tokennetworkregistries.keys()),
         # pylint: disable=E1101
         len(self.payment_mapping.secrethashes_to_task),
         self.chain_id,
     )
Exemple #6
0
    def send_and_wait_valid(self, raiden, path, mediated_transfer):  # pylint: disable=no-self-use
        """ Send the `mediated_transfer` and wait for either a message from
        `target` or the `next_hop`.

        Validate the message received and discards the invalid ones. The most
        important case being next_hop sending a SecretRequest.
        """
        message_timeout = raiden.config['msg_timeout']
        next_hop = path[1]
        target = path[-1]

        transfer_details = 'path:{} hash:{}'.format(
            lpex(path),
            pex(mediated_transfer.hash),
        )
        log.debug('MEDIATED TRANSFER STARTED {}'.format(transfer_details))

        current_time = time.time()
        limit_time = current_time + message_timeout

        self.event = AsyncResult()
        raiden.send(next_hop, mediated_transfer)

        while current_time <= limit_time:
            response = self.event.wait(limit_time - current_time)

            # critical write section
            self.event = AsyncResult()  # reset so that a new value can be received
            # /critical write section

            current_time = time.time()

            if response is None:
                log.debug('MEDIATED TRANSFER TIMED OUT {}'.format(transfer_details))
                return None

            if response.sender == next_hop:
                if isinstance(response, (RefundTransfer, TransferTimeout)):
                    return response
                else:
                    log.info('Partner {} sent an invalid message'.format(pex(next_hop)))
                    return None

            if response.sender == target:
                if isinstance(response, SecretRequest):
                    return response
                else:
                    log.info('target {} sent an invalid message'.format(pex(target)))
                    return None

            log.error('Invalid message ignoring. {}'.format(repr(response)))

        return None
Exemple #7
0
    def send_and_wait_valid(self, raiden, path, mediated_transfer):
        message_timeout = raiden.config['msg_timeout']
        next_hop = path[1]

        transfer_details = 'path:{} hash:{} initiator:{}'.format(
            lpex(path),
            pex(mediated_transfer.hash),
            pex(mediated_transfer.initiator),
        )
        log.debug('MEDIATED TRANSFER {}'.format(transfer_details))

        current_time = time.time()
        limit_time = current_time + message_timeout

        self.event = AsyncResult()
        raiden.send(next_hop, mediated_transfer)

        while current_time <= limit_time:
            response = self.event.wait(limit_time - current_time)

            # critical write section
            self.event = AsyncResult()  # reset so that a new value can be received
            # /critical write section

            current_time = time.time()

            if response is None:
                log.error('MEDIATED TRANSFER TIMED OUT {} timeout:{}'.format(
                    transfer_details,
                    message_timeout,
                ))
                return None

            if isinstance(response, Secret):
                if response.hashlock != mediated_transfer.lock.hashlock:
                    log.error('Secret doesnt match the hashlock, ignoring.')
                    continue

                return response

            if response.target != raiden.address or response.sender != next_hop:
                log.error('Invalid message supplied to the task. {}'.format(repr(response)))
                continue

            if isinstance(response, RefundTransfer):
                return response

            log.error('Partner sent an invalid message. {}'.format(repr(response)))

        return None
Exemple #8
0
    def _run(self):  # pylint: disable=method-hidden

        for path, channel in self.get_best_routes():
            next_hop = path[1]
            timeout = self.timeout(next_hop, self.target)

            mediated_transfer = channel.create_mediatedtransfer(
                self.initiator,
                self.target,
                self.fee,
                self.amount,
                self.expiration,
                self.hashlock,
            )
            self.raiden.sign(mediated_transfer)
            channel.register_transfer(mediated_transfer)

            log.debug('MEDIATED TRANSFER initiator={} {}'.format(
                pex(mediated_transfer.initiator),
                lpex(path),
            ))

            msg = self.send_transfer_and_wait(next_hop, mediated_transfer,
                                              path, timeout)

            log.debug('MEDIATED TRANSFER RETURNED {} {}'.format(
                pex(self.raiden.address),
                msg,
            ))

            result = self.check_path(msg, channel)

            # `next_hop` didn't get a response from any of it's, let's try the
            # next path
            if isinstance(result, TransferTimeout):
                continue

            # `next_hop` doesn't have any path with a valid channel to proceed,
            # try the next path
            if isinstance(result, CancelTransfer):
                continue

            # `check_path` failed, try next path
            if result is None:
                continue

            return self.on_completion(result)

        # No suitable path avaiable (e.g. insufficient distributable, no active node)
        # Send CancelTransfer to the originating node, this has the effect of
        # backtracking in the graph search of the raiden network.
        if self.originating_transfer:
            from_address = self.originating_transfer.sender
            from_transfer = self.originating_transfer
            from_channel = self.assetmanager.channels[from_address]

            log.debug('CANCEL MEDIATED TRANSFER from={} {}'.format(
                pex(from_address),
                pex(self.raiden.address),
            ))

            cancel_transfer = from_channel.create_canceltransfer_for(
                from_transfer)
            self.raiden.sign(cancel_transfer)
            from_channel.register_transfer(cancel_transfer)
            self.raiden.send(from_address, cancel_transfer)
        else:
            log.error(
                'UNABLE TO COMPLETE MEDIATED TRANSFER target={} amount={}'.
                format(
                    pex(self.target),
                    self.amount,
                ))

        return self.on_completion(False)
Exemple #9
0
    def register_transfer_from_to(self, transfer, from_state, to_state):  # noqa pylint: disable=too-many-branches
        """ Validates and register a signed transfer, updating the channel's state accordingly.

        Note:
            The transfer must be register before it is sent, not on
            acknowledgement. That is necessary for to reasons:

            - Guarantee that the transfer is valid.
            - Avoiding sending a new transaction without funds.

        Raises:
            InsufficientBalance: If the transfer is negative or above the distributable amount.
            InvalidLocksRoot: If locksroot check fails.
            InvalidLockTime: If the transfer has expired.
            InvalidNonce: If the expected nonce does not match.
            InvalidSecret: If there is no lock registered for the given secret.
            ValueError: If there is an address mismatch (asset or node address).
        """
        if transfer.asset != self.asset_address:
            raise ValueError('Asset address mismatch')

        if transfer.recipient != to_state.address:
            raise ValueError('Unknow recipient')

        if transfer.sender != from_state.address:
            raise ValueError('Unsigned transfer')

        # nonce is changed only when a transfer is un/registered, if the test
        # fail either we are out of sync, a message out of order, or it's an
        # forged transfer
        if transfer.nonce < 1 or transfer.nonce != from_state.nonce:
            raise InvalidNonce(transfer)

        # if the locksroot is out-of-sync (because a transfer was created while
        # a Secret was in trafic) the balance _will_ be wrong, so first check
        # the locksroot and then the balance
        if isinstance(transfer, LockedTransfer):
            block_number = self.external_state.get_block_number()

            if to_state.balance_proof.is_pending(transfer.lock.hashlock):
                raise ValueError('hashlock is already registered')

            # As a receiver: Check that all locked transfers are registered in
            # the locksroot, if any hashlock is missing there is no way to
            # claim it while the channel is closing
            expected_locksroot = to_state.compute_merkleroot_with(transfer.lock)
            if expected_locksroot != transfer.locksroot:
                if log.isEnabledFor(logging.ERROR):
                    log.error(
                        'LOCKSROOT MISMATCH node:%s %s > %s lockhash:%s lockhashes:%s',
                        pex(self.our_state.address),
                        pex(from_state.address),
                        pex(to_state.address),
                        pex(sha3(transfer.lock.as_bytes)),
                        lpex(to_state.balance_proof.unclaimed_merkletree()),
                        expected_locksroot=pex(expected_locksroot),
                        received_locksroot=pex(transfer.locksroot),
                    )

                raise InvalidLocksRoot(transfer)

            # As a receiver: If the lock expiration is larger than the settling
            # time a secret could be revealed after the channel is settled and
            # we won't be able to claim the asset
            if not transfer.lock.expiration - block_number < self.settle_timeout:
                log.error(
                    "Transfer expiration doesn't allow for correct settlement.",
                    lock_expiration=transfer.lock.expiration,
                    current_block=block_number,
                    settle_timeout=self.settle_timeout,
                )

                raise ValueError("Transfer expiration doesn't allow for correct settlement.")

            if not transfer.lock.expiration - block_number > self.reveal_timeout:
                log.error(
                    'Expiration smaller than the minimum required.',
                    lock_expiration=transfer.lock.expiration,
                    current_block=block_number,
                    reveal_timeout=self.reveal_timeout,
                )

                raise ValueError('Expiration smaller than the minimum required.')

        # only check the balance if the locksroot matched
        if transfer.transferred_amount < from_state.transferred_amount:
            if log.isEnabledFor(logging.ERROR):
                log.error(
                    'NEGATIVE TRANSFER node:%s %s > %s %s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    transfer,
                )

            raise ValueError('Negative transfer')

        amount = transfer.transferred_amount - from_state.transferred_amount
        distributable = from_state.distributable(to_state)

        if amount > distributable:
            raise InsufficientBalance(transfer)

        if isinstance(transfer, LockedTransfer):
            if amount + transfer.lock.amount > distributable:
                raise InsufficientBalance(transfer)

        # all checks need to be done before the internal state of the channel
        # is changed, otherwise if a check fails and state was changed the
        # channel will be left trashed

        if isinstance(transfer, LockedTransfer):
            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'REGISTERED LOCK node:%s %s > %s currentlocksroot:%s lockhashes:%s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    pex(to_state.balance_proof.merkleroot_for_unclaimed()),
                    lpex(to_state.balance_proof.unclaimed_merkletree()),

                    lock_amount=transfer.lock.amount,
                    lock_expiration=transfer.lock.expiration,
                    lock_hashlock=pex(transfer.lock.hashlock),
                    lockhash=pex(sha3(transfer.lock.as_bytes)),
                )

            to_state.register_locked_transfer(transfer)

            # register this channel as waiting for the secret (the secret can
            # be revealed through a message or an blockchain log)
            self.external_state.register_channel_for_hashlock(
                self,
                transfer.lock.hashlock,
            )

        if isinstance(transfer, DirectTransfer):
            to_state.register_direct_transfer(transfer)

        from_state.transferred_amount = transfer.transferred_amount
        from_state.nonce += 1

        if isinstance(transfer, DirectTransfer):
            # if we are the recipient, spawn callback for incoming transfers
            if transfer.recipient == self.our_state.address:
                for callback in self.on_withdrawable_callbacks:
                    gevent.spawn(
                        callback,
                        transfer.asset,
                        transfer.recipient,
                        transfer.sender,  # 'initiator' is sender here
                        transfer.transferred_amount,
                        None  # no hashlock in DirectTransfer
                    )

            # if we are the sender, call the 'success' callback
            elif from_state.address == self.our_state.address:
                callbacks_to_remove = list()
                for callback in self.on_task_completed_callbacks:
                    result = callback(task=None, success=True)  # XXX maybe use gevent.spawn()

                    if result is True:
                        callbacks_to_remove.append(callback)

                for callback in callbacks_to_remove:
                    self.on_task_completed_callbacks.remove(callback)

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REGISTERED TRANSFER node:%s %s > %s '
                'transfer:%s transferred_amount:%s nonce:%s '
                'current_locksroot:%s',
                pex(self.our_state.address),
                pex(from_state.address),
                pex(to_state.address),
                repr(transfer),
                from_state.transferred_amount,
                from_state.nonce,
                pex(to_state.balance_proof.merkleroot_for_unclaimed()),
            )
Exemple #10
0
    def register_transfer_from_to(self, transfer, from_state, to_state):  # noqa pylint: disable=too-many-branches,too-many-statements
        """ Validates and register a signed transfer, updating the channel's state accordingly.

        Note:
            The transfer must be registered before it is sent, not on
            acknowledgement. That is necessary for two reasons:

            - Guarantee that the transfer is valid.
            - Avoid sending a new transaction without funds.

        Raises:
            InsufficientBalance: If the transfer is negative or above the distributable amount.
            InvalidLocksRoot: If locksroot check fails.
            InvalidNonce: If the expected nonce does not match.
            ValueError: If there is an address mismatch (token or node address).
        """
        if transfer.token != self.token_address:
            raise ValueError('Token address mismatch')

        if transfer.recipient != to_state.address:
            raise ValueError('Unknown recipient')

        if transfer.sender != from_state.address:
            raise ValueError('Unsigned transfer')

        # nonce is changed only when a transfer is un/registered, if the test
        # fails either we are out of sync, a message out of order, or it's a
        # forged transfer
        if transfer.nonce < 1 or transfer.nonce != from_state.nonce:
            if log.isEnabledFor(logging.WARN):
                log.warn(
                    'Received out of order transfer from %s. Expected '
                    'nonce: %s but got nonce: %s',
                    pex(transfer.sender),
                    from_state.nonce,
                    transfer.nonce,
                )
            raise InvalidNonce(transfer)

        # if the locksroot is out-of-sync (because a transfer was created while
        # a Secret was in traffic) the balance _will_ be wrong, so first check
        # the locksroot and then the balance
        if isinstance(transfer, LockedTransfer):
            if to_state.balance_proof.is_pending(transfer.lock.hashlock):
                raise ValueError('hashlock is already registered')

            # As a receiver: Check that all locked transfers are registered in
            # the locksroot, if any hashlock is missing there is no way to
            # claim it while the channel is closing
            expected_locksroot = to_state.compute_merkleroot_with(
                transfer.lock)
            if expected_locksroot != transfer.locksroot:
                if log.isEnabledFor(logging.ERROR):
                    log.error(
                        'LOCKSROOT MISMATCH node:%s %s > %s lockhash:%s lockhashes:%s',
                        pex(self.our_state.address),
                        pex(from_state.address),
                        pex(to_state.address),
                        pex(sha3(transfer.lock.as_bytes)),
                        lpex(to_state.balance_proof.unclaimed_merkletree()),
                        expected_locksroot=pex(expected_locksroot),
                        received_locksroot=pex(transfer.locksroot),
                    )

                raise InvalidLocksRoot(expected_locksroot, transfer.locksroot)

            # If the lock expires after the settle_period a secret could be
            # revealed after the channel is settled and we won't be able to
            # claim the token.
            end_settle_period = self.block_number + self.settle_timeout
            expires_after_settle = transfer.lock.expiration > end_settle_period

            if expires_after_settle:
                log.error(
                    'Lock expires after the settlement period.',
                    lock_expiration=transfer.lock.expiration,
                    current_block=self.block_number,
                    settle_timeout=self.settle_timeout,
                )

                raise ValueError('Lock expires after the settlement period.')

            # If the lock expires within the unsafe_period we cannot accept the
            # transfer, since there is not enough time to properly settle
            # on-chain.
            end_unsafe_period = self.block_number + self.reveal_timeout
            expires_unsafe = transfer.lock.expiration < end_unsafe_period

            if expires_unsafe:
                log.error(
                    'Lock expires within the unsafe_period.',
                    lock_expiration=transfer.lock.expiration,
                    current_block=self.block_number,
                    reveal_timeout=self.reveal_timeout,
                )

                raise ValueError('Lock expires within the unsafe_period.')

        # only check the balance if the locksroot matched
        if transfer.transferred_amount < from_state.transferred_amount:
            if log.isEnabledFor(logging.ERROR):
                log.error(
                    'NEGATIVE TRANSFER node:%s %s > %s %s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    transfer,
                )

            raise ValueError('Negative transfer')

        amount = transfer.transferred_amount - from_state.transferred_amount
        distributable = from_state.distributable(to_state)

        if amount > distributable:
            raise InsufficientBalance(transfer)

        if isinstance(transfer, LockedTransfer):
            if amount + transfer.lock.amount > distributable:
                raise InsufficientBalance(transfer)

        # all checks need to be done before the internal state of the channel
        # is changed, otherwise if a check fails and the state was changed the
        # channel will be left trashed

        if isinstance(transfer, LockedTransfer):
            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'REGISTERED LOCK node:%s %s > %s currentlocksroot:%s lockhashes:%s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    pex(to_state.balance_proof.merkleroot_for_unclaimed()),
                    lpex(to_state.balance_proof.unclaimed_merkletree()),
                    lock_amount=transfer.lock.amount,
                    lock_expiration=transfer.lock.expiration,
                    lock_hashlock=pex(transfer.lock.hashlock),
                    lockhash=pex(sha3(transfer.lock.as_bytes)),
                )

            to_state.register_locked_transfer(transfer)

            # register this channel as waiting for the secret (the secret can
            # be revealed through a message or a blockchain log)
            self.external_state.register_channel_for_hashlock(
                self,
                transfer.lock.hashlock,
            )

        if isinstance(transfer, DirectTransfer):
            to_state.register_direct_transfer(transfer)

        from_state.transferred_amount = transfer.transferred_amount
        from_state.nonce += 1

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REGISTERED TRANSFER node:%s %s > %s '
                'transfer:%s transferred_amount:%s nonce:%s '
                'current_locksroot:%s',
                pex(self.our_state.address),
                pex(from_state.address),
                pex(to_state.address),
                repr(transfer),
                from_state.transferred_amount,
                from_state.nonce,
                pex(to_state.balance_proof.merkleroot_for_unclaimed()),
            )
Exemple #11
0
 def __repr__(self):
     return '<NodeState block:{} networks:{} qtd_transfers:{}>'.format(
         self.block_number,
         lpex(self.identifiers_to_paymentnetworks.keys()),
         len(self.payment_mapping.hashlocks_to_task),
     )
Exemple #12
0
    def _run(self):  # pylint: disable=method-hidden,too-many-locals,too-many-branches,too-many-statements
        fee = self.fee
        transfer = self.originating_transfer

        assetmanager = self.transfermanager.assetmanager
        raiden = assetmanager.raiden
        originating_channel = assetmanager.partneraddress_channel[
            transfer.sender]

        assetmanager.register_channel_for_hashlock(
            originating_channel,
            transfer.lock.hashlock,
        )

        lock_expiration = transfer.lock.expiration - raiden.config[
            'reveal_timeout']
        lock_timeout = lock_expiration - raiden.chain.block_number()

        # there are no guarantees that the next_hop will follow the same route
        routes = assetmanager.get_best_routes(
            transfer.lock.amount,
            transfer.target,
            lock_timeout,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'MEDIATED TRANSFER initiator:%s node:%s target:%s',
                pex(transfer.initiator),
                pex(self.address),
                pex(transfer.target),
            )

        for path, forward_channel in routes:
            next_hop = path[1]

            mediated_transfer = forward_channel.create_mediatedtransfer(
                transfer.initiator,
                transfer.target,
                fee,
                transfer.lock.amount,
                lock_expiration,
                transfer.lock.hashlock,
            )
            raiden.sign(mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(transfer.lock.hashlock),
                )

            # Using assetmanager to register the interest because it outlives
            # this task, the secret handling will happend only _once_
            assetmanager.register_channel_for_hashlock(
                forward_channel,
                transfer.lock.hashlock,
            )
            forward_channel.register_transfer(mediated_transfer)

            response = self.send_and_wait_valid(raiden, path,
                                                mediated_transfer)

            if response is None:
                timeout_message = forward_channel.create_timeouttransfer_for(
                    transfer)
                raiden.send_async(transfer.sender, timeout_message)
                self.transfermanager.on_hashlock_result(
                    transfer.lock.hashlock, False)
                return

            if isinstance(response, RefundTransfer):
                if response.lock.amount != transfer.amount:
                    log.info(
                        'Partner %s sent an refund message with an invalid amount',
                        pex(next_hop),
                    )
                    timeout_message = forward_channel.create_timeouttransfer_for(
                        transfer)
                    raiden.send_async(transfer.sender, timeout_message)
                    self.transfermanager.on_hashlock_result(
                        transfer.lock.hashlock, False)
                    return
                else:
                    forward_channel.register_transfer(response)

            elif isinstance(response, Secret):
                # update all channels and propagate the secret (this doesnt claim the lock yet)
                assetmanager.handle_secret(response.secret)

                # wait for the secret from `sender`
                while True:
                    response = self.response_message.wait()
                    # critical write section
                    self.response_message = AsyncResult()
                    # /critical write section

                    # NOTE: this relies on the fact RaindenService dispatches
                    # messages based on the `hashlock` calculated from the
                    # secret, so we know this `response` message secret matches
                    # the secret from the `next_hop`
                    if isinstance(
                            response,
                            Secret) and response.sender == transfer.sender:
                        originating_channel.claim_lock(response.secret)
                        self.transfermanager.on_hashlock_result(
                            transfer.lock.hashlock, True)
                        return

        # No suitable path avaiable (e.g. insufficient distributable, no active node)
        # Send RefundTransfer to the originating node, this has the effect of
        # backtracking in the graph search of the raiden network.
        from_address = transfer.sender
        from_channel = assetmanager.partneraddress_channel[from_address]

        refund_transfer = from_channel.create_refundtransfer_for(transfer)
        from_channel.register_transfer(refund_transfer)

        raiden.sign(refund_transfer)
        raiden.send_async(from_address, refund_transfer)

        log.debug(
            'REFUND MEDIATED TRANSFER from=%s node:%s hashlock:%s',
            pex(from_address),
            pex(raiden.address),
            pex(transfer.lock.hashlock),
        )

        self.transfermanager.on_hashlock_result(transfer.lock.hashlock, False)
        return
    def register_transfer_from_to(
            self,
            block_number,
            transfer,
            from_state,
            to_state):  # noqa pylint: disable=too-many-branches,too-many-statements

        """ Validates and register a signed transfer, updating the channel's state accordingly.

        Note:
            The transfer must be registered before it is sent, not on
            acknowledgement. That is necessary for two reasons:

            - Guarantee that the transfer is valid.
            - Avoid sending a new transaction without funds.

        Raises:
            InsufficientBalance: If the transfer is negative or above the distributable amount.
            InvalidLocksRoot: If locksroot check fails.
            InvalidNonce: If the expected nonce does not match.
            ValueError: If there is an address mismatch (token or node address).
        """
        if transfer.channel != self.channel_address:
            raise ValueError('Channel address mismatch')

        if transfer.sender != from_state.address:
            raise ValueError('Unsigned transfer')

        # nonce is changed only when a transfer is un/registered, if the test
        # fails either we are out of sync, a message out of order, or it's a
        # forged transfer
        is_invalid_nonce = (
            transfer.nonce < 1 or
            (
                from_state.nonce is not None and
                transfer.nonce != from_state.nonce + 1
            )
        )
        if is_invalid_nonce:
            # this may occur on normal operation
            if log.isEnabledFor(logging.INFO):
                log.info(
                    'INVALID NONCE',
                    node=pex(self.our_address),
                    from_=pex(transfer.sender),
                    to=pex(to_state.address),

                    expected_nonce=from_state.nonce,
                    nonce=transfer.nonce,
                )
            raise InvalidNonce(transfer)

        # if the locksroot is out-of-sync (because a transfer was created while
        # a Secret was in traffic) the balance _will_ be wrong, so first check
        # the locksroot and then the balance
        if isinstance(transfer, LockedTransfer):
            if from_state.is_known(transfer.lock.hashlock):
                # this may occur on normal operation
                if log.isEnabledFor(logging.INFO):
                    lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values())
                    lockhashes.extend(from_state.hashlocks_to_pendinglocks.values())
                    log.info(
                        'duplicated lock',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        hashlock=pex(transfer.lock.hashlock),
                        lockhash=pex(sha3(transfer.lock.as_bytes)),
                        lockhashes=lpex(str(l).encode() for l in lockhashes),
                        received_locksroot=pex(transfer.locksroot),
                    )
                raise ValueError('hashlock is already registered')

            # As a receiver: Check that all locked transfers are registered in
            # the locksroot, if any hashlock is missing there is no way to
            # claim it while the channel is closing
            expected_locksroot = from_state.compute_merkleroot_with(transfer.lock)
            if expected_locksroot != transfer.locksroot:
                # this should not happen
                if log.isEnabledFor(logging.WARN):
                    lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values())
                    lockhashes.extend(from_state.hashlocks_to_pendinglocks.values())
                    log.warn(
                        'LOCKSROOT MISMATCH',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        lockhash=pex(sha3(transfer.lock.as_bytes)),
                        lockhashes=lpex(str(l).encode() for l in lockhashes),
                        expected_locksroot=pex(expected_locksroot),
                        received_locksroot=pex(transfer.locksroot),
                    )

                raise InvalidLocksRoot(expected_locksroot, transfer.locksroot)

            # For mediators: This is registering the *mediator* paying
            # transfer. The expiration of the lock must be `reveal_timeout`
            # blocks smaller than the *received* paying transfer. This cannot
            # be checked by the paying channel alone.
            #
            # For the initiators: As there is no backing transfer, the
            # expiration is arbitrary, using the channel settle_timeout as an
            # upper limit because the node receiving the transfer will use it
            # as an upper bound while mediating.
            #
            # For the receiver: A lock that expires after the settle period
            # just means there is more time to withdraw it.
            end_settle_period = self.get_settle_expiration(block_number)
            expires_after_settle = transfer.lock.expiration > end_settle_period
            is_sender = transfer.sender == self.our_address

            if is_sender and expires_after_settle:
                if log.isEnabledFor(logging.ERROR):
                    log.error(
                        'Lock expires after the settlement period.',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        lock_expiration=transfer.lock.expiration,
                        current_block=block_number,
                        end_settle_period=end_settle_period,
                    )

                raise ValueError('Lock expires after the settlement period.')

        # only check the balance if the locksroot matched
        if transfer.transferred_amount < from_state.transferred_amount:
            if log.isEnabledFor(logging.ERROR):
                log.error(
                    'NEGATIVE TRANSFER',
                    node=pex(self.our_state.address),
                    from_=pex(from_state.address),
                    to=pex(to_state.address),
                    transfer=transfer,
                )

            raise ValueError('Negative transfer')

        amount = transfer.transferred_amount - from_state.transferred_amount
        distributable = from_state.distributable(to_state)

        if isinstance(transfer, DirectTransfer):
            if amount > distributable:
                raise InsufficientBalance(transfer)

        elif isinstance(transfer, LockedTransfer):
            if amount + transfer.lock.amount > distributable:
                raise InsufficientBalance(transfer)

        elif isinstance(transfer, Secret):
            hashlock = sha3(transfer.secret)
            lock = from_state.get_lock_by_hashlock(hashlock)
            transferred_amount = from_state.transferred_amount + lock.amount

            # transfer.transferred_amount could be larger than the previous
            # transferred_amount + lock.amount, that scenario is a bug of the
            # payer
            if transfer.transferred_amount != transferred_amount:
                raise ValueError(
                    'invalid transferred_amount, expected: {} got: {}'.format(
                        transferred_amount,
                        transfer.transferred_amount,
                    )
                )

        # all checks need to be done before the internal state of the channel
        # is changed, otherwise if a check fails and the state was changed the
        # channel will be left trashed

        if isinstance(transfer, LockedTransfer):
            if log.isEnabledFor(logging.DEBUG):
                lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values())
                lockhashes.extend(from_state.hashlocks_to_pendinglocks.values())
                log.debug(
                    'REGISTERED LOCK',
                    node=pex(self.our_state.address),
                    from_=pex(from_state.address),
                    to=pex(to_state.address),
                    currentlocksroot=pex(merkleroot(from_state.merkletree)),
                    lockhashes=lpex(str(l).encode() for l in lockhashes),
                    lock_amount=transfer.lock.amount,
                    lock_expiration=transfer.lock.expiration,
                    lock_hashlock=pex(transfer.lock.hashlock),
                    lockhash=pex(sha3(transfer.lock.as_bytes)),
                )

            from_state.register_locked_transfer(transfer)

            # register this channel as waiting for the secret (the secret can
            # be revealed through a message or a blockchain log)
            self.external_state.register_channel_for_hashlock(
                self,
                transfer.lock.hashlock,
            )

        if isinstance(transfer, DirectTransfer):
            from_state.register_direct_transfer(transfer)

        elif isinstance(transfer, Secret):
            from_state.register_secretmessage(transfer)

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REGISTERED TRANSFER',
                node=pex(self.our_state.address),
                from_=pex(from_state.address),
                to=pex(to_state.address),
                transfer=repr(transfer),
                transferred_amount=from_state.transferred_amount,
                nonce=from_state.nonce,
                current_locksroot=pex(merkleroot(from_state.merkletree)),
            )
Exemple #14
0
    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        if not self.stop_event.ready():
            raise RuntimeError(f'{self!r} already started')
        self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['transport']['udp']['external_ip'],
                self.config['transport']['udp']['external_port'],
            )

        self.maybe_upgrade_db()

        storage = sqlite.SerializedSQLiteStorage(
            database_path=self.database_path,
            serializer=serialize.JSONSerializer(),
        )
        storage.log_run()
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier='latest',
        )

        if self.wal.state_manager.current_state is None:
            log.debug(
                'No recoverable state available, created inital state',
                node=pex(self.address),
            )
            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = self.query_start_block

            state_change = ActionInitChain(
                random.Random(),
                last_log_block_number,
                self.chain.node_address,
                self.chain.network_id,
            )
            self.handle_state_change(state_change)

            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                constants.EMPTY_HASH,
                payment_network,
                last_log_block_number,
            )
            self.handle_state_change(state_change)
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(self.wal.state_manager.current_state)
            log.debug(
                'Restored state from WAL',
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f'Token network address mismatch.\n'
                    f'Raiden is configured to use the smart contract '
                    f'{configured_registry}, which conflicts with the current known '
                    f'smart contracts {known_registries}',
                )

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run before the transport is started,
        #   to reject messages for closed/settled channels.
        self.alarm.register_callback(self._callback_new_block)
        with self.dispatch_events_lock:
            self.alarm.first_run(last_log_block_number)

        chain_state = views.state_from_raiden(self)
        self._initialize_transactions_queues(chain_state)
        self._initialize_whitelists(chain_state)
        self._initialize_payment_statuses(chain_state)
        # send messages in queue before starting transport,
        # this is necessary to avoid a race where, if the transport is started
        # before the messages are queued, actions triggered by it can cause new
        # messages to be enqueued before these older ones
        self._initialize_messages_queues(chain_state)

        # The transport must not ever be started before the alarm task's
        # `first_run()` has been, because it's this method which synchronizes the
        # node with the blockchain, including the channel's state (if the channel
        # is closed on-chain new messages must be rejected, which will not be the
        # case if the node is not synchronized)
        self.transport.start(
            raiden_service=self,
            message_handler=self.message_handler,
            prev_auth_data=chain_state.last_transport_authdata,
        )

        # First run has been called above!
        self.alarm.start()

        # exceptions on these subtasks should crash the app and bubble up
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck(chain_state)

        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet.get()  # re-raise if exception occurred

        log.debug('Raiden Service started', node=pex(self.address))
        super().start()
Exemple #15
0
    def send_transfer_and_wait(self, recipient, transfer, path, msg_timeout):
        """ Send `transfer` to `recipient` and wait for the response.

        Args:
            recipient (address): The address of the node that will receive the
                message.
            transfer: The transfer message.
            path: The current path that is being tried.
            msg_timeout: How long should we wait for a response from `recipient`.

        Returns:
            TransferTimeout: If the other end didn't respond
        """
        self.event = AsyncResult()
        self.raiden.send(recipient, transfer)

        # The event is set either when a relevant message is received or we
        # reach the timeout.
        #
        # The relevant messages are: CancelTransfer, TransferTimeout,
        # SecretRequest, or Secret
        msg = self.event.wait(msg_timeout)

        # Timed out
        if msg is None:
            log.error('TIMEOUT [{}]! recipient={} didnt respond path={}'.format(
                msg_timeout,
                pex(recipient),
                lpex(path),
            ))

            transfer_timeout = TransferTimeout(
                echo=transfer.hash,
                hashlock=transfer.lock.hashlock,
            )
            self.raiden.sign(transfer_timeout)
            return transfer_timeout

        log.debug(
            'HAVE EVENT {} {}'.format(self, msg),
            node=pex(self.raiden.address),
        )

        if isinstance(msg, CancelTransfer):
            assert msg.lock.hashlock == transfer.lock.hashlock
            assert msg.lock.amount == transfer.lock.amount
            assert msg.recipient == transfer.sender == self.raiden.address
            channel = self.assetmanager.channels[msg.sender]
            channel.register_transfer(msg)
            return msg
        elif isinstance(msg, TransferTimeout):
            assert msg.echo == transfer.hash
            return msg
            # send back StaleHashLock, we need new hashlock
        elif isinstance(msg, Secret):
            # done exit
            assert msg.hashlock == self.hashlock
            # channel = self.assetmanager.channels[msg.recipient]
            # channel.claim_locked(msg.secret)  # fixme this is also done by assetmanager
            return msg
        elif isinstance(msg, SecretRequest):
            # reveal secret
            log.info('SECRETREQUEST RECEIVED {}'.format(msg))
            assert msg.sender == self.target
            return msg

        raise NotImplementedError()
Exemple #16
0
    def send_transfer_and_wait(self, recipient, transfer, path, timeout):
        """ Send `transfer` to `recipient` and wait for the response.

        Args:
            recipient (address): The address of the node that will receive the
                message.
            transfer: The transfer message.
            path: The current path that is being tried.
            timeout: How long should we wait for a response from `recipient`.

        Returns:
            TransferTimeout: If the other end didn't respond
        """
        self.event = AsyncResult()
        self.raiden.send(recipient, transfer)

        # The event is set either when a relevant message is received or we
        # reach the timeout.
        #
        # The relevant messages are: CancelTransfer, TransferTimeout,
        # SecretRequest, or Secret
        msg = self.event.wait(timeout)

        # Timed out
        if msg is None:
            log.error(
                'TIMEOUT [{}]! recipient={} didnt respond path={}'.format(
                    timeout,
                    pex(recipient),
                    lpex(path),
                ))

            transfer_timeout = TransferTimeout(
                echo=transfer.hash,
                hashlock=transfer.lock.hashlock,
            )
            self.raiden.sign(transfer_timeout)
            return transfer_timeout

        log.debug(
            'HAVE EVENT {} {}'.format(self, msg),
            node=pex(self.raiden.address),
        )

        if isinstance(msg, CancelTransfer):
            assert msg.lock.hashlock == transfer.lock.hashlock
            assert msg.lock.amount == transfer.lock.amount
            assert msg.recipient == transfer.sender == self.raiden.address
            channel = self.assetmanager.channels[msg.sender]
            channel.register_transfer(msg)
            return msg
        elif isinstance(msg, TransferTimeout):
            assert msg.echo == transfer.hash
            return msg
            # send back StaleHashLock, we need new hashlock
        elif isinstance(msg, Secret):
            # done exit
            assert msg.hashlock == self.hashlock
            # channel = self.assetmanager.channels[msg.recipient]
            # channel.claim_locked(msg.secret)  # fixme this is also done by assetmanager
            return msg
        elif isinstance(msg, SecretRequest):
            # reveal secret
            log.info('SECRETREQUEST RECEIVED {}'.format(msg))
            assert msg.sender == self.target
            return msg

        raise NotImplementedError()
Exemple #17
0
    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        assert self.stop_event.ready(), f"Node already started. node:{self!r}"
        self.stop_event.clear()
        self.greenlets = list()

        self.ready_to_process_events = False  # set to False because of restarts

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked, f"Database not locked. node:{self!r}"

        # start the registration early to speed up the start
        if self.config["transport_type"] == "udp":
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config["transport"]["udp"]["external_ip"],
                self.config["transport"]["udp"]["external_port"],
            )

        self.maybe_upgrade_db()

        storage = sqlite.SerializedSQLiteStorage(
            database_path=self.database_path,
            serializer=serialize.JSONSerializer())
        storage.update_version()
        storage.log_run()
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier="latest",
        )

        if self.wal.state_manager.current_state is None:
            log.debug("No recoverable state available, creating inital state.",
                      node=pex(self.address))
            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = self.query_start_block
            last_log_block_hash = self.chain.client.blockhash_from_blocknumber(
                last_log_block_number)

            state_change = ActionInitChain(
                pseudo_random_generator=random.Random(),
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
                our_address=self.chain.node_address,
                chain_id=self.chain.network_id,
            )
            self.handle_and_track_state_change(state_change)

            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                transaction_hash=constants.EMPTY_HASH,
                payment_network=payment_network,
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
            )
            self.handle_and_track_state_change(state_change)
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(
                self.wal.state_manager.current_state)
            log.debug(
                "Restored state from WAL",
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(
                views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f"Token network address mismatch.\n"
                    f"Raiden is configured to use the smart contract "
                    f"{configured_registry}, which conflicts with the current known "
                    f"smart contracts {known_registries}")

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the latest confirmed from_block value,
        # otherwise blockchain logs can be lost.
        self.install_all_blockchain_filters(self.default_registry,
                                            self.default_secret_registry,
                                            last_log_block_number)

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run before the transport is started,
        #   to reject messages for closed/settled channels.
        self.alarm.register_callback(self._callback_new_block)
        self.alarm.first_run(last_log_block_number)

        chain_state = views.state_from_raiden(self)

        self._initialize_payment_statuses(chain_state)
        self._initialize_transactions_queues(chain_state)
        self._initialize_messages_queues(chain_state)
        self._initialize_whitelists(chain_state)
        self._initialize_monitoring_services_queue(chain_state)
        self._initialize_ready_to_processed_events()

        if self.config["transport_type"] == "udp":
            endpoint_registration_greenlet.get(
            )  # re-raise if exception occurred

        # Start the side-effects:
        # - React to blockchain events
        # - React to incoming messages
        # - Send pending transactions
        # - Send pending message
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)
        self._start_transport(chain_state)
        self._start_alarm_task()

        log.debug("Raiden Service started", node=pex(self.address))
        super().start()
Exemple #18
0
    def _run(self):  # pylint: disable=method-hidden

        for path, channel in self.get_best_routes():
            next_hop = path[1]

            mediated_transfer = channel.create_mediatedtransfer(
                self.initiator,
                self.target,
                self.fee,
                self.amount,
                # HOTFIX: you cannot know channel.settle_timeout beforehand,since path is not yet defined
                # FIXME: implement expiration adjustment in different task (e.g. 'InitMediatedTransferTask')
                self.expiration if self.expiration is not None else channel.settle_timeout - 1,
                self.hashlock,
            )
            self.raiden.sign(mediated_transfer)
            channel.register_transfer(mediated_transfer)

            log.debug('MEDIATED TRANSFER initiator={} {}'.format(
                pex(mediated_transfer.initiator),
                lpex(path),
            ))

            msg_timeout = self.raiden.config['msg_timeout']

            # timeout not dependent on expiration (canceltransfer/transfertimeout msgs),
            # but should be set shorter than the expiration
            msg = self.send_transfer_and_wait(next_hop, mediated_transfer, path, msg_timeout)

            log.debug('MEDIATED TRANSFER RETURNED {} {}'.format(
                pex(self.raiden.address),
                msg,
            ))

            result = self.check_path(msg, channel)

            # `next_hop` didn't get a response from any of it's, let's try the
            # next path
            if isinstance(result, TransferTimeout):
                continue

            # `next_hop` doesn't have any path with a valid channel to proceed,
            # try the next path
            if isinstance(result, CancelTransfer):
                continue

            # `check_path` failed, try next path
            if result is None:
                continue

            return self.on_completion(result)

        # No suitable path avaiable (e.g. insufficient distributable, no active node)
        # Send CancelTransfer to the originating node, this has the effect of
        # backtracking in the graph search of the raiden network.
        if self.originating_transfer:
            from_address = self.originating_transfer.sender
            from_transfer = self.originating_transfer
            from_channel = self.assetmanager.channels[from_address]

            log.debug('CANCEL MEDIATED TRANSFER from={} {}'.format(
                pex(from_address),
                pex(self.raiden.address),
            ))

            cancel_transfer = from_channel.create_canceltransfer_for(from_transfer)
            self.raiden.sign(cancel_transfer)
            from_channel.register_transfer(cancel_transfer)
            self.raiden.send(from_address, cancel_transfer)
        else:
            log.error('UNABLE TO COMPLETE MEDIATED TRANSFER target={} amount={}'.format(
                pex(self.target),
                self.amount,
            ))

        return self.on_completion(False)
Exemple #19
0
    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        if not self.stop_event.ready():
            raise RuntimeError(f'{self!r} already started')
        self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['transport']['udp']['external_ip'],
                self.config['transport']['udp']['external_port'],
            )

        storage = sqlite.SQLiteStorage(self.database_path, serialize.JSONSerializer())
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier='latest',
        )

        if self.wal.state_manager.current_state is None:
            log.debug(
                'No recoverable state available, created inital state',
                node=pex(self.address),
            )
            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = self.query_start_block

            state_change = ActionInitChain(
                random.Random(),
                last_log_block_number,
                self.chain.node_address,
                self.chain.network_id,
            )
            self.handle_state_change(state_change)

            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                constants.EMPTY_HASH,
                payment_network,
                last_log_block_number,
            )
            self.handle_state_change(state_change)
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(self.wal.state_manager.current_state)
            log.debug(
                'Restored state from WAL',
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f'Token network address mismatch.\n'
                    f'Raiden is configured to use the smart contract '
                    f'{configured_registry}, which conflicts with the current known '
                    f'smart contracts {known_registries}',
                )

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run before the transport is started,
        #   to reject messages for closed/settled channels.
        self.alarm.register_callback(self._callback_new_block)
        with self.dispatch_events_lock:
            self.alarm.first_run(last_log_block_number)

        chain_state = views.state_from_raiden(self)
        self._initialize_transactions_queues(chain_state)
        self._initialize_whitelists(chain_state)

        # send messages in queue before starting transport,
        # this is necessary to avoid a race where, if the transport is started
        # before the messages are queued, actions triggered by it can cause new
        # messages to be enqueued before these older ones
        self._initialize_messages_queues(chain_state)

        # The transport must not ever be started before the alarm task's
        # `first_run()` has been, because it's this method which synchronizes the
        # node with the blockchain, including the channel's state (if the channel
        # is closed on-chain new messages must be rejected, which will not be the
        # case if the node is not synchronized)
        self.transport.start(
            raiden_service=self,
            message_handler=self.message_handler,
            prev_auth_data=chain_state.last_transport_authdata,
        )

        # First run has been called above!
        self.alarm.start()

        # exceptions on these subtasks should crash the app and bubble up
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck(chain_state)

        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet.get()  # re-raise if exception occurred

        log.debug('Raiden Service started', node=pex(self.address))
        super().start()
Exemple #20
0
    def register_transfer_from_to(self, transfer, from_state, to_state):  # noqa pylint: disable=too-many-branches
        """ Validates and register a signed transfer, updating the channel's state accordingly.

        Note:
            The transfer must be register before it is sent, not on
            acknowledgement. That is necessary for to reasons:

            - Guarantee that the transfer is valid.
            - Avoiding sending a new transaction without funds.

        Raises:
            InsufficientBalance: If the transfer is negative or above the distributable amount.
            InvalidLocksRoot: If locksroot check fails.
            InvalidLockTime: If the transfer has expired.
            InvalidNonce: If the expected nonce does not match.
            InvalidSecret: If there is no lock registered for the given secret.
            ValueError: If there is an address mismatch (asset or node address).
        """
        if transfer.asset != self.asset_address:
            raise ValueError('Asset address mismatch')

        if transfer.recipient != to_state.address:
            raise ValueError('Unknow recipient')

        if transfer.sender != from_state.address:
            raise ValueError('Unsigned transfer')

        # nonce is changed only when a transfer is un/registered, if the test
        # fail either we are out of sync, a message out of order, or it's an
        # forged transfer
        if transfer.nonce < 1 or transfer.nonce != from_state.nonce:
            raise InvalidNonce(transfer)

        # if the locksroot is out-of-sync (because a transfer was created while
        # a Secret was in trafic) the balance _will_ be wrong, so first check
        # the locksroot and then the balance
        if isinstance(transfer, LockedTransfer):
            block_number = self.external_state.get_block_number()

            if to_state.balance_proof.is_pending(transfer.lock.hashlock):
                raise ValueError('hashlock is already registered')

            # As a receiver: Check that all locked transfers are registered in
            # the locksroot, if any hashlock is missing there is no way to
            # claim it while the channel is closing
            expected_locksroot = to_state.compute_merkleroot_with(
                transfer.lock)
            if expected_locksroot != transfer.locksroot:
                if log.isEnabledFor(logging.ERROR):
                    log.error(
                        'LOCKSROOT MISMATCH node:%s %s > %s lockhash:%s lockhashes:%s',
                        pex(self.our_state.address),
                        pex(from_state.address),
                        pex(to_state.address),
                        pex(sha3(transfer.lock.as_bytes)),
                        lpex(to_state.balance_proof.unclaimed_merkletree()),
                        expected_locksroot=pex(expected_locksroot),
                        received_locksroot=pex(transfer.locksroot),
                    )

                raise InvalidLocksRoot(transfer)

            # As a receiver: If the lock expiration is larger than the settling
            # time a secret could be revealed after the channel is settled and
            # we won't be able to claim the asset
            if not transfer.lock.expiration - block_number < self.settle_timeout:
                log.error(
                    "Transfer expiration doesn't allow for correct settlement.",
                    lock_expiration=transfer.lock.expiration,
                    current_block=block_number,
                    settle_timeout=self.settle_timeout,
                )

                raise ValueError(
                    "Transfer expiration doesn't allow for correct settlement."
                )

            if not transfer.lock.expiration - block_number > self.reveal_timeout:
                log.error(
                    'Expiration smaller than the minimum required.',
                    lock_expiration=transfer.lock.expiration,
                    current_block=block_number,
                    reveal_timeout=self.reveal_timeout,
                )

                raise ValueError(
                    'Expiration smaller than the minimum required.')

        # only check the balance if the locksroot matched
        if transfer.transferred_amount < from_state.transferred_amount:
            if log.isEnabledFor(logging.ERROR):
                log.error(
                    'NEGATIVE TRANSFER node:%s %s > %s %s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    transfer,
                )

            raise ValueError('Negative transfer')

        amount = transfer.transferred_amount - from_state.transferred_amount
        distributable = from_state.distributable(to_state)

        if amount > distributable:
            raise InsufficientBalance(transfer)

        if isinstance(transfer, LockedTransfer):
            if amount + transfer.lock.amount > distributable:
                raise InsufficientBalance(transfer)

        # all checks need to be done before the internal state of the channel
        # is changed, otherwise if a check fails and state was changed the
        # channel will be left trashed

        if isinstance(transfer, LockedTransfer):
            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'REGISTERED LOCK node:%s %s > %s currentlocksroot:%s lockhashes:%s',
                    pex(self.our_state.address),
                    pex(from_state.address),
                    pex(to_state.address),
                    pex(to_state.balance_proof.merkleroot_for_unclaimed()),
                    lpex(to_state.balance_proof.unclaimed_merkletree()),
                    lock_amount=transfer.lock.amount,
                    lock_expiration=transfer.lock.expiration,
                    lock_hashlock=pex(transfer.lock.hashlock),
                    lockhash=pex(sha3(transfer.lock.as_bytes)),
                )

            to_state.register_locked_transfer(transfer)

            # register this channel as waiting for the secret (the secret can
            # be revealed through a message or an blockchain log)
            self.external_state.register_channel_for_hashlock(
                self,
                transfer.lock.hashlock,
            )

        if isinstance(transfer, DirectTransfer):
            to_state.register_direct_transfer(transfer)

        from_state.transferred_amount = transfer.transferred_amount
        from_state.nonce += 1

        if isinstance(transfer, DirectTransfer):
            # if we are the recipient, spawn callback for incoming transfers
            if transfer.recipient == self.our_state.address:
                for callback in self.on_withdrawable_callbacks:
                    gevent.spawn(
                        callback,
                        transfer.asset,
                        transfer.recipient,
                        transfer.sender,  # 'initiator' is sender here
                        transfer.transferred_amount,
                        None  # no hashlock in DirectTransfer
                    )

            # if we are the sender, call the 'success' callback
            elif from_state.address == self.our_state.address:
                callbacks_to_remove = list()
                for callback in self.on_task_completed_callbacks:
                    result = callback(
                        task=None,
                        success=True)  # XXX maybe use gevent.spawn()

                    if result is True:
                        callbacks_to_remove.append(callback)

                for callback in callbacks_to_remove:
                    self.on_task_completed_callbacks.remove(callback)

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REGISTERED TRANSFER node:%s %s > %s '
                'transfer:%s transferred_amount:%s nonce:%s '
                'current_locksroot:%s',
                pex(self.our_state.address),
                pex(from_state.address),
                pex(to_state.address),
                repr(transfer),
                from_state.transferred_amount,
                from_state.nonce,
                pex(to_state.balance_proof.merkleroot_for_unclaimed()),
            )
Exemple #21
0
    def _run(self):  # pylint: disable=method-hidden,too-many-locals
        fee = 0
        raiden = self.raiden

        from_mediated_transfer = self.from_mediated_transfer
        hashlock = from_mediated_transfer.lock.hashlock

        from_asset = from_mediated_transfer.asset
        to_asset = self.to_asset
        to_amount = self.to_amount

        to_assetmanager = raiden.get_manager_by_asset_address(to_asset)
        from_assetmanager = raiden.get_manager_by_asset_address(from_asset)
        from_transfermanager = from_assetmanager.transfermanager

        from_channel = from_assetmanager.get_channel_by_partner_address(
            from_mediated_transfer.sender, )

        from_transfermanager.register_task_for_hashlock(self, hashlock)
        from_assetmanager.register_channel_for_hashlock(from_channel, hashlock)

        lock_expiration = from_mediated_transfer.lock.expiration - raiden.config[
            'reveal_timeout']
        lock_timeout = lock_expiration - raiden.chain.block_number()

        to_routes = to_assetmanager.get_best_routes(
            from_mediated_transfer.lock.amount,
            from_mediated_transfer.initiator,  # route back to the initiator
            lock_timeout,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'EXCHANGE TRANSFER %s -> %s msghash:%s hashlock:%s',
                pex(from_mediated_transfer.target),
                pex(from_mediated_transfer.initiator),
                pex(from_mediated_transfer.hash),
                pex(hashlock),
            )

        secret_request = SecretRequest(
            from_mediated_transfer.identifier,
            from_mediated_transfer.lock.hashlock,
            from_mediated_transfer.lock.amount,
        )
        raiden.sign(secret_request)
        raiden.send_async(from_mediated_transfer.initiator, secret_request)

        for path, to_channel in to_routes:
            to_next_hop = path[1]

            to_mediated_transfer = to_channel.create_mediatedtransfer(
                raiden.address,  # this node is the new initiator
                from_mediated_transfer.
                initiator,  # the initiator is the target for the to_asset
                fee,
                to_amount,
                lock_expiration,
                hashlock,  # use the original hashlock
            )
            raiden.sign(to_mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(from_mediated_transfer.lock.hashlock),
                )

            # Using assetmanager to register the interest because it outlives
            # this task, the secret handling will happen only _once_
            to_assetmanager.register_channel_for_hashlock(
                to_channel,
                hashlock,
            )
            to_channel.register_transfer(to_mediated_transfer)

            response = self.send_and_wait_valid(raiden, to_mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'EXCHANGE TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(hashlock),
                )

            # only refunds for `from_asset` must be considered (check send_and_wait_valid)
            if isinstance(response, RefundTransfer):
                if response.lock.amount != to_mediated_transfer.amount:
                    log.info(
                        'Partner %s sent an invalid refund message with an invalid amount',
                        pex(to_next_hop),
                    )
                    timeout_message = from_channel.create_timeouttransfer_for(
                        from_mediated_transfer)
                    raiden.send_async(from_mediated_transfer.sender,
                                      timeout_message)
                    self.transfermanager.on_hashlock_result(hashlock, False)
                    return
                else:
                    to_channel.register_transfer(response)

            elif isinstance(response, Secret):
                # this node is receiving the from_asset and sending the
                # to_asset, meaning that it can claim the to_asset but it needs
                # a Secret message to claim the from_asset
                to_assetmanager.handle_secretmessage(response)
                from_assetmanager.handle_secretmessage(response)

                self._wait_for_unlock_or_close(
                    raiden,
                    from_assetmanager,
                    from_channel,
                    from_mediated_transfer,
                )
Exemple #22
0
    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        if not self.stop_event.ready():
            raise RuntimeError(f'{self!r} already started')
        self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['transport']['udp']['external_ip'],
                self.config['transport']['udp']['external_port'],
            )

        storage = sqlite.SQLiteStorage(self.database_path,
                                       serialize.JSONSerializer())
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier='latest',
        )

        if self.wal.state_manager.current_state is None:
            log.debug(
                'No recoverable state available, created inital state',
                node=pex(self.address),
            )
            block_number = self.chain.block_number()

            state_change = ActionInitChain(
                random.Random(),
                block_number,
                self.chain.node_address,
                self.chain.network_id,
            )
            self.wal.log_and_dispatch(state_change)
            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                constants.EMPTY_HASH,
                payment_network,
            )
            self.handle_state_change(state_change)

            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = 0
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(
                self.wal.state_manager.current_state)
            log.debug(
                'Restored state from WAL',
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(
                views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f'Token network address mismatch.\n'
                    f'Raiden is configured to use the smart contract '
                    f'{configured_registry}, which conflicts with the current known '
                    f'smart contracts {known_registries}', )

        # Clear ref cache & disable caching
        serialize.RaidenJSONDecoder.ref_cache.clear()
        serialize.RaidenJSONDecoder.cache_object_references = False

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run  before the transport is started,
        #  to avoid rejecting messages for unknown channels.
        self.alarm.register_callback(self._callback_new_block)

        # alarm.first_run may process some new channel, which would start_health_check_for
        # a partner, that's why transport needs to be already started at this point
        self.transport.start(self)

        self.alarm.first_run()

        chain_state = views.state_from_raiden(self)
        # Dispatch pending transactions
        pending_transactions = views.get_pending_transactions(chain_state, )
        log.debug(
            'Processing pending transactions',
            num_pending_transactions=len(pending_transactions),
            node=pex(self.address),
        )
        with self.dispatch_events_lock:
            for transaction in pending_transactions:
                try:
                    self.raiden_event_handler.on_raiden_event(
                        self, transaction)
                except RaidenRecoverableError as e:
                    log.error(str(e))
                except RaidenUnrecoverableError as e:
                    if self.config['network_type'] == NetworkType.MAIN:
                        if isinstance(e, InvalidDBData):
                            raise
                        log.error(str(e))
                    else:
                        raise

        self.alarm.start()

        # after transport and alarm is started, send queued messages
        events_queues = views.get_all_messagequeues(chain_state)

        for queue_identifier, event_queue in events_queues.items():
            self.start_health_check_for(queue_identifier.recipient)

            # repopulate identifier_to_results for pending transfers
            for event in event_queue:
                if type(event) == SendDirectTransfer:
                    self.identifier_to_results[
                        event.payment_identifier] = AsyncResult()

                message = message_from_sendevent(event, self.address)
                self.sign(message)
                self.transport.send_async(queue_identifier, message)

        # exceptions on these subtasks should crash the app and bubble up
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck()

        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet.get(
            )  # re-raise if exception occurred

        super().start()
Exemple #23
0
    def _run(self):  # noqa pylint: disable=method-hidden,too-many-locals
        raiden = self.raiden
        amount = self.amount
        identifier = self.identifier
        target = self.target

        node_address = raiden.address
        assetmanager = raiden.get_manager_by_asset_address(self.asset_address)
        transfermanager = assetmanager.transfermanager

        fee = 0
        # there are no guarantees that the next_hop will follow the same route
        routes = assetmanager.get_best_routes(
            amount,
            target,
            lock_timeout=None,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'START MEDIATED TRANSFER initiator:%s target:%s',
                pex(node_address),
                pex(target),
            )

        for path, forward_channel in routes:
            # never reuse the last secret, discard it to avoid losing asset
            secret = sha3(hex(random.getrandbits(256)))
            hashlock = sha3(secret)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'START MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(hashlock),
                )

            transfermanager.register_task_for_hashlock(self, hashlock)
            assetmanager.register_channel_for_hashlock(forward_channel,
                                                       hashlock)

            lock_timeout = forward_channel.settle_timeout - forward_channel.reveal_timeout
            lock_expiration = raiden.chain.block_number() + lock_timeout

            mediated_transfer = forward_channel.create_mediatedtransfer(
                node_address,
                target,
                fee,
                amount,
                identifier,
                lock_expiration,
                hashlock,
            )
            raiden.sign(mediated_transfer)
            forward_channel.register_transfer(mediated_transfer)

            for response in self.send_and_iter_valid(raiden, path,
                                                     mediated_transfer):
                valid_secretrequest = (isinstance(response, SecretRequest)
                                       and response.amount == amount
                                       and response.hashlock == hashlock
                                       and response.identifier == identifier)

                if valid_secretrequest:
                    # This node must reveal the Secret starting with the
                    # end-of-chain, the `next_hop` can not be trusted to reveal the
                    # secret to the other nodes.
                    revealsecret_message = RevealSecret(secret)
                    raiden.sign(revealsecret_message)

                    # we cannot wait for ever since the `target` might
                    # intentionally _not_ send the Ack, blocking us from
                    # unlocking the asset.
                    wait = (ESTIMATED_BLOCK_TIME * lock_timeout / .6)

                    raiden.send_and_wait(target, revealsecret_message, wait)

                    # target has acknowledged the RevealSecret, we can update
                    # the chain in the forward direction
                    assetmanager.handle_secret(
                        identifier,
                        secret,
                    )

                    # call the callbacks and unregister the task
                    transfermanager.on_hashlock_result(hashlock, True)

                    # the transfer is done when the lock is unlocked and the Secret
                    # message is sent (doesn't imply the other nodes in the chain
                    # have unlocked/withdrawn)
                    self.done_result.set(True)

                    return

                # someone down the line timed out / couldn't proceed, try next
                # path, stop listening for messages for the current hashlock
                else:
                    # the initiator can unregister right away because it knowns
                    # no one else can reveal the secret
                    transfermanager.on_hashlock_result(hashlock, False)
                    del assetmanager.hashlock_channel[hashlock]
                    break

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'START MEDIATED TRANSFER FAILED initiator:%s target:%s',
                pex(node_address),
                pex(self.target),
            )

        # all routes failed, consider:
        # - if the target is a good node to have a channel:
        #   - deposit/reopen/open a channel with target
        # - if the target has a direct channel with good nodes and there is
        #   sufficient funds to complete the transfer
        #   - open the required channels with these nodes
        self.done_result.set(False)
    def _run(self):  # pylint: disable=method-hidden,too-many-locals
        fee = 0
        raiden = self.raiden
        tokenswap = self.tokenswap

        # this is the MediatedTransfer that wil pay the maker's half of the
        # swap, not necessarily from him
        maker_paying_transfer = self.from_mediated_transfer

        # this is the address of the node that the taker actually has a channel
        # with (might or might not be the maker)
        maker_payer_hop = maker_paying_transfer.sender

        assert tokenswap.identifier == maker_paying_transfer.identifier
        assert tokenswap.from_token == maker_paying_transfer.token
        assert tokenswap.from_amount == maker_paying_transfer.lock.amount
        assert tokenswap.from_nodeaddress == maker_paying_transfer.initiator

        maker_receiving_token = tokenswap.to_token
        to_amount = tokenswap.to_amount

        identifier = maker_paying_transfer.identifier
        hashlock = maker_paying_transfer.lock.hashlock
        maker_address = maker_paying_transfer.initiator

        taker_receiving_token = maker_paying_transfer.token
        taker_paying_token = maker_receiving_token

        from_graph = raiden.token_to_channelgraph[taker_receiving_token]
        from_channel = from_graph.partneraddress_to_channel[maker_payer_hop]

        to_graph = raiden.token_to_channelgraph[maker_receiving_token]

        # update the channel's distributable and merkle tree
        from_channel.register_transfer(
            raiden.get_block_number(),
            maker_paying_transfer,
        )

        # register the task to receive Refund/Secrect/RevealSecret messages
        raiden.greenlet_task_dispatcher.register_task(self, hashlock)
        raiden.register_channel_for_hashlock(taker_receiving_token, from_channel, hashlock)

        # send to the maker a secret request informing how much the taker will
        # be _paid_, this is used to inform the maker that his part of the
        # mediated transfer is okay
        secret_request = SecretRequest(
            identifier,
            maker_paying_transfer.lock.hashlock,
            maker_paying_transfer.lock.amount,
        )
        raiden.sign(secret_request)
        raiden.send_async(maker_address, secret_request)

        lock_expiration = maker_paying_transfer.lock.expiration - raiden.config['reveal_timeout']

        # Note: taker may only try different routes if a RefundTransfer is
        # received, because the maker is the node controlling the secret
        available_routes = get_best_routes(
            to_graph,
            raiden.protocol.nodeaddresses_networkstatuses,
            raiden.address,
            maker_address,
            maker_paying_transfer.lock.amount,
            previous_address=None,
        )

        if not available_routes:
            if log.isEnabledFor(logging.DEBUG):
                node_address = raiden.address
                log.debug(
                    'TAKER TOKEN SWAP FAILED, NO ROUTES',
                    from_=pex(node_address),
                    to=pex(maker_address),
                )

            return

        first_transfer = None
        for route in available_routes:
            taker_paying_channel = to_graph.get_channel_by_contract_address(
                route.channel_address,
            )
            taker_paying_hop = route.node_address

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'TAKER TOKEN SWAP',
                    from_=pex(maker_paying_transfer.target),
                    to=pex(maker_address),
                    msghash=pex(maker_paying_transfer.hash),
                    hashlock=pex(hashlock),
                )

            # make a paying MediatedTransfer with same hashlock/identifier and the
            # taker's paying token/amount
            taker_paying_transfer = taker_paying_channel.create_mediatedtransfer(
                raiden.address,
                maker_address,
                fee,
                to_amount,
                identifier,
                lock_expiration,
                hashlock,
            )
            raiden.sign(taker_paying_transfer)
            taker_paying_channel.register_transfer(
                raiden.get_block_number(),
                taker_paying_transfer,
            )

            if not first_transfer:
                first_transfer = taker_paying_transfer

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'EXCHANGE TRANSFER NEW PATH',
                    path=lpex(taker_paying_hop),
                    hashlock=pex(hashlock),
                )

            # register the task to receive Refund/Secrect/RevealSecret messages
            raiden.register_channel_for_hashlock(
                maker_receiving_token,
                taker_paying_channel,
                hashlock,
            )

            response, secret = self.send_and_wait_valid(
                raiden,
                taker_paying_transfer,
                maker_payer_hop,
            )

            # only refunds for `maker_receiving_token` must be considered
            # (check send_and_wait_valid)
            if isinstance(response, RefundTransfer):
                if response.lock.amount != taker_paying_transfer.amount:
                    log.info(
                        'Partner %s sent an invalid refund message with an invalid amount',
                        pex(taker_paying_hop),
                    )
                    raiden.greenlet_task_dispatcher.unregister_task(self, hashlock, False)
                    return
                else:
                    taker_paying_channel.register_transfer(
                        raiden.get_block_number(),
                        response,
                    )

            elif isinstance(response, RevealSecret):
                # the secret was registered by the message handler

                # wait for the taker_paying_hop to reveal the secret prior to
                # unlocking locally
                if response.sender != taker_paying_hop:
                    response = self.wait_reveal_secret(
                        raiden,
                        taker_paying_hop,
                        taker_paying_transfer.lock.expiration,
                    )

                # unlock and send the Secret message
                raiden.handle_secret(
                    identifier,
                    taker_paying_token,
                    response.secret,
                    None,
                    hashlock,
                )

                # if the secret arrived early, withdraw it, otherwise send the
                # RevealSecret forward in the maker-path
                if secret:
                    raiden.handle_secret(
                        identifier,
                        taker_receiving_token,
                        response.secret,
                        secret,
                        hashlock,
                    )

                # wait for the withdraw in case it did not happen yet
                self._wait_for_unlock_or_close(
                    raiden,
                    from_graph,
                    from_channel,
                    maker_paying_transfer,
                )

                return

            # the lock expired
            else:
                if log.isEnabledFor(logging.DEBUG):
                    node_address = raiden.address
                    log.debug(
                        'TAKER TOKEN SWAP FAILED',
                        from_=pex(node_address),
                        to=pex(maker_address),
                    )

                self.async_result.set(False)
                return

        # no route is available, wait for the sent mediated transfer to expire
        self._wait_expiration(raiden, first_transfer)

        if log.isEnabledFor(logging.DEBUG):
            node_address = raiden.address
            log.debug(
                'TAKER TOKEN SWAP FAILED',
                from_=pex(node_address),
                to=pex(maker_address),
            )

        self.async_result.set(False)
Exemple #25
0
    def _run(self):  # noqa
        # pylint: disable=method-hidden,too-many-locals,too-many-branches,too-many-statements
        raiden = self.raiden
        fee = self.fee
        originating_transfer = self.originating_transfer

        assetmanager = raiden.get_manager_by_asset_address(self.asset_address)
        transfermanager = assetmanager.transfermanager
        from_address = originating_transfer.sender
        originating_channel = assetmanager.partneraddress_channel[from_address]
        hashlock = originating_transfer.lock.hashlock

        transfermanager.register_task_for_hashlock(self, hashlock)
        assetmanager.register_channel_for_hashlock(originating_channel,
                                                   hashlock)

        # there are no guarantees that the next_hop will follow the same route
        routes = assetmanager.get_best_routes(
            originating_transfer.lock.amount,
            originating_transfer.target,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'MEDIATED TRANSFER initiator:%s node:%s target:%s',
                pex(originating_transfer.initiator),
                pex(raiden.address),
                pex(originating_transfer.target),
            )

        maximum_expiration = (
            originating_channel.settle_timeout + raiden.chain.block_number() -
            2  # decrement as a safety measure to avoid limit errors
        )

        # Ignore locks that expire after settle_timeout
        if originating_transfer.lock.expiration > maximum_expiration:
            if log.isEnabledFor(logging.ERROR):
                log.debug(
                    'lock_expiration is too large, ignore the mediated transfer',
                    initiator=pex(originating_transfer.initiator),
                    node=pex(self.address),
                    target=pex(originating_transfer.target),
                )

            # Notes:
            # - The node didn't send a transfer forward, so it can not lose
            #   asset.
            # - It's quiting early, so it wont claim the lock if the secret is
            #   revealed.
            # - The previous_node knowns the settle_timeout because this value
            #   is in the smart contract.
            # - It's not sending a RefundTransfer to the previous_node, so it
            #   will force a retry with a new path/different hashlock, this
            #   could make the bad behaving node lose it's fees but it will
            #   also increase latency.
            return

        for path, forward_channel in routes:
            current_block_number = raiden.chain.block_number()

            # Dont forward the mediated transfer to the next_hop if we cannot
            # decrease the expiration by `reveal_timeout`, this is time
            # required to learn the secret through the blockchain that needs to
            # consider DoS attacks.
            lock_timeout = originating_transfer.lock.expiration - current_block_number
            if lock_timeout < forward_channel.reveal_timeout:
                if log.isEnabledFor(logging.INFO):
                    log.info(
                        'transfer.lock_expiration is smaller than'
                        ' reveal_timeout, channel/path cannot be used',
                        lock_timeout=originating_transfer.lock.expiration,
                        reveal_timeout=forward_channel.reveal_timeout,
                        settle_timeout=forward_channel.settle_timeout,
                        nodeid=pex(path[0]),
                        partner=pex(path[1]),
                    )
                continue

            new_lock_timeout = lock_timeout - forward_channel.reveal_timeout

            # Our partner won't accept a locked transfer that can expire after
            # the settlement period, otherwise the secret could be revealed
            # after channel is settled and asset would be lost, in that case
            # decrease the expiration by an amount larger than reveal_timeout.
            if new_lock_timeout > forward_channel.settle_timeout:
                new_lock_timeout = forward_channel.settle_timeout - 2  # arbitrary decrement

                if log.isEnabledFor(logging.DEBUG):
                    log.debug(
                        'lock_expiration would be too large, decrement more so'
                        ' that the channel/path can be used',
                        lock_timeout=lock_timeout,
                        new_lock_timeout=new_lock_timeout,
                        nodeid=pex(path[0]),
                        partner=pex(path[1]),
                    )

            new_lock_expiration = current_block_number + new_lock_timeout
            mediated_transfer = forward_channel.create_mediatedtransfer(
                originating_transfer.initiator,
                originating_transfer.target,
                fee,
                originating_transfer.lock.amount,
                originating_transfer.identifier,
                new_lock_expiration,
                hashlock,
            )
            raiden.sign(mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(hashlock),
                )

            assetmanager.register_channel_for_hashlock(
                forward_channel,
                hashlock,
            )
            forward_channel.register_transfer(mediated_transfer)

            for response in self.send_and_iter_valid(raiden, path,
                                                     mediated_transfer):
                valid_refund = (isinstance(response, RefundTransfer)
                                and response.lock.amount
                                == originating_transfer.lock.amount)

                if isinstance(response, RevealSecret):
                    assetmanager.handle_secret(
                        originating_transfer.identifier,
                        response.secret,
                    )

                    self._wait_for_unlock_or_close(
                        raiden,
                        assetmanager,
                        originating_channel,
                        originating_transfer,
                    )

                elif isinstance(response, Secret):
                    assetmanager.handle_secretmessage(response)

                    # Secret might be from a different node, wait for the
                    # update from `from_address`
                    self._wait_for_unlock_or_close(
                        raiden,
                        assetmanager,
                        originating_channel,
                        originating_transfer,
                    )

                    transfermanager.on_hashlock_result(hashlock, True)
                    return

                elif valid_refund:
                    forward_channel.register_transfer(response)
                    break

                else:
                    timeout_message = originating_channel.create_timeouttransfer_for(
                        originating_transfer, )
                    raiden.send_async(
                        originating_transfer.sender,
                        timeout_message,
                    )

                    self._wait_expiration(
                        raiden,
                        originating_transfer,
                    )
                    transfermanager.on_hashlock_result(hashlock, False)

                    return

        # No suitable path avaiable (e.g. insufficient distributable, no active node)
        # Send RefundTransfer to the originating node, this has the effect of
        # backtracking in the graph search of the raiden network.

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REFUND MEDIATED TRANSFER from=%s node:%s hashlock:%s',
                pex(from_address),
                pex(raiden.address),
                pex(hashlock),
            )

        refund_transfer = originating_channel.create_refundtransfer_for(
            originating_transfer, )
        raiden.sign(refund_transfer)

        originating_channel.register_transfer(refund_transfer)
        raiden.send_async(from_address, refund_transfer)

        self._wait_expiration(
            raiden,
            originating_transfer,
        )
        transfermanager.on_hashlock_result(hashlock, False)
Exemple #26
0
    def _run(self):  # pylint: disable=method-hidden,too-many-locals
        amount = self.amount
        target = self.target
        raiden = self.transfermanager.assetmanager.raiden

        fee = 0
        # there are no guarantees that the next_hop will follow the same route
        routes = self.transfermanager.assetmanager.get_best_routes(
            amount,
            target,
            lock_timeout=None,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'START MEDIATED TRANSFER initiator:%s target:%s',
                pex(self.address),
                pex(self.target),
            )

        for path, forward_channel in routes:
            # try a new secret
            secret = sha3(hex(random.getrandbits(256)))
            hashlock = sha3(secret)

            next_hop = path[1]

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'START MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(hashlock),
                )

            self.transfermanager.register_task_for_hashlock(self, hashlock)

            lock_expiration = (raiden.chain.block_number() +
                               forward_channel.settle_timeout -
                               raiden.config['reveal_timeout'])

            mediated_transfer = forward_channel.create_mediatedtransfer(
                raiden.address,
                target,
                fee,
                amount,
                lock_expiration,
                hashlock,
            )
            raiden.sign(mediated_transfer)
            forward_channel.register_transfer(mediated_transfer)

            response = self.send_and_wait_valid(raiden, path,
                                                mediated_transfer)

            # `next_hop` timedout
            if response is None:
                self.transfermanager.on_hashlock_result(hashlock, False)

            # someone down the line timedout / couldn't proceed
            elif isinstance(response, (RefundTransfer, TransferTimeout)):
                self.transfermanager.on_hashlock_result(hashlock, False)

            # `target` received the MediatedTransfer
            elif response.sender == target and isinstance(
                    response, SecretRequest):
                secret_message = Secret(secret)
                raiden.sign(secret_message)
                raiden.send_async(target, secret_message)

                # register the secret now and just incur with the additional
                # overhead of retrying until the `next_hop` receives the secret
                # forward_channel.register_secret(secret)

                # wait until `next_hop` received the secret to syncronize our
                # state (otherwise we can send a new transfer with an invalid
                # locksroot while the secret is in transit that will incur into
                # additional retry/timeout latency)
                next_hop = path[1]
                while True:
                    response = self.response_message.wait()
                    # critical write section
                    self.response_message = AsyncResult()
                    # /critical write section
                    if isinstance(response,
                                  Secret) and response.sender == next_hop:
                        # critical read/write section
                        # The channel and it's queue must be locked, a transfer
                        # must not be created while we update the balance_proof.
                        forward_channel.claim_lock(secret)
                        raiden.send_async(next_hop, secret_message)
                        # /critical write section

                        self.transfermanager.on_hashlock_result(hashlock, True)
                        self.done_result.set(True)

                        return

                    log.error(
                        'Invalid message ignoring. %s',
                        repr(response),
                    )
            else:
                log.error(
                    'Unexpected response %s',
                    repr(response),
                )
                self.transfermanager.on_hashlock_result(hashlock, False)

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'START MEDIATED TRANSFER FAILED initiator:%s target:%s',
                pex(self.address),
                pex(self.target),
            )

        self.done_result.set(False)  # all paths failed
    def _run(self):  # pylint: disable=method-hidden,too-many-locals
        fee = 0
        raiden = self.raiden
        tokenswap = self.tokenswap

        # this is the MediatedTransfer that wil pay the maker's half of the
        # swap, not necessarily from him
        maker_paying_transfer = self.from_mediated_transfer

        # this is the address of the node that the taker actually has a channel
        # with (might or might not be the maker)
        maker_payer_hop = maker_paying_transfer.sender

        assert tokenswap.identifier == maker_paying_transfer.identifier
        assert tokenswap.from_token == maker_paying_transfer.token
        assert tokenswap.from_amount == maker_paying_transfer.lock.amount
        assert tokenswap.from_nodeaddress == maker_paying_transfer.initiator

        maker_receiving_token = tokenswap.to_token
        to_amount = tokenswap.to_amount

        identifier = maker_paying_transfer.identifier
        hashlock = maker_paying_transfer.lock.hashlock
        maker_address = maker_paying_transfer.initiator

        taker_receiving_token = maker_paying_transfer.token
        taker_paying_token = maker_receiving_token

        from_graph = raiden.token_to_channelgraph[taker_receiving_token]
        from_channel = from_graph.partneraddress_to_channel[maker_payer_hop]

        to_graph = raiden.token_to_channelgraph[maker_receiving_token]

        # update the channel's distributable and merkle tree
        from_channel.register_transfer(
            raiden.get_block_number(),
            maker_paying_transfer,
        )

        # register the task to receive Refund/Secrect/RevealSecret messages
        raiden.greenlet_task_dispatcher.register_task(self, hashlock)
        raiden.register_channel_for_hashlock(taker_receiving_token, from_channel, hashlock)

        # send to the maker a secret request informing how much the taker will
        # be _paid_, this is used to inform the maker that his part of the
        # mediated transfer is okay
        secret_request = SecretRequest(
            identifier,
            maker_paying_transfer.lock.hashlock,
            maker_paying_transfer.lock.amount,
        )
        raiden.sign(secret_request)
        raiden.send_async(maker_address, secret_request)

        lock_expiration = maker_paying_transfer.lock.expiration - raiden.config['reveal_timeout']

        # Note: taker may only try different routes if a RefundTransfer is
        # received, because the maker is the node controlling the secret
        available_routes = get_best_routes(
            to_graph,
            raiden.protocol.nodeaddresses_networkstatuses,
            raiden.address,
            maker_address,
            maker_paying_transfer.lock.amount,
            previous_address=None,
        )

        if not available_routes:
            if log.isEnabledFor(logging.DEBUG):
                node_address = raiden.address
                log.debug(
                    'TAKER TOKEN SWAP FAILED, NO ROUTES',
                    from_=pex(node_address),
                    to=pex(maker_address),
                )

            return

        first_transfer = None
        for route in available_routes:
            taker_paying_channel = to_graph.get_channel_by_contract_address(
                route.channel_address,
            )
            taker_paying_hop = route.node_address

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'TAKER TOKEN SWAP',
                    from_=pex(maker_paying_transfer.target),
                    to=pex(maker_address),
                    msghash=pex(maker_paying_transfer.hash),
                    hashlock=pex(hashlock),
                )

            # make a paying MediatedTransfer with same hashlock/identifier and the
            # taker's paying token/amount
            taker_paying_transfer = taker_paying_channel.create_mediatedtransfer(
                raiden.address,
                maker_address,
                fee,
                to_amount,
                identifier,
                lock_expiration,
                hashlock,
            )
            raiden.sign(taker_paying_transfer)
            taker_paying_channel.register_transfer(
                raiden.get_block_number(),
                taker_paying_transfer,
            )

            if not first_transfer:
                first_transfer = taker_paying_transfer

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'EXCHANGE TRANSFER NEW PATH',
                    path=lpex(str(t).encode() for t in taker_paying_hop),
                    hashlock=pex(hashlock),
                )

            # register the task to receive Refund/Secrect/RevealSecret messages
            raiden.register_channel_for_hashlock(
                maker_receiving_token,
                taker_paying_channel,
                hashlock,
            )

            response, secret = self.send_and_wait_valid(
                raiden,
                taker_paying_transfer,
                maker_payer_hop,
            )

            # only refunds for `maker_receiving_token` must be considered
            # (check send_and_wait_valid)
            if isinstance(response, RefundTransfer):
                if response.lock.amount != taker_paying_transfer.amount:
                    log.info(
                        'Partner %s sent an invalid refund message with an invalid amount',
                        pex(taker_paying_hop),
                    )
                    raiden.greenlet_task_dispatcher.unregister_task(self, hashlock, False)
                    return
                else:
                    taker_paying_channel.register_transfer(
                        raiden.get_block_number(),
                        response,
                    )

            elif isinstance(response, RevealSecret):
                # the secret was registered by the message handler

                # wait for the taker_paying_hop to reveal the secret prior to
                # unlocking locally
                if response.sender != taker_paying_hop:
                    response = self.wait_reveal_secret(
                        raiden,
                        taker_paying_hop,
                        taker_paying_transfer.lock.expiration,
                    )

                # unlock and send the Secret message
                raiden.handle_secret(
                    identifier,
                    taker_paying_token,
                    response.secret,
                    None,
                    hashlock,
                )

                # if the secret arrived early, withdraw it, otherwise send the
                # RevealSecret forward in the maker-path
                if secret:
                    raiden.handle_secret(
                        identifier,
                        taker_receiving_token,
                        response.secret,
                        secret,
                        hashlock,
                    )

                # wait for the withdraw in case it did not happen yet
                self._wait_for_unlock_or_close(
                    raiden,
                    from_graph,
                    from_channel,
                    maker_paying_transfer,
                )

                return

            # the lock expired
            else:
                if log.isEnabledFor(logging.DEBUG):
                    node_address = raiden.address
                    log.debug(
                        'TAKER TOKEN SWAP FAILED',
                        from_=pex(node_address),
                        to=pex(maker_address),
                    )

                return

        # no route is available, wait for the sent mediated transfer to expire
        self._wait_expiration(raiden, first_transfer)

        if log.isEnabledFor(logging.DEBUG):
            node_address = raiden.address
            log.debug(
                'TAKER TOKEN SWAP FAILED',
                from_=pex(node_address),
                to=pex(maker_address),
            )
Exemple #28
0
    def register_transfer_from_to(self, block_number, transfer, from_state,
                                  to_state):  # noqa pylint: disable=too-many-branches,too-many-statements
        """ Validates and register a signed transfer, updating the channel's state accordingly.

        Note:
            The transfer must be registered before it is sent, not on
            acknowledgement. That is necessary for two reasons:

            - Guarantee that the transfer is valid.
            - Avoid sending a new transaction without funds.

        Raises:
            InsufficientBalance: If the transfer is negative or above the distributable amount.
            InvalidLocksRoot: If locksroot check fails.
            InvalidNonce: If the expected nonce does not match.
            ValueError: If there is an address mismatch (token or node address).
        """
        if transfer.channel != self.channel_address:
            raise ValueError('Channel address mismatch')

        if transfer.sender != from_state.address:
            raise ValueError('Unsigned transfer')

        # nonce is changed only when a transfer is un/registered, if the test
        # fails either we are out of sync, a message out of order, or it's a
        # forged transfer
        is_invalid_nonce = (transfer.nonce < 1
                            or (from_state.nonce is not None
                                and transfer.nonce != from_state.nonce + 1))
        if is_invalid_nonce:
            # this may occur on normal operation
            if log.isEnabledFor(logging.INFO):
                log.info(
                    'INVALID NONCE',
                    node=pex(self.our_address),
                    from_=pex(transfer.sender),
                    to=pex(to_state.address),
                    expected_nonce=from_state.nonce,
                    nonce=transfer.nonce,
                )
            raise InvalidNonce(transfer)

        # if the locksroot is out-of-sync (because a transfer was created while
        # a Secret was in traffic) the balance _will_ be wrong, so first check
        # the locksroot and then the balance
        if isinstance(transfer, LockedTransfer):
            if from_state.is_known(transfer.lock.hashlock):
                # this may occur on normal operation
                if log.isEnabledFor(logging.INFO):
                    lockhashes = list(
                        from_state.hashlocks_to_unclaimedlocks.values())
                    lockhashes.extend(
                        from_state.hashlocks_to_pendinglocks.values())
                    log.info(
                        'duplicated lock',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        hashlock=pex(transfer.lock.hashlock),
                        lockhash=pex(sha3(transfer.lock.as_bytes)),
                        lockhashes=lpex(lockhashes),
                        received_locksroot=pex(transfer.locksroot),
                    )
                raise ValueError('hashlock is already registered')

            # As a receiver: Check that all locked transfers are registered in
            # the locksroot, if any hashlock is missing there is no way to
            # claim it while the channel is closing
            expected_locksroot = from_state.compute_merkleroot_with(
                transfer.lock)
            if expected_locksroot != transfer.locksroot:
                # this should not happen
                if log.isEnabledFor(logging.WARN):
                    lockhashes = list(
                        from_state.hashlocks_to_unclaimedlocks.values())
                    lockhashes.extend(
                        from_state.hashlocks_to_pendinglocks.values())
                    log.warn(
                        'LOCKSROOT MISMATCH',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        lockhash=pex(sha3(transfer.lock.as_bytes)),
                        lockhashes=lpex(lockhashes),
                        expected_locksroot=pex(expected_locksroot),
                        received_locksroot=pex(transfer.locksroot),
                    )

                raise InvalidLocksRoot(expected_locksroot, transfer.locksroot)

            # For mediators: This is registering the *mediator* paying
            # transfer. The expiration of the lock must be `reveal_timeout`
            # blocks smaller than the *received* paying transfer. This cannot
            # be checked by the paying channel alone.
            #
            # For the initiators: As there is no backing transfer, the
            # expiration is arbitrary, using the channel settle_timeout as an
            # upper limit because the node receiving the transfer will use it
            # as an upper bound while mediating.
            #
            # For the receiver: A lock that expires after the settle period
            # just means there is more time to withdraw it.
            end_settle_period = self.get_settle_expiration(block_number)
            expires_after_settle = transfer.lock.expiration > end_settle_period
            is_sender = transfer.sender == self.our_address

            if is_sender and expires_after_settle:
                if log.isEnabledFor(logging.ERROR):
                    log.error(
                        'Lock expires after the settlement period.',
                        node=pex(self.our_address),
                        from_=pex(from_state.address),
                        to=pex(to_state.address),
                        lock_expiration=transfer.lock.expiration,
                        current_block=block_number,
                        end_settle_period=end_settle_period,
                    )

                raise ValueError('Lock expires after the settlement period.')

        # only check the balance if the locksroot matched
        if transfer.transferred_amount < from_state.transferred_amount:
            if log.isEnabledFor(logging.ERROR):
                log.error(
                    'NEGATIVE TRANSFER',
                    node=pex(self.our_state.address),
                    from_=pex(from_state.address),
                    to=pex(to_state.address),
                    transfer=transfer,
                )

            raise ValueError('Negative transfer')

        amount = transfer.transferred_amount - from_state.transferred_amount
        distributable = from_state.distributable(to_state)

        if isinstance(transfer, DirectTransfer):
            if amount > distributable:
                raise InsufficientBalance(transfer)

        elif isinstance(transfer, LockedTransfer):
            if amount + transfer.lock.amount > distributable:
                raise InsufficientBalance(transfer)

        elif isinstance(transfer, Secret):
            hashlock = sha3(transfer.secret)
            lock = from_state.get_lock_by_hashlock(hashlock)
            transferred_amount = from_state.transferred_amount + lock.amount

            # transfer.transferred_amount could be larger than the previous
            # transferred_amount + lock.amount, that scenario is a bug of the
            # payer
            if transfer.transferred_amount != transferred_amount:
                raise ValueError(
                    'invalid transferred_amount, expected: {} got: {}'.format(
                        transferred_amount,
                        transfer.transferred_amount,
                    ))

        # all checks need to be done before the internal state of the channel
        # is changed, otherwise if a check fails and the state was changed the
        # channel will be left trashed

        if isinstance(transfer, LockedTransfer):
            if log.isEnabledFor(logging.DEBUG):
                lockhashes = list(
                    from_state.hashlocks_to_unclaimedlocks.values())
                lockhashes.extend(
                    from_state.hashlocks_to_pendinglocks.values())
                log.debug(
                    'REGISTERED LOCK',
                    node=pex(self.our_state.address),
                    from_=pex(from_state.address),
                    to=pex(to_state.address),
                    currentlocksroot=pex(merkleroot(from_state.merkletree)),
                    lockhashes=lpex(lockhashes),
                    lock_amount=transfer.lock.amount,
                    lock_expiration=transfer.lock.expiration,
                    lock_hashlock=pex(transfer.lock.hashlock),
                    lockhash=pex(sha3(transfer.lock.as_bytes)),
                )

            from_state.register_locked_transfer(transfer)

            # register this channel as waiting for the secret (the secret can
            # be revealed through a message or a blockchain log)
            self.external_state.register_channel_for_hashlock(
                self,
                transfer.lock.hashlock,
            )

        if isinstance(transfer, DirectTransfer):
            from_state.register_direct_transfer(transfer)

        elif isinstance(transfer, Secret):
            from_state.register_secretmessage(transfer)

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'REGISTERED TRANSFER',
                node=pex(self.our_state.address),
                from_=pex(from_state.address),
                to=pex(to_state.address),
                transfer=repr(transfer),
                transferred_amount=from_state.transferred_amount,
                nonce=from_state.nonce,
                current_locksroot=pex(merkleroot(from_state.merkletree)),
            )
Exemple #29
0
    def _run(self):  # pylint: disable=method-hidden,too-many-locals
        fee = 0
        raiden = self.raiden

        from_mediated_transfer = self.from_mediated_transfer
        hashlock = from_mediated_transfer.lock.hashlock

        from_token = from_mediated_transfer.token
        to_token = self.to_token
        to_amount = self.to_amount

        to_graph = raiden.channelgraphs[to_token]
        from_graph = raiden.channelgraphs[from_token]

        from_channel = from_graph.partneraddress_channel[from_mediated_transfer.sender]

        raiden.register_task_for_hashlock(from_token, self, hashlock)
        raiden.register_channel_for_hashlock(from_token, from_channel, hashlock)

        lock_expiration = from_mediated_transfer.lock.expiration - raiden.config['reveal_timeout']
        lock_timeout = lock_expiration - raiden.get_block_number()

        to_routes = to_graph.get_best_routes(
            raiden.addres,
            from_mediated_transfer.initiator,  # route back to the initiator
            from_mediated_transfer.lock.amount,
            lock_timeout,
        )

        if log.isEnabledFor(logging.DEBUG):
            log.debug(
                'EXCHANGE TRANSFER %s -> %s msghash:%s hashlock:%s',
                pex(from_mediated_transfer.target),
                pex(from_mediated_transfer.initiator),
                pex(from_mediated_transfer.hash),
                pex(hashlock),
            )

        secret_request = SecretRequest(
            from_mediated_transfer.identifier,
            from_mediated_transfer.lock.hashlock,
            from_mediated_transfer.lock.amount,
        )
        raiden.sign(secret_request)
        raiden.send_async(from_mediated_transfer.initiator, secret_request)

        for path, to_channel in to_routes:
            to_next_hop = path[1]

            to_mediated_transfer = to_channel.create_mediatedtransfer(
                raiden.address,  # this node is the new initiator
                from_mediated_transfer.initiator,  # the initiator is the target for the to_token
                fee,
                to_amount,
                lock_expiration,
                hashlock,  # use the original hashlock
            )
            raiden.sign(to_mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'MEDIATED TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(from_mediated_transfer.lock.hashlock),
                )

            # The interest on the hashlock outlives this task, the secret
            # handling will happen only _once_
            raiden.register_channel_for_hashlock(to_token, to_channel, hashlock)
            to_channel.register_transfer(to_mediated_transfer)

            response = self.send_and_wait_valid(raiden, to_mediated_transfer)

            if log.isEnabledFor(logging.DEBUG):
                log.debug(
                    'EXCHANGE TRANSFER NEW PATH path:%s hashlock:%s',
                    lpex(path),
                    pex(hashlock),
                )

            # only refunds for `from_token` must be considered (check send_and_wait_valid)
            if isinstance(response, RefundTransfer):
                if response.lock.amount != to_mediated_transfer.amount:
                    log.info(
                        'Partner %s sent an invalid refund message with an invalid amount',
                        pex(to_next_hop),
                    )
                    raiden.on_hashlock_result(from_token, hashlock, False)
                    return
                else:
                    to_channel.register_transfer(response)

            elif isinstance(response, Secret):
                # claim the from_token
                raiden.handle_secret(
                    response.identifier,
                    from_token,
                    response.secret,
                    response,
                    hashlock,
                )

                # unlock the to_token
                raiden.handle_secret(
                    response.identifier,
                    to_token,
                    response.secret,
                    response,
                    hashlock,
                )

                self._wait_for_unlock_or_close(
                    raiden,
                    from_graph,
                    from_channel,
                    from_mediated_transfer,
                )