예제 #1
0
    def register_balanceproof(self, balance_proof):
        if not isinstance(balance_proof, BalanceProofState):
            raise ValueError('balance_proof must be a BalanceProof instance')

        unclaimed_locksroot = self.merkleroot_for_unclaimed()

        if balance_proof.locksroot != unclaimed_locksroot:
            raise InvalidLocksRoot(unclaimed_locksroot,
                                   balance_proof.locksroot)

        self.balance_proof = balance_proof
예제 #2
0
    def register_direct_transfer(self, direct_transfer):
        if not isinstance(direct_transfer, DirectTransfer):
            raise ValueError('transfer must be a DirectTransfer')

        unclaimed_locksroot = self.merkleroot_for_unclaimed()

        if direct_transfer.locksroot != unclaimed_locksroot:
            raise InvalidLocksRoot(unclaimed_locksroot,
                                   direct_transfer.locksroot)

        self.transfer = direct_transfer
        self.hashlock_unlockedlocks = dict()
예제 #3
0
    def register_direct_transfer(self, direct_transfer):
        """ Register a direct_transfer.

        Raises:
            InvalidLocksRoot: If the merkleroot of `direct_transfer` does not
            match the current value.
        """
        balance_proof = direct_transfer.to_balanceproof()

        if balance_proof.locksroot != merkleroot(self.merkletree):
            raise InvalidLocksRoot(merkleroot(self.merkletree), balance_proof.locksroot)

        self.balance_proof = balance_proof
    def register_locked_transfer(self, locked_transfer):
        """ Register the latest known transfer.

        The sender needs to use this method before sending a locked transfer,
        otherwise the calculate locksroot of the transfer message will be
        invalid and the transfer will be rejected by the partner. Since the
        sender wants the transfer to be accepted by the receiver otherwise the
        transfer won't proceed and the sender won't receive their fee.

        The receiver needs to use this method to update the container with a
        _valid_ transfer, otherwise the locksroot will not contain the pending
        transfer. The receiver needs to ensure that the merkle root has the
        hashlock included, otherwise it won't be able to claim it.

        Args:
            transfer (LockedTransfer): The transfer to be added.

        Raises:
            InvalidLocksRoot: If the merkleroot of `locked_transfer` does not
            match with the expected value.

            ValueError: If the transfer contains a lock that was registered
            previously.
        """
        balance_proof = locked_transfer.to_balanceproof()
        lock = locked_transfer.lock
        lockhashed = sha3(lock.as_bytes)

        if self.is_known(lock.hashlock):
            raise ValueError('hashlock is already registered')

        leaves = list(self.merkletree.layers[LEAVES])
        leaves.append(lockhashed)

        newtree = MerkleTreeState(compute_layers(leaves))
        locksroot = merkleroot(newtree)

        if balance_proof.locksroot != locksroot:
            raise InvalidLocksRoot(locksroot, balance_proof.locksroot)

        self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock(
            lock, lockhashed)
        self.balance_proof = balance_proof
        self.merkletree = newtree
예제 #5
0
    def register_balanceproof_with_lock(self, balance_proof, lock):
        lockhashed = sha3(lock.as_bytes)

        if not isinstance(balance_proof, BalanceProofState):
            raise ValueError('balance_proof must be a BalanceProof instance')

        if self.is_known(lock.hashlock):
            raise ValueError('hashlock is already registered')

        leafs = self.unclaimed_merkletree()
        leafs.append(lockhashed)
        new_locksroot = Merkletree(leafs).merkleroot

        if balance_proof.locksroot != new_locksroot:
            raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot)

        self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock(
            lock, lockhashed)
        self.balance_proof = balance_proof
예제 #6
0
    def register_balanceproof_without_lock(self, balance_proof, lock):
        lockhashed = sha3(lock.as_bytes)

        if not isinstance(balance_proof, BalanceProofState):
            raise ValueError('balance_proof must be a BalanceProof instance')

        if not self.is_known(lock.hashlock):
            raise ValueError('hashlock is not registered')

        leaves = self.unclaimed_merkletree()
        leaves.remove(lockhashed)
        new_locksroot = Merkletree(leaves).merkleroot

        if balance_proof.locksroot != new_locksroot:
            raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot)

        if lock.hashlock in self.hashlocks_to_pendinglocks:
            del self.hashlocks_to_pendinglocks[lock.hashlock]
        else:
            del self.hashlocks_to_unclaimedlocks[lock.hashlock]

        self.balance_proof = balance_proof
    def register_secretmessage(self, message_secret):
        balance_proof = message_secret.to_balanceproof()
        hashlock = sha3(message_secret.secret)
        pendinglock = self.hashlocks_to_pendinglocks.get(hashlock)

        if not pendinglock:
            pendinglock = self.hashlocks_to_unclaimedlocks[hashlock]

        lock = pendinglock.lock
        lockhashed = sha3(lock.as_bytes)

        if not isinstance(balance_proof, BalanceProofState):
            raise ValueError('balance_proof must be a BalanceProof instance')

        if not self.is_known(lock.hashlock):
            raise ValueError('hashlock is not registered')

        leaves = list(self.merkletree.layers[LEAVES])
        leaves.remove(lockhashed)

        if leaves:
            layers = compute_layers(leaves)
            new_merkletree = MerkleTreeState(layers)
            new_locksroot = merkleroot(new_merkletree)
        else:
            new_merkletree = EMPTY_MERKLE_TREE
            new_locksroot = EMPTY_MERKLE_ROOT

        if balance_proof.locksroot != new_locksroot:
            raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot)

        if lock.hashlock in self.hashlocks_to_pendinglocks:
            del self.hashlocks_to_pendinglocks[lock.hashlock]
        else:
            del self.hashlocks_to_unclaimedlocks[lock.hashlock]

        self.merkletree = new_merkletree
        self.balance_proof = balance_proof
예제 #8
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)),
            )
예제 #9
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()),
            )