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