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_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 = (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, ) return TransitionResult(None, expired_lock_events) else: return TransitionResult(initiator_state, list())
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) lock_expired = channel.is_lock_expired( end_state=channel_state.our_state, locked_lock=locked_lock, secrethash=secrethash, block_number=state_change.block_number, ) if locked_lock and lock_expired: # Lock has expired, cleanup... expired_lock_events = channel.events_for_expired_lock( channel_state, secrethash, locked_lock, pseudo_random_generator, ) iteration = TransitionResult(None, expired_lock_events) return iteration return TransitionResult(initiator_state, list())
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_onchain_secretreveal( initiator_state: InitiatorTransferState, state_change: ContractReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: """ When a secret is revealed on-chain all nodes learn the secret. This check the on-chain secret corresponds to the one used by the initiator, and if valid a new balance proof is sent to the next hop with the current lock removed from the pending locks and the transferred amount updated. """ iteration: TransitionResult[Optional[InitiatorTransferState]] secret = state_change.secret secrethash = initiator_state.transfer_description.secrethash is_valid_secret = is_valid_secret_reveal(state_change=state_change, transfer_secrethash=secrethash) is_channel_open = channel.get_status( channel_state) == ChannelState.STATE_OPENED is_lock_expired = state_change.block_number > initiator_state.transfer.lock.expiration is_lock_unlocked = is_valid_secret and not is_lock_expired if is_lock_unlocked: channel.register_onchain_secret( channel_state=channel_state, secret=secret, secrethash=secrethash, secret_reveal_block_number=state_change.block_number, ) lock = initiator_state.transfer.lock expired = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=lock.expiration, ) if is_lock_unlocked and is_channel_open and not expired: events = events_for_unlock_lock( initiator_state, channel_state, state_change.secret, state_change.secrethash, pseudo_random_generator, block_number, ) iteration = TransitionResult(None, events) else: events = list() iteration = TransitionResult(initiator_state, events) return iteration
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 handle_offchain_secretreveal( initiator_state: InitiatorTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[Optional[InitiatorTransferState]]: """ Once the next hop proves it knows the secret, the initiator can unlock the mediated transfer. This will validate the secret, and if valid a new balance proof is sent to the next hop with the current lock removed from the pending locks and the transferred amount updated. """ iteration: TransitionResult[Optional[InitiatorTransferState]] valid_reveal = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=initiator_state.transfer_description.secrethash, ) sent_by_partner = state_change.sender == channel_state.partner_state.address is_channel_open = channel.get_status( channel_state) == ChannelState.STATE_OPENED lock = initiator_state.transfer.lock expired = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=lock.expiration, ) if valid_reveal and is_channel_open and sent_by_partner and not expired: events = events_for_unlock_lock( initiator_state=initiator_state, channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) iteration = TransitionResult(None, events) else: events = list() iteration = TransitionResult(initiator_state, events) return iteration
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, block_hash: BlockHash, ) -> TransitionResult[TargetTransferState]: """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=channel.get_receiver_expiration_threshold( lock), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = TargetTransferState.EXPIRED events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state=target_state, channel_state=channel_state, block_number=block_number, block_hash=block_hash, ) return TransitionResult(target_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_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=typing.BlockNumber( lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS, ), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = 'expired' events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) return TransitionResult(target_state, events)
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=channel.get_receiver_expiration_threshold(lock), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = TargetTransferState.EXPIRED events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) return TransitionResult(target_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 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
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())