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, 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 test_timestamped_event(): event = EventPaymentSentFailed(make_token_network_registry_address(), make_address(), 1, make_address(), "whatever") log_time = "2018-09-07T20:02:35.000" timestamped = TimestampedEvent(event, log_time) assert timestamped.log_time == log_time assert timestamped.reason == timestamped.wrapped_event.reason == "whatever" assert timestamped.identifier == 1
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 test_timestamped_event(): event = EventPaymentSentFailed(make_token_network_registry_address(), make_address(), 1, make_address(), "whatever") log_time = datetime.fromisoformat("2018-09-07T20:02:35.000") timestamped = TimestampedEvent(event, log_time) assert timestamped.log_time == log_time assert isinstance(timestamped.event, EventPaymentSentFailed) assert timestamped.reason == timestamped.event.reason == "whatever" assert timestamped.identifier == timestamped.event.identifier == 1
def handle_block( initiator_state: InitiatorTransferState, state_change: Block, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: secrethash = initiator_state.transfer.lock.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get( secrethash) if not locked_lock: return TransitionResult(initiator_state, list()) lock_expiration_threshold = typing.BlockNumber( locked_lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2, ) 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, ) if lock_has_expired: expired_lock_events = channel.events_for_expired_lock( channel_state=channel_state, locked_lock=locked_lock, pseudo_random_generator=pseudo_random_generator, ) transfer_description = initiator_state.transfer_description # 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 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="transfer's lock has expired", ) expired_lock_events.append(transfer_failed) return TransitionResult( None, typing.cast(typing.List[Event], expired_lock_events), ) else: return TransitionResult(initiator_state, list())
def test_timestamped_event(): event = EventPaymentSentFailed( factories.make_payment_network_identifier(), factories.make_address(), 1, factories.make_address(), 'whatever', ) log_time = '2018-09-07T20:02:35.000' timestamped = TimestampedEvent(event, log_time) assert timestamped.log_time == log_time assert timestamped.reason == timestamped.wrapped_event.reason == 'whatever' assert timestamped.identifier == 1
def test_event_filter_for_payments(): token_network_address = factories.make_address() secret = factories.make_secret() token_network_registry_address = factories.make_token_network_registry_address( ) identifier = 1 target = factories.make_address() event = EventPaymentSentSuccess( token_network_registry_address=token_network_registry_address, token_network_address=token_network_address, identifier=identifier, amount=5, target=target, secret=secret, route=[], ) assert event_filter_for_payments(event=event, partner_address=None) assert event_filter_for_payments(event=event, partner_address=target) assert not event_filter_for_payments( event=event, partner_address=factories.make_address()) event = EventPaymentReceivedSuccess( token_network_registry_address=token_network_registry_address, token_network_address=token_network_address, identifier=identifier, amount=5, initiator=target, ) assert event_filter_for_payments(event=event, partner_address=None) assert event_filter_for_payments(event=event, partner_address=target) assert not event_filter_for_payments( event=event, partner_address=factories.make_address()) event = EventPaymentSentFailed( token_network_registry_address=factories. make_token_network_registry_address(), token_network_address=token_network_address, identifier=identifier, target=target, reason="whatever", ) assert event_filter_for_payments(event=event, partner_address=None) assert event_filter_for_payments(event=event, partner_address=target) assert not event_filter_for_payments( event=event, partner_address=factories.make_address())
def test_event_filter_for_payments(): secret = factories.make_secret() identifier = PaymentID(1) target = TargetAddress(factories.make_address()) event1 = EventPaymentSentSuccess( token_network_registry_address=UNIT_TOKEN_NETWORK_REGISTRY_ADDRESS, token_network_address=UNIT_TOKEN_NETWORK_ADDRESS, identifier=identifier, amount=PaymentAmount(5), target=target, secret=secret, route=[], ) assert event_filter_for_payments(event=event1, partner_address=None) assert event_filter_for_payments(event=event1, partner_address=Address(target)) assert not event_filter_for_payments( event=event1, partner_address=factories.make_address()) initiator = InitiatorAddress(factories.make_address()) event2 = EventPaymentReceivedSuccess( token_network_registry_address=UNIT_TOKEN_NETWORK_REGISTRY_ADDRESS, token_network_address=UNIT_TOKEN_NETWORK_ADDRESS, identifier=identifier, amount=TokenAmount(5), initiator=initiator, ) assert event_filter_for_payments(event=event2, partner_address=None) assert event_filter_for_payments(event=event2, partner_address=Address(initiator)) assert not event_filter_for_payments( event=event2, partner_address=factories.make_address()) event3 = EventPaymentSentFailed( token_network_registry_address=UNIT_TOKEN_NETWORK_REGISTRY_ADDRESS, token_network_address=UNIT_TOKEN_NETWORK_ADDRESS, identifier=identifier, target=target, reason="whatever", ) assert event_filter_for_payments(event=event3, partner_address=None) assert event_filter_for_payments(event=event3, partner_address=Address(target)) assert not event_filter_for_payments( event=event3, partner_address=factories.make_address())
def handle_cancelpayment( payment_state: InitiatorPaymentState, channel_state: NettingChannelState, ) -> TransitionResult: """ Cancel the payment. """ assert can_cancel(payment_state), 'Cannot cancel a transfer after the secret is revealed' transfer_description = payment_state.initiator.transfer_description cancel_events = cancel_current_route(payment_state) cancel = EventPaymentSentFailed( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=channel_state.token_network_identifier, identifier=transfer_description.payment_identifier, target=transfer_description.target, reason='user canceled payment', ) cancel_events.append(cancel) return TransitionResult(None, cancel_events)
def test_v1_event_payment_sent_failed_schema(): event = EventPaymentSentFailed( factories.make_payment_network_identifier(), factories.make_address(), 1, factories.make_address(), 'whatever', ) log_time = '2018-09-07T20:02:35.000' timestamped = TimestampedEvent(event, log_time) dumped = EventPaymentSentFailedSchema().dump(timestamped) expected = { 'event': 'EventPaymentSentFailed', 'log_time': log_time, 'reason': 'whatever', } assert all(dumped.data.get(key) == value for key, value in expected.items())
def test_v1_event_payment_sent_failed_schema(): event = EventPaymentSentFailed( token_network_registry_address=UNIT_TOKEN_NETWORK_REGISTRY_ADDRESS, token_network_address=UNIT_TOKEN_NETWORK_ADDRESS, identifier=PaymentID(1), target=TargetAddress(factories.make_address()), reason="whatever", ) log_time = datetime.datetime.now() timestamped = TimestampedEvent(event, log_time) dumped = EventPaymentSentFailedSchema().dump(timestamped) expected = { "event": "EventPaymentSentFailed", "log_time": log_time.isoformat(), "reason": "whatever", } assert all(dumped.get(key) == value for key, value in expected.items())
def test_event_filter_for_payments(): token_network_identifier = factories.make_address() payment_network_identifier = factories.make_payment_network_identifier() identifier = 1 target = factories.make_address() event = EventPaymentSentSuccess( payment_network_identifier=payment_network_identifier, token_network_identifier=token_network_identifier, identifier=identifier, amount=5, target=target, ) assert event_filter_for_payments(event, token_network_identifier, None) assert event_filter_for_payments(event, token_network_identifier, target) assert not event_filter_for_payments(event, token_network_identifier, factories.make_address()) event = EventPaymentReceivedSuccess( payment_network_identifier=payment_network_identifier, token_network_identifier=token_network_identifier, identifier=identifier, amount=5, initiator=target, ) assert event_filter_for_payments(event, token_network_identifier, None) assert event_filter_for_payments(event, token_network_identifier, target) assert not event_filter_for_payments(event, token_network_identifier, factories.make_address()) event = EventPaymentSentFailed( payment_network_identifier=factories.make_payment_network_identifier(), token_network_identifier=token_network_identifier, identifier=identifier, target=target, reason="whatever", ) assert event_filter_for_payments(event, token_network_identifier, None) assert event_filter_for_payments(event, token_network_identifier, target) assert not event_filter_for_payments(event, token_network_identifier, factories.make_address())
def test_v1_event_payment_sent_failed_schema(): event = EventPaymentSentFailed( payment_network_identifier=factories.make_payment_network_identifier(), token_network_identifier=factories.make_address(), identifier=1, target=factories.make_address(), reason="whatever", ) log_time = "2018-09-07T20:02:35.000" timestamped = TimestampedEvent(event, log_time) dumped = EventPaymentSentFailedSchema().dump(timestamped) expected = { "event": "EventPaymentSentFailed", "log_time": log_time, "reason": "whatever" } assert all( dumped.data.get(key) == value for key, value in expected.items())
def test_write_read_events(): wal = new_wal(state_transition_noop) event = EventPaymentSentFailed( factories.make_payment_network_identifier(), factories.make_address(), 1, factories.make_address(), 'whatever', ) event_list = [event] with pytest.raises(sqlite3.IntegrityError): unexisting_state_change_id = 1 wal.storage.write_events( unexisting_state_change_id, event_list, '2018-08-31T17:38:00.000', ) previous_events = wal.storage.get_events_with_timestamps() log_time = '2018-09-07T20:02:35.0000' state_change_id = wal.storage.write_state_change('statechangedata', log_time) wal.storage.write_events( state_change_id, event_list, log_time, ) new_events = wal.storage.get_events_with_timestamps() assert len(previous_events) + 1 == len(new_events) latest_event = new_events[-1] assert isinstance(latest_event, TimestampedEvent) assert isinstance(latest_event.wrapped_event, EventPaymentSentFailed) assert latest_event.log_time == log_time
def test_write_read_events(): wal = new_wal(state_transition_noop) event = EventPaymentSentFailed(make_token_network_registry_address(), make_address(), 1, make_address(), "whatever") with pytest.raises(sqlite3.IntegrityError): unexisting_state_change_id = random.getrandbits(16 * 8).to_bytes( 16, "big") wal.storage.write_events([(unexisting_state_change_id, event)]) previous_events = wal.storage.get_events_with_timestamps() state_change_ids = wal.storage.write_state_changes([StateChange()]) wal.storage.write_events([(state_change_ids[0], event)]) new_events = wal.storage.get_events_with_timestamps() assert len(previous_events) + 1 == len(new_events) latest_event = new_events[-1] assert isinstance(latest_event, TimestampedEvent) assert isinstance(latest_event.wrapped_event, EventPaymentSentFailed)
def test_write_read_events(): wal = new_wal() event = EventPaymentSentFailed(2, 3, 1, 'address', 'whatever') event_list = [event] block_number = 10 with pytest.raises(sqlite3.IntegrityError): unexisting_state_change_id = 1 wal.storage.write_events( unexisting_state_change_id, block_number, event_list, ) previous_events = wal.storage.get_events_by_identifier( from_identifier=0, to_identifier='latest', ) state_change_id = wal.storage.write_state_change('statechangedata') wal.storage.write_events( state_change_id, block_number, event_list, ) new_events = wal.storage.get_events_by_identifier( from_identifier=0, to_identifier='latest', ) assert len(previous_events) + 1 == len(new_events) latest_event = new_events[-1] assert latest_event[0] == block_number assert isinstance(latest_event[1], EventPaymentSentFailed)
def handle_action_transfer_direct( payment_network_identifier, token_network_state, state_change, pseudo_random_generator, block_number, ): receiver_address = state_change.receiver_address channels = [ token_network_state.channelidentifiers_to_channels[channel_id] for channel_id in token_network_state. partneraddresses_to_channelidentifiers[receiver_address] ] channel_states = views.filter_channels_by_status( channels, [CHANNEL_STATE_UNUSABLE], ) if channel_states: iteration = channel.state_transition( channel_states[-1], state_change, pseudo_random_generator, block_number, ) events = iteration.events else: failure = EventPaymentSentFailed( payment_network_identifier, state_change.token_network_identifier, state_change.identifier, receiver_address, 'Unknown partner channel', ) events = [failure] return TransitionResult(token_network_state, events)
def handle_block( initiator_state: InitiatorTransferState, state_change: Block, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: 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 = BlockNumber( 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, ) if lock_has_expired: expired_lock_events = channel.events_for_expired_lock( channel_state=channel_state, locked_lock=locked_lock, pseudo_random_generator=pseudo_random_generator, ) transfer_description = initiator_state.transfer_description # 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 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="transfer's lock has expired", ) expired_lock_events.append(transfer_failed) lock_exists = channel.lock_exists_in_either_channel_side( channel_state=channel_state, secrethash=secrethash, ) 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, cast(List[Event], expired_lock_events), ) else: return TransitionResult(initiator_state, list())
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)
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 = BlockNumber( 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: is_channel_open = channel.get_status( channel_state) == CHANNEL_STATE_OPENED if is_channel_open: expired_lock_events = channel.events_for_expired_lock( 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( payment_network_identifier=transfer_description. payment_network_identifier, token_network_identifier=transfer_description. token_network_identifier, identifier=payment_identifier, target=transfer_description.target, reason=reason, ) 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, ) 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, unlock_failed], ) else: return TransitionResult(initiator_state, events)
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 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)