Exemple #1
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 #2
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()),
            )