def handle_cancelpayment( payment_state: InitiatorPaymentState, channelidentifiers_to_channels: ChannelMap ) -> TransitionResult[InitiatorPaymentState]: """ Cancel the payment and all related transfers. """ # Cannot cancel a transfer after the secret is revealed events = list() for initiator_state in payment_state.initiator_transfers.values(): channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if can_cancel(initiator_state): transfer_description = initiator_state.transfer_description cancel_events = cancel_current_route(payment_state, initiator_state) initiator_state.transfer_state = "transfer_cancelled" cancel = EventPaymentSentFailed( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=TokenNetworkID( channel_state.token_network_identifier), identifier=transfer_description.payment_identifier, target=transfer_description.target, reason="user canceled payment", ) cancel_events.append(cancel) events.extend(cancel_events) return TransitionResult(payment_state, events)
def next_channel_from_routes( available_routes: typing.List[RouteState], channelidentifiers_to_channels: typing.ChannelMap, transfer_amount: typing.TokenAmount, ) -> typing.Optional[NettingChannelState]: """ Returns the first channel that can be used to start the transfer. The routing service can race with local changes, so the recommended routes must be validated. """ for route in available_routes: channel_identifier = route.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if channel.get_status(channel_state) != CHANNEL_STATE_OPENED: continue pending_transfers = channel.get_number_of_pending_transfers( channel_state.our_state) if pending_transfers >= MAXIMUM_PENDING_TRANSFERS: continue distributable = channel.get_distributable( channel_state.our_state, channel_state.partner_state, ) if transfer_amount > distributable: continue if channel.is_valid_amount(channel_state.our_state, transfer_amount): return channel_state return None
def subdispatch_to_initiatortransfer( payment_state: InitiatorPaymentState, initiator_state: InitiatorTransferState, state_change: StateChange, channelidentifiers_to_channels: ChannelMap, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[InitiatorPaymentState]: channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(initiator_state, list()) sub_iteration = initiator.state_transition( initiator_state=initiator_state, state_change=state_change, channel_state=channel_state, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) if sub_iteration.new_state is None: del payment_state.initiator_transfers[ initiator_state.transfer.lock.secrethash] return sub_iteration
def events_for_expired_locks( mediator_state: MediatorTransferState, channelidentifiers_to_channels: typing.ChannelMap, block_number: typing.BlockNumber, pseudo_random_generator: random.Random, ): events = list() transfer_pair: MediationPairState for transfer_pair in mediator_state.transfers_pair: balance_proof = transfer_pair.payee_transfer.balance_proof channel_identifier = balance_proof.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) assert channel_state, "Couldn't find channel for channel_id: {}".format(channel_identifier) secrethash = mediator_state.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get(secrethash) lock_expired = channel.is_lock_expired( end_state=channel_state.our_state, locked_lock=locked_lock, secrethash=secrethash, block_number=block_number, ) if locked_lock and lock_expired: # Lock has expired, cleanup... transfer_pair.payee_state = 'payee_expired' expired_lock_events = channel.events_for_expired_lock( channel_state, secrethash, locked_lock, pseudo_random_generator, ) events.extend(expired_lock_events) return events
def handle_lock_expired( mediator_state: MediatorTransferState, state_change: ReceiveLockExpired, channelidentifiers_to_channels: typing.ChannelMap, block_number: typing.BlockNumber, ): events = list() for transfer_pair in mediator_state.transfers_pair: balance_proof = transfer_pair.payer_transfer.balance_proof channel_state = channelidentifiers_to_channels.get( balance_proof.channel_identifier) if not channel_state: return TransitionResult(mediator_state, list()) result = channel.handle_receive_lock_expired( channel_state=channel_state, state_change=state_change, block_number=block_number, ) if not channel.get_lock(result.new_state.partner_state, mediator_state.secrethash): transfer_pair.payer_state = 'payer_expired' events.extend(result.events) return TransitionResult(mediator_state, events)
def events_to_remove_expired_locks( mediator_state: MediatorTransferState, channelidentifiers_to_channels: typing.ChannelMap, block_number: typing.BlockNumber, pseudo_random_generator: random.Random, ): """ Clear the channels which have expired locks. This only considers the *sent* transfers, received transfers can only be updated by the partner. """ events = list() for transfer_pair in mediator_state.transfers_pair: balance_proof = transfer_pair.payee_transfer.balance_proof channel_identifier = balance_proof.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) assert channel_state, "Couldn't find channel for channel_id: {}".format(channel_identifier) secrethash = mediator_state.secrethash lock = None if secrethash in channel_state.our_state.secrethashes_to_lockedlocks: assert secrethash not in channel_state.our_state.secrethashes_to_unlockedlocks lock = channel_state.our_state.secrethashes_to_lockedlocks.get(secrethash) elif secrethash in channel_state.our_state.secrethashes_to_unlockedlocks: lock = channel_state.our_state.secrethashes_to_unlockedlocks.get(secrethash) if lock: lock_expiration_threshold = ( lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 ) has_lock_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=lock_expiration_threshold, ) if has_lock_expired: transfer_pair.payee_state = 'payee_expired' expired_lock_events = channel.events_for_expired_lock( channel_state=channel_state, locked_lock=lock, pseudo_random_generator=pseudo_random_generator, ) events.extend(expired_lock_events) unlock_failed = EventUnlockFailed( transfer_pair.payee_transfer.payment_identifier, transfer_pair.payee_transfer.lock.secrethash, 'lock expired', ) events.append(unlock_failed) return events
def handle_lock_expired( payment_state: InitiatorPaymentState, state_change: ReceiveLockExpired, channelidentifiers_to_channels: ChannelMap, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[InitiatorPaymentState]: """Initiator also needs to handle LockExpired messages when refund transfers are involved. A -> B -> C - A sends locked transfer to B - B attempted to forward to C but has not enough capacity - B sends a refund transfer with the same secrethash back to A - When the lock expires B will also send a LockExpired message to A - A needs to be able to properly process it Related issue: https://github.com/raiden-network/raiden/issues/3183 """ initiator_state = payment_state.initiator_transfers.get( state_change.secrethash) if not initiator_state: return TransitionResult(payment_state, list()) channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(payment_state, list()) secrethash = initiator_state.transfer.lock.secrethash result = channel.handle_receive_lock_expired( channel_state=channel_state, state_change=state_change, block_number=block_number, ) if not channel.get_lock(result.new_state.partner_state, secrethash): transfer = initiator_state.transfer unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='Lock expired', ) result.events.append(unlock_failed) return TransitionResult(payment_state, result.events)
def handle_block( payment_state: InitiatorPaymentState, state_change: Block, channelidentifiers_to_channels: typing.ChannelMap, pseudo_random_generator: random.Random, ) -> TransitionResult: channel_identifier = payment_state.initiator.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(payment_state, list()) sub_iteration = initiator.handle_block( initiator_state=payment_state.initiator, state_change=state_change, channel_state=channel_state, pseudo_random_generator=pseudo_random_generator, ) iteration = iteration_from_sub(payment_state, sub_iteration) return iteration
def handle_block( payment_state: InitiatorPaymentState, state_change: Block, channelidentifiers_to_channels: ChannelMap, pseudo_random_generator: random.Random, ) -> TransitionResult: channel_identifier = payment_state.initiator.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(payment_state, list()) sub_iteration = initiator.handle_block( initiator_state=payment_state.initiator, state_change=state_change, channel_state=channel_state, pseudo_random_generator=pseudo_random_generator, ) iteration = iteration_from_sub(payment_state, sub_iteration) return iteration
def handle_init( state_change: ActionInitMediator, channelidentifiers_to_channels: typing.ChannelMap, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get( from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, events, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. return TransitionResult(None, events) iteration = mediate_transfer( mediator_state, routes, payer_channel, channelidentifiers_to_channels, pseudo_random_generator, from_transfer, block_number, ) events.extend(iteration.events) return TransitionResult(iteration.new_state, events)
def events_for_expired_locks( mediator_state: MediatorTransferState, channelidentifiers_to_channels: typing.ChannelMap, block_number: typing.BlockNumber, pseudo_random_generator: random.Random, ): events = list() for transfer_pair in mediator_state.transfers_pair: balance_proof = transfer_pair.payee_transfer.balance_proof channel_identifier = balance_proof.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) assert channel_state, "Couldn't find channel for channel_id: {}".format( channel_identifier) secrethash = mediator_state.secrethash locked_lock = channel_state.our_state.secrethashes_to_lockedlocks.get( secrethash) if locked_lock: lock_expiration_threshold = ( locked_lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2) has_lock_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=locked_lock, block_number=block_number, lock_expiration_threshold=lock_expiration_threshold, ) if has_lock_expired: transfer_pair.payee_state = 'payee_expired' expired_lock_events = channel.events_for_expired_lock( channel_state, secrethash, locked_lock, pseudo_random_generator, ) events.extend(expired_lock_events) return events
def handle_init( state_change: ActionInitMediator, channelidentifiers_to_channels: typing.ChannelMap, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get( from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, events, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: return TransitionResult(None, events) iteration = mediate_transfer( mediator_state, routes, payer_channel, channelidentifiers_to_channels, pseudo_random_generator, from_transfer, block_number, ) events.extend(iteration.events) return TransitionResult(iteration.new_state, events)
def handle_transferrefundcancelroute( payment_state: InitiatorPaymentState, state_change: ReceiveTransferRefundCancelRoute, channelidentifiers_to_channels: ChannelMap, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[InitiatorPaymentState]: initiator_state = payment_state.initiator_transfers.get( state_change.transfer.lock.secrethash) if not initiator_state: return TransitionResult(payment_state, list()) channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: return TransitionResult(payment_state, list()) refund_transfer = state_change.transfer original_transfer = initiator_state.transfer is_valid_lock = ( refund_transfer.lock.secrethash == original_transfer.lock.secrethash and refund_transfer.lock.amount == original_transfer.lock.amount and refund_transfer.lock.expiration == original_transfer.lock.expiration) is_valid_refund = channel.refund_transfer_matches_transfer( refund_transfer, original_transfer) events = list() if not is_valid_lock or not is_valid_refund: return TransitionResult(payment_state, list()) is_valid, channel_events, _ = channel.handle_receive_refundtransfercancelroute( channel_state, refund_transfer) events.extend(channel_events) if not is_valid: return TransitionResult(payment_state, list()) route_failed_event = EventRouteFailed( secrethash=original_transfer.lock.secrethash) events.append(route_failed_event) old_description = initiator_state.transfer_description transfer_description = TransferDescriptionWithSecretState( payment_network_identifier=old_description.payment_network_identifier, payment_identifier=old_description.payment_identifier, amount=old_description.amount, token_network_identifier=old_description.token_network_identifier, allocated_fee=old_description.allocated_fee, initiator=old_description.initiator, target=old_description.target, secret=state_change.secret, ) sub_iteration = maybe_try_new_route( payment_state=payment_state, initiator_state=initiator_state, transfer_description=transfer_description, available_routes=state_change.routes, channelidentifiers_to_channels=channelidentifiers_to_channels, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) events.extend(sub_iteration.events) iteration = TransitionResult(payment_state, events) return iteration
def events_for_expired_pairs( channelidentifiers_to_channels: typing.ChannelMap, transfers_pair: typing.List[MediationPairState], waiting_transfer: WaitingTransferState, block_number: typing.BlockNumber, ) -> typing.List[Event]: """ Informational events for expired locks. """ pending_transfers_pairs = get_pending_transfer_pairs(transfers_pair) events = list() for pair in pending_transfers_pairs: payer_balance_proof = pair.payer_transfer.balance_proof payer_channel = channelidentifiers_to_channels.get(payer_balance_proof.channel_identifier) payer_lock_expiration_threshold = ( pair.payer_transfer.lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 ) has_payer_lock_expired, _ = channel.is_lock_expired( end_state=payer_channel.our_state, lock=pair.payer_transfer.lock, block_number=block_number, lock_expiration_threshold=payer_lock_expiration_threshold, ) has_payer_transfer_expired = ( has_payer_lock_expired and pair.payer_state != 'payer_expired' ) if has_payer_transfer_expired: # For safety, the correct behavior is: # # - If the payee has been paid, then the payer must pay too. # # And the corollary: # # - If the payer transfer has expired, then the payee transfer must # have expired too. # # The problem is that this corollary cannot be asserted. If a user # is running Raiden without a monitoring service, then it may go # offline after having paid a transfer to a payee, but without # getting a balance proof of the payer, and once it comes back # online the transfer may have expired. # # assert pair.payee_state == 'payee_expired' pair.payer_state = 'payer_expired' unlock_claim_failed = EventUnlockClaimFailed( pair.payer_transfer.payment_identifier, pair.payer_transfer.lock.secrethash, 'lock expired', ) events.append(unlock_claim_failed) if waiting_transfer and waiting_transfer.state != 'expired': waiting_transfer.state = 'expired' unlock_claim_failed = EventUnlockClaimFailed( waiting_transfer.transfer.payment_identifier, waiting_transfer.transfer.lock.secrethash, 'lock expired', ) events.append(unlock_claim_failed) return events