def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult[InitiatorTransferState]: is_message_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash and state_change.payment_identifier == initiator_state.transfer_description.payment_identifier) lock = channel.get_lock( channel_state.our_state, initiator_state.transfer_description.secrethash, ) already_received_secret_request = initiator_state.received_secret_request is_valid_secretrequest = (state_change.amount == initiator_state.transfer_description.amount and state_change.expiration == lock.expiration) if already_received_secret_request and is_message_from_target: # A secret request was received earlier, all subsequent are ignored # as it might be an attack iteration = TransitionResult(initiator_state, list()) elif is_valid_secretrequest and is_message_from_target: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng( pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target revealsecret = SendSecretReveal( recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=transfer_description.secret, ) initiator_state.revealsecret = revealsecret initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, [revealsecret]) elif not is_valid_secretrequest and is_message_from_target: initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, list()) else: iteration = TransitionResult(initiator_state, list()) return iteration
def handle_send_secret_reveal_light( initiator_state: InitiatorTransferState, state_change: ActionSendSecretRevealLight ) -> TransitionResult[InitiatorTransferState]: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = state_change.reveal_secret.message_identifier transfer_description = initiator_state.transfer_description recipient = transfer_description.target revealsecret = SendSecretRevealLight( sender= Address(state_change.sender), recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=state_change.reveal_secret.secret, signed_secret_reveal=state_change.reveal_secret ) initiator_state.revealsecret = revealsecret initiator_state.received_secret_request = True store_message_event = StoreMessageEvent(message_identifier, transfer_description.payment_identifier, 7, state_change.reveal_secret, True) iteration = TransitionResult(initiator_state, [revealsecret, store_message_event]) return iteration
def handle_secretrequest_light( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequestLight, channel_state: NettingChannelState ) -> TransitionResult[InitiatorTransferState]: is_message_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash and state_change.payment_identifier == initiator_state.transfer_description.payment_identifier ) lock = channel.get_lock( channel_state.our_state, initiator_state.transfer_description.secrethash ) # This should not ever happen. This task clears itself when the lock is # removed. assert lock is not None, "channel is does not have the transfer's lock" already_received_secret_request = initiator_state.received_secret_request # lock.amount includes the fees, transfer_description.amount is the actual # payment amount, for the transfer to be valid and the unlock allowed the # target must receive an amount between these values. is_valid_secretrequest = ( state_change.amount <= lock.amount and state_change.amount >= initiator_state.transfer_description.amount and state_change.expiration == lock.expiration ## and initiator_state.transfer_description.secret != EMPTY_SECRET ) if already_received_secret_request and is_message_from_target: # A secret request was received earlier, all subsequent are ignored # as it might be an attack iteration = TransitionResult(initiator_state, list()) elif is_valid_secretrequest and is_message_from_target: store_event = StoreMessageEvent(state_change.secret_request_message.message_identifier, state_change.payment_identifier, 5, state_change.secret_request_message, True) initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, [store_event]) elif not is_valid_secretrequest and is_message_from_target: initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, list()) else: iteration = TransitionResult(initiator_state, list()) return iteration
def send_lockedtransfer( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, message_identifier, block_number: typing.BlockNumber, ) -> SendLockedTransfer: """ Create a mediated transfer using channel. Raises: AssertionError: If the channel does not have enough capacity. """ transfer_token_address = initiator_state.transfer_description.token_network_identifier assert channel_state.token_network_identifier == transfer_token_address transfer_description = initiator_state.transfer_description lock_expiration = get_initial_lock_expiration( block_number, channel_state.settle_timeout, ) lockedtransfer_event = channel.send_lockedtransfer( channel_state, transfer_description.initiator, transfer_description.target, transfer_description.amount, message_identifier, transfer_description.payment_identifier, lock_expiration, transfer_description.secrethash, ) assert lockedtransfer_event initiator_state.transfer = lockedtransfer_event.transfer return lockedtransfer_event
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: request_from_target = (state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash) is_valid_payment_id = (state_change.payment_identifier == initiator_state. transfer_description.payment_identifier) valid_secretrequest = (request_from_target and is_valid_payment_id and state_change.amount == initiator_state.transfer_description.amount) invalid_secretrequest = request_from_target and ( is_valid_payment_id or state_change.amount != initiator_state.transfer_description.amount) if valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng( pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target queue_name = b'global' revealsecret = SendRevealSecret( recipient, queue_name, message_identifier, transfer_description.secret, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif invalid_secretrequest: cancel = EventPaymentSentFailed( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=channel_state.token_network_identifer, identifier=initiator_state.transfer_description.payment_identifier, target=initiator_state.transfer_description.target, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def send_mediatedtransfer( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ) -> SendMediatedTransfer: """ Create a mediated transfer using channel. Raises: AssertionError: If the channel does not have enough capacity. """ assert channel_state.token_address == initiator_state.transfer_description.token transfer_description = initiator_state.transfer_description lock_expiration = get_initial_lock_expiration( block_number, channel_state.settle_timeout, ) mediatedtransfer_event = channel.send_mediatedtransfer( channel_state, transfer_description.initiator, transfer_description.target, transfer_description.amount, transfer_description.identifier, lock_expiration, transfer_description.hashlock, ) assert mediatedtransfer_event initiator_state.transfer = mediatedtransfer_event.transfer return mediatedtransfer_event
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: is_message_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash and state_change.payment_identifier == initiator_state.transfer_description.payment_identifier) lock = channel.get_lock( channel_state.our_state, initiator_state.transfer_description.secrethash, ) is_valid_secretrequest = (is_message_from_target and state_change.amount == initiator_state.transfer_description.amount and state_change.expiration == lock.expiration) if is_valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng( pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target revealsecret = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=transfer_description.secret, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif not is_valid_secretrequest and is_message_from_target: cancel = EventPaymentSentFailed( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=channel_state.token_network_identifier, identifier=initiator_state.transfer_description.payment_identifier, target=initiator_state.transfer_description.target, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( old_initiator_state: typing.Optional[InitiatorTransferState], channelidentifiers_to_channels: typing.ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events: typing.List[Event] = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description. payment_network_identifier, token_network_identifier=transfer_description. token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) # Here we don't delete the initiator state, but instead let it live. # It will be deleted when the lock expires. We do that so that we # still have an initiator payment task around to process the # LockExpired message that our partner will send us. # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046 initiator_state = old_initiator_state else: message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, ) assert lockedtransfer_event initiator_state = InitiatorTransferState( transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, revealsecret=None, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, pseudo_random_generator: random.Random, ) -> TransitionResult: request_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash ) is_valid_payment_id = ( state_change.payment_identifier == initiator_state.transfer_description.payment_identifier ) valid_secretrequest = ( request_from_target and is_valid_payment_id and state_change.amount == initiator_state.transfer_description.amount ) invalid_secretrequest = request_from_target and ( is_valid_payment_id or state_change.amount != initiator_state.transfer_description.amount ) if valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng(pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target queue_name = b'global' revealsecret = SendRevealSecret( recipient, queue_name, message_identifier, transfer_description.secret, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif invalid_secretrequest: cancel = EventTransferSentFailed( identifier=initiator_state.transfer_description.payment_identifier, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( channelidentifiers_to_channels: typing.ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description. payment_network_identifier, token_network_identifier=transfer_description. token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, ) assert lockedtransfer_event initiator_state = InitiatorTransferState( transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, revealsecret=None, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, ) -> TransitionResult: request_from_target = (state_change.sender == initiator_state.transfer_description.target and state_change.hashlock == initiator_state.transfer_description.hashlock) valid_secretrequest = (request_from_target and state_change.identifier == initiator_state.transfer_description.identifier and state_change.amount == initiator_state.transfer_description.amount) invalid_secretrequest = request_from_target and ( state_change.identifier != initiator_state.transfer_description.identifier or state_change.amount != initiator_state.transfer_description.amount) if valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # transfer_description = initiator_state.transfer_description reveal_secret = SendRevealSecret( transfer_description.identifier, transfer_description.secret, transfer_description.token, transfer_description.target, ) initiator_state.revealsecret = reveal_secret iteration = TransitionResult(initiator_state, [reveal_secret]) elif invalid_secretrequest: cancel = EventTransferSentFailed( identifier=initiator_state.transfer_description.identifier, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def test_initiator_task_view(): """Test transfer_tasks_view(), which is used to generate the output of the pending transfers API, with an initiator task. """ channel_id = factories.UNIT_CHANNEL_ID secret = factories.make_secret() payment_hash_invoice = factories.make_payment_hash_invoice() transfer = factories.create( factories.LockedTransferUnsignedStateProperties( secret=secret, payment_hash_invoice=payment_hash_invoice)) secrethash = transfer.lock.secrethash transfer_description = TransferDescriptionWithSecretState( payment_network_identifier=factories.UNIT_PAYMENT_NETWORK_IDENTIFIER, payment_identifier=transfer.payment_identifier, payment_hash_invoice=transfer.payment_hash_invoice, amount=transfer.balance_proof.locked_amount, allocated_fee=0, token_network_identifier=factories.UNIT_TOKEN_NETWORK_ADDRESS, initiator=transfer.initiator, target=transfer.target, secret=secret, ) transfer_state = InitiatorTransferState( transfer_description=transfer_description, channel_identifier=channel_id, transfer=transfer, revealsecret=None, ) payment_state = InitiatorPaymentState({secrethash: transfer_state}) task = InitiatorTask( token_network_identifier=factories.UNIT_TOKEN_NETWORK_ADDRESS, manager_state=payment_state) payment_mapping = {secrethash: task} view = transfer_tasks_view(payment_mapping) assert len(view) == 1 pending_transfer = view[0] assert pending_transfer.get("role") == "initiator" balance_proof = transfer.balance_proof assert pending_transfer.get("channel_identifier") == str( balance_proof.channel_identifier) assert pending_transfer.get("locked_amount") == str( balance_proof.locked_amount) assert pending_transfer.get("transferred_amount") == str( balance_proof.transferred_amount)
def test_initiator_task_view(): """Test transfer_tasks_view(), which is used to generate the output of the pending transfers API, with an initiator task. """ channel_id = factories.UNIT_CHANNEL_ID secret = factories.make_secret() transfer = factories.create( factories.LockedTransferUnsignedStateProperties(secret=secret)) secrethash = transfer.lock.secrethash transfer_description = TransferDescriptionWithSecretState( token_network_registry_address=factories. UNIT_TOKEN_NETWORK_REGISTRY_ADDRESS, payment_identifier=transfer.payment_identifier, amount=transfer.balance_proof.locked_amount, token_network_address=factories.UNIT_TOKEN_NETWORK_ADDRESS, initiator=transfer.initiator, target=transfer.target, secret=secret, secrethash=sha256_secrethash(secret), ) transfer_state = InitiatorTransferState( route=RouteState(route=[transfer.initiator, transfer.target], forward_channel_id=channel_id), transfer_description=transfer_description, channel_identifier=channel_id, transfer=transfer, ) payment_state = InitiatorPaymentState( routes=[], initiator_transfers={secrethash: transfer_state}) task = InitiatorTask( token_network_address=factories.UNIT_TOKEN_NETWORK_ADDRESS, manager_state=payment_state) payment_mapping = {secrethash: cast(TransferTask, task)} view = transfer_tasks_view(payment_mapping) assert len(view) == 1 pending_transfer = view[0] assert pending_transfer.get("role") == "initiator" balance_proof = transfer.balance_proof assert pending_transfer.get("channel_identifier") == str( balance_proof.channel_identifier) assert pending_transfer.get("locked_amount") == str( balance_proof.locked_amount) assert pending_transfer.get("transferred_amount") == str( balance_proof.transferred_amount)
def try_new_route( channelidentifiers_to_channels: ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventTransferSentFailed( identifier=transfer_description.identifier, reason=reason, ) events.append(transfer_failed) initiator_state = None else: initiator_state = InitiatorTransferState( transfer_description, channel_state.identifier, ) mediatedtransfer_event = send_mediatedtransfer( initiator_state, channel_state, block_number, ) assert mediatedtransfer_event events.append(mediatedtransfer_event) return TransitionResult(initiator_state, events)
def try_new_route_light( channelidentifiers_to_channels: ChannelMap, available_routes: List[RouteState], transfer_description: TransferDescriptionWithoutSecretState, signed_locked_transfer: LockedTransfer ) -> TransitionResult[InitiatorTransferState]: channel_state = next_channel_from_routes( available_routes=available_routes, channelidentifiers_to_channels=channelidentifiers_to_channels, transfer_amount=transfer_description.amount, initiator=to_canonical_address(transfer_description.initiator) ) events: List[Event] = list() if channel_state is None: if not available_routes: reason = "there is no route available" else: reason = "none of the available routes could be used" # TODO mmartinez handle persistance with status failure? transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description.payment_network_identifier, token_network_identifier=transfer_description.token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: received_lock = signed_locked_transfer.lock calculated_lt_event, merkletree = create_sendlockedtransfer( channel_state, signed_locked_transfer.initiator, signed_locked_transfer.target, signed_locked_transfer.locked_amount, signed_locked_transfer.message_identifier, signed_locked_transfer.payment_identifier, signed_locked_transfer.payment_hash_invoice, received_lock.expiration, received_lock.secrethash, ) calculated_transfer = calculated_lt_event.transfer lock = calculated_transfer.lock channel_state.our_state.balance_proof = calculated_transfer.balance_proof channel_state.our_state.merkletree = merkletree channel_state.our_state.secrethashes_to_lockedlocks[lock.secrethash] = lock lockedtransfer_event = SendLockedTransferLight(signed_locked_transfer.recipient, signed_locked_transfer.channel_identifier, signed_locked_transfer.message_identifier, signed_locked_transfer) # Check that the constructed merkletree is equals to the sent by the light client. calculated_locksroot = merkleroot(merkletree) if signed_locked_transfer.locksroot.__eq__(calculated_locksroot): initiator_state = InitiatorTransferState( transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=calculated_transfer, revealsecret=None, ) store_signed_lt = StoreMessageEvent(signed_locked_transfer.message_identifier, signed_locked_transfer.payment_identifier, 1, signed_locked_transfer, True) events.append(lockedtransfer_event) events.append(store_signed_lt) else: transfer_failed = EventPaymentSentFailed( payment_network_identifier=transfer_description.payment_network_identifier, token_network_identifier=transfer_description.token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason="Received locksroot {} doesnt match with expected one {}".format( signed_locked_transfer.locksroot.hex(), calculated_locksroot.hex()), ) # FIXME mmartinez same events.append(transfer_failed) initiator_state = None return TransitionResult(initiator_state, events)
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult[InitiatorTransferState]: is_message_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash and state_change.payment_identifier == initiator_state.transfer_description.payment_identifier) lock = channel.get_lock(channel_state.our_state, initiator_state.transfer_description.secrethash) # This should not ever happen. This task clears itself when the lock is # removed. assert lock is not None, "channel is does not have the transfer's lock" already_received_secret_request = initiator_state.received_secret_request # transfer_description.amount is the actual payment amount without fees. # For the transfer to be valid and the unlock allowed the target must # receive at least that amount. is_valid_secretrequest = ( state_change.amount >= initiator_state.transfer_description.amount and state_change.expiration == lock.expiration and initiator_state.transfer_description.secret != ABSENT_SECRET) if already_received_secret_request and is_message_from_target: # A secret request was received earlier, all subsequent are ignored # as it might be an attack iteration = TransitionResult(initiator_state, list()) elif is_valid_secretrequest and is_message_from_target: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng( pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target revealsecret = SendSecretReveal( recipient=Address(recipient), message_identifier=message_identifier, secret=transfer_description.secret, canonical_identifier=CANONICAL_IDENTIFIER_GLOBAL_QUEUE, ) initiator_state.transfer_state = "transfer_secret_revealed" initiator_state.received_secret_request = True iteration = TransitionResult(initiator_state, [revealsecret]) elif not is_valid_secretrequest and is_message_from_target: initiator_state.received_secret_request = True invalid_request = EventInvalidSecretRequest( payment_identifier=state_change.payment_identifier, intended_amount=initiator_state.transfer_description.amount, actual_amount=state_change.amount, ) iteration = TransitionResult(initiator_state, [invalid_request]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( channelidentifiers_to_channels: Dict[ChannelID, NettingChannelState], nodeaddresses_to_networkstates: NodeNetworkStateMap, candidate_route_states: List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: initiator_state = None events: List[Event] = list() route_fee_exceeds_max = False channel_state = None route_state = None reachable_route_states = routes.filter_reachable_routes( candidate_route_states, nodeaddresses_to_networkstates) for reachable_route_state in reachable_route_states: forward_channel_id = reachable_route_state.forward_channel_id candidate_channel_state = forward_channel_id and channelidentifiers_to_channels.get( forward_channel_id) assert isinstance(candidate_channel_state, NettingChannelState) amount_with_fee = calculate_safe_amount_with_fee( payment_amount=transfer_description.amount, estimated_fee=reachable_route_state.estimated_fee, ) # https://github.com/raiden-network/raiden/issues/4751 # If the transfer amount + fees exceeds a percentage of the # initial amount then don't use this route max_amount_limit = transfer_description.amount + int( transfer_description.amount * MAX_MEDIATION_FEE_PERC) if amount_with_fee > max_amount_limit: route_fee_exceeds_max = True continue is_channel_usable = channel.is_channel_usable_for_new_transfer( channel_state=candidate_channel_state, transfer_amount=amount_with_fee, lock_timeout=transfer_description.lock_timeout, ) if is_channel_usable: channel_state = candidate_channel_state route_state = reachable_route_state break if route_state is None: if not reachable_route_states: reason = "there is no route available" else: reason = "none of the available routes could be used" if route_fee_exceeds_max: reason += " and at least one of them exceeded the maximum fee limit" transfer_failed = EventPaymentSentFailed( token_network_registry_address=transfer_description. token_network_registry_address, token_network_address=transfer_description.token_network_address, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: assert channel_state is not None message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, route_state=route_state, route_states=reachable_route_states, ) assert lockedtransfer_event initiator_state = InitiatorTransferState( route=route_state, transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_block( initiator_state: InitiatorTransferState, state_change: Block, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult[Optional[InitiatorTransferState]]: """ Checks if the lock has expired, and if it has sends a remove expired lock and emits the failing events. """ secrethash = initiator_state.transfer.lock.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get( secrethash) if not locked_lock: if channel_state.partner_state.secrethashes_to_lockedlocks.get( secrethash): return TransitionResult(initiator_state, list()) else: # if lock is not in our or our partner's locked locks then the # task can go return TransitionResult(None, list()) lock_expiration_threshold = BlockExpiration( locked_lock.expiration + DEFAULT_WAIT_BEFORE_LOCK_REMOVAL) lock_has_expired = channel.is_lock_expired( end_state=channel_state.our_state, lock=locked_lock, block_number=state_change.block_number, lock_expiration_threshold=lock_expiration_threshold, ) events: List[Event] = list() if lock_has_expired and initiator_state.transfer_state != "transfer_expired": is_channel_open = channel.get_status( channel_state) == ChannelState.STATE_OPENED if is_channel_open: expired_lock_events = channel.send_lock_expired( channel_state=channel_state, locked_lock=locked_lock, pseudo_random_generator=pseudo_random_generator, ) events.extend(expired_lock_events) if initiator_state.received_secret_request: reason = "bad secret request message from target" else: reason = "lock expired" transfer_description = initiator_state.transfer_description payment_identifier = transfer_description.payment_identifier # TODO: When we introduce multiple transfers per payment this needs to be # reconsidered. As we would want to try other routes once a route # has failed, and a transfer failing does not mean the entire payment # would have to fail. # Related issue: https://github.com/raiden-network/raiden/issues/2329 payment_failed = EventPaymentSentFailed( token_network_registry_address=transfer_description. token_network_registry_address, token_network_address=transfer_description.token_network_address, identifier=payment_identifier, target=transfer_description.target, reason=reason, ) route_failed = EventRouteFailed( secrethash=secrethash, route=initiator_state.route.route, token_network_address=transfer_description.token_network_address, ) unlock_failed = EventUnlockFailed( identifier=payment_identifier, secrethash=initiator_state.transfer_description.secrethash, reason=reason, ) lock_exists = channel.lock_exists_in_either_channel_side( channel_state=channel_state, secrethash=secrethash) initiator_state.transfer_state = "transfer_expired" return TransitionResult( # If the lock is either in our state or partner state we keep the # task around to wait for the LockExpired messages to sync. # Check https://github.com/raiden-network/raiden/issues/3183 initiator_state if lock_exists else None, events + [payment_failed, route_failed, unlock_failed], ) else: return TransitionResult(initiator_state, events)
def try_new_route( addresses_to_channel: Dict[Tuple[TokenNetworkAddress, Address], NettingChannelState], nodeaddresses_to_networkstates: NodeNetworkStateMap, candidate_route_states: List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: initiator_state = None events: List[Event] = list() route_fee_exceeds_max = False channel_state = None route_state = None reachable_route_states = routes.filter_reachable_routes( candidate_route_states, nodeaddresses_to_networkstates) for reachable_route_state in reachable_route_states: candidate_channel_state = addresses_to_channel[( transfer_description.token_network_address, reachable_route_state.route[1])] amount_with_fee = calculate_safe_amount_with_fee( payment_amount=transfer_description.amount, estimated_fee=reachable_route_state.estimated_fee, ) # https://github.com/raiden-network/raiden/issues/4751 # If the transfer amount + fees exceeds a percentage of the # initial amount then don't use this route max_amount_limit = transfer_description.amount + int( transfer_description.amount * MAX_MEDIATION_FEE_PERC) if amount_with_fee > max_amount_limit: route_fee_exceeds_max = True continue channel_usability_state = channel.is_channel_usable_for_new_transfer( channel_state=candidate_channel_state, transfer_amount=amount_with_fee, lock_timeout=transfer_description.lock_timeout, ) if channel_usability_state is channel.ChannelUsability.USABLE: channel_state = candidate_channel_state route_state = reachable_route_state break if route_state is None: if not reachable_route_states: reason = "there is no route available" else: reason = "none of the available routes could be used" if route_fee_exceeds_max: reason += ( " and at least one of them exceeded the maximum fee limit " "(see https://docs.raiden.network/using-raiden/mediation-fees#frequently-asked-questions)" # noqa ) transfer_failed = EventPaymentSentFailed( token_network_registry_address=transfer_description. token_network_registry_address, token_network_address=transfer_description.token_network_address, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason=reason, ) events.append(transfer_failed) initiator_state = None else: assert channel_state is not None, "We must have a channel_state if we have a route_state" message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( transfer_description=transfer_description, channel_state=channel_state, message_identifier=message_identifier, block_number=block_number, route_state=route_state, route_states=reachable_route_states, ) initiator_state = InitiatorTransferState( route=route_state, transfer_description=transfer_description, channel_identifier=channel_state.identifier, transfer=lockedtransfer_event.transfer, ) events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)