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)), )
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()), )