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