def events_for_onchain_secretreveal(target_state, channel_state, block_number): """ Emits the event for revealing the secret on-chain if the transfer cannot to be settled off-chain. """ transfer = target_state.transfer safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( channel_state.partner_state, transfer.lock.secrethash, ) return secret_registry.events_for_onchain_secretreveal( channel_state, block_number, secret, ) return list()
def handle_block(target_state, channel_state, block_number): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not secret_known and block_number > transfer.lock.expiration: # XXX: emit the event only once failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] elif target_state.state != 'waiting_close': # only emit the close event once # TODO: to be removed events = events_for_close(target_state, channel_state, block_number) events.extend( events_for_onchain_secretregister(target_state, channel_state, block_number)) else: events = list() iteration = TransitionResult(target_state, events) return iteration
def handle_block(target_state, channel_state, block_number): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not secret_known and block_number > transfer.lock.expiration: # XXX: emit the event only once failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] elif target_state.state != 'waiting_close': # only emit the close event once events = events_for_onchain_secretreveal(target_state, channel_state, block_number) else: events = list() iteration = TransitionResult(target_state, events) return iteration
def events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ Emits the event for revealing the secret on-chain if the transfer cannot to be settled off-chain. """ transfer = target_state.transfer expiration = transfer.lock.expiration safe_to_wait, _ = is_safe_to_wait( expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( channel_state.partner_state, transfer.lock.secrethash, ) return secret_registry.events_for_onchain_secretreveal( channel_state, secret, expiration, ) return list()
def events_for_close( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ Emits the event for closing the netting channel if the transfer needs to be settled on-chain. """ transfer = target_state.transfer safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: target_state.state = 'waiting_close' return channel.events_for_close(channel_state, block_number) return list()
def handle_block(target_state, channel_state, block_number): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.hashlock, ) if not secret_known and block_number > transfer.lock.expiration: # XXX: emit the event only once failed = EventWithdrawFailed( identifier=transfer.identifier, hashlock=transfer.lock.hashlock, reason='lock expired', ) target_state.state = 'expired' events = [failed] elif target_state.state != 'waiting_close': # only emit the close event once events = events_for_close(target_state, channel_state, block_number) else: events = list() iteration = TransitionResult(target_state, events) return iteration
def events_for_onchain_secretregister(target_state, channel_state, block_number): """ Emits the event for revealing the secret on-chain if the transfer cannot to be settled off-chain. """ transfer = target_state.transfer safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( channel_state.partner_state, transfer.lock.secrethash, ) return secret_registry.events_for_onchain_secretregister( channel_state, block_number, secret, ) return list()
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, pseudo_random_generator: random.Random, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not secret_known and block_number > transfer.lock.expiration: if target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] else: events = list() elif target_state.state != 'waiting_close': # only emit the close event once events = events_for_onchain_secretreveal(target_state, channel_state, block_number) else: events = list() iteration = TransitionResult(target_state, events) return iteration
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() secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) is_lock_expired = (not secret_known and block_number > transfer.lock.expiration) if secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) elif is_lock_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] return TransitionResult(target_state, events)
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_close(target_state, channel_state, block_number): """ Emits the event for closing the netting channel if the transfer needs to be settled on-chain. """ transfer = target_state.transfer safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: target_state.state = 'waiting_close' return channel.events_for_close(channel_state, block_number) return list()
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 events_for_onchain_secretreveal( channelidentifiers_to_channels, transfers_pair, block_number, ): """ Reveal the secret if a locks is in the unsafe region. """ events = list() pending_transfers_pairs = get_pending_transfer_pairs(transfers_pair) for pair in reversed(pending_transfers_pairs): payer_channel = get_payer_channel(channelidentifiers_to_channels, pair) expiration = pair.payer_transfer.lock.expiration safe_to_wait, _ = is_safe_to_wait( expiration, payer_channel.reveal_timeout, block_number, ) # We check if the secret is already known by receiving a ReceiveSecretReveal state change secret_known = channel.is_secret_known( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) reveal_events = secret_registry.events_for_onchain_secretreveal( payer_channel, secret, expiration, ) # short circuit because the secret needs to be revealed on-chain # only once return reveal_events return 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 events_for_onchain_secretreveal( channelidentifiers_to_channels, transfers_pair, block_number, ): """ Reveal secrets for transfer locks that are in the unsafe region. """ events = list() pending_transfers_pairs = get_pending_transfer_pairs(transfers_pair) for pair in reversed(pending_transfers_pairs): payer_channel = get_payer_channel(channelidentifiers_to_channels, pair) safe_to_wait = is_safe_to_wait( pair.payer_transfer.lock.expiration, payer_channel.reveal_timeout, block_number, ) # We check if the secret is already known by receiving a ReceiveSecretReveal state change secret_known = channel.is_secret_known( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) reveal_events = secret_registry.events_for_onchain_secretreveal( payer_channel, block_number, secret, ) events.extend(reveal_events) return events
def events_for_onchain_secretreveal( channelidentifiers_to_channels, transfers_pair, block_number, ): """ Reveal secrets for transfer locks that are in the unsafe region. """ events = list() pending_transfers_pairs = get_pending_transfer_pairs(transfers_pair) for pair in reversed(pending_transfers_pairs): payer_channel = get_payer_channel(channelidentifiers_to_channels, pair) safe_to_wait = is_safe_to_wait( pair.payer_transfer.lock.expiration, payer_channel.reveal_timeout, block_number, ) # We check if the secret is already known by receiving a ReceiveSecretReveal state change secret_known = channel.is_secret_known( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) if not safe_to_wait and secret_known: secret = channel.get_secret( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) reveal_events = secret_registry.events_for_onchain_secretreveal( payer_channel, block_number, secret, ) events.extend(reveal_events) return events
def test_regression_mediator_not_update_payer_state_twice(): """ Regression Test for https://github.com/raiden-network/raiden/issues/3086 Make sure that after a lock expired the mediator doesn't update the pair twice causing EventUnlockClaimFailed to be generated at every block. """ pseudo_random_generator = random.Random() pair = factories.mediator_make_channel_pair() payer_channel, payee_channel = pair.channels payer_route = factories.route_from_channel(payer_channel) payer_transfer = factories.make_signed_transfer_for( payer_channel, LONG_EXPIRATION) available_routes = [factories.route_from_channel(payee_channel)] init_state_change = ActionInitMediator( routes=available_routes, from_route=payer_route, from_transfer=payer_transfer, ) iteration = mediator.state_transition( mediator_state=None, state_change=init_state_change, channelidentifiers_to_channels=pair.channel_map, nodeaddresses_to_networkstates=pair.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=5, block_hash=factories.make_block_hash(), ) assert iteration.new_state is not None current_state = iteration.new_state send_transfer = search_for_item(iteration.events, SendLockedTransfer, {}) assert send_transfer transfer = send_transfer.transfer block_expiration_number = channel.get_sender_expiration_threshold( transfer.lock) block = Block( block_number=block_expiration_number, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = mediator.state_transition( mediator_state=current_state, state_change=block, channelidentifiers_to_channels=pair.channel_map, nodeaddresses_to_networkstates=pair.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, block_hash=factories.make_block_hash(), ) msg = 'At the expiration block we should get an EventUnlockClaimFailed' assert search_for_item(iteration.events, EventUnlockClaimFailed, {}), msg current_state = iteration.new_state next_block = Block( block_number=block_expiration_number + 1, gas_limit=1, block_hash=factories.make_transaction_hash(), ) # Initiator receives the secret reveal after the lock expired receive_secret = ReceiveSecretReveal( secret=UNIT_SECRET, sender=payee_channel.partner_state.address, ) iteration = mediator.state_transition( mediator_state=current_state, state_change=receive_secret, channelidentifiers_to_channels=pair.channel_map, nodeaddresses_to_networkstates=pair.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=next_block.block_number, block_hash=next_block.block_hash, ) current_state = iteration.new_state lock = payer_transfer.lock secrethash = lock.secrethash assert secrethash in payer_channel.partner_state.secrethashes_to_lockedlocks assert current_state.transfers_pair[0].payee_state == 'payee_expired' assert not channel.is_secret_known(payer_channel.partner_state, secrethash) safe_to_wait, _ = mediator.is_safe_to_wait( lock_expiration=lock.expiration, reveal_timeout=payer_channel.reveal_timeout, block_number=lock.expiration + 10, ) assert not safe_to_wait iteration = mediator.state_transition( mediator_state=current_state, state_change=next_block, channelidentifiers_to_channels=pair.channel_map, nodeaddresses_to_networkstates=pair.nodeaddresses_to_networkstates, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, block_hash=factories.make_block_hash(), ) msg = 'At the next block we should not get the same event' assert not search_for_item(iteration.events, EventUnlockClaimFailed, {}), msg
def test_regression_mediator_not_update_payer_state_twice(): """ Regression Test for https://github.com/raiden-network/raiden/issues/3086 Make sure that after a lock expired the mediator doesn't update the pair twice causing EventUnlockClaimFailed to be generated at every block. """ pseudo_random_generator = random.Random() pair = factories.mediator_make_channel_pair() payer_channel, payee_channel = pair.channels payer_route = factories.route_from_channel(payer_channel) payer_transfer = factories.make_signed_transfer_for(payer_channel, LONG_EXPIRATION) available_routes = [factories.route_from_channel(payee_channel)] init_state_change = ActionInitMediator( routes=available_routes, from_route=payer_route, from_transfer=payer_transfer, ) iteration = mediator.state_transition( mediator_state=None, state_change=init_state_change, channelidentifiers_to_channels=pair.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=5, ) assert iteration.new_state is not None current_state = iteration.new_state send_transfer = must_contain_entry(iteration.events, SendLockedTransfer, {}) assert send_transfer transfer = send_transfer.transfer block_expiration_number = channel.get_sender_expiration_threshold(transfer.lock) block = Block( block_number=block_expiration_number, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = mediator.state_transition( mediator_state=current_state, state_change=block, channelidentifiers_to_channels=pair.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, ) msg = 'At the expiration block we should get an EventUnlockClaimFailed' assert must_contain_entry(iteration.events, EventUnlockClaimFailed, {}), msg current_state = iteration.new_state next_block = Block( block_number=block_expiration_number + 1, gas_limit=1, block_hash=factories.make_transaction_hash(), ) # Initiator receives the secret reveal after the lock expired receive_secret = ReceiveSecretReveal( secret=UNIT_SECRET, sender=payee_channel.partner_state.address, ) iteration = mediator.state_transition( mediator_state=current_state, state_change=receive_secret, channelidentifiers_to_channels=pair.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=next_block.block_number, ) current_state = iteration.new_state lock = payer_transfer.lock secrethash = lock.secrethash assert secrethash in payer_channel.partner_state.secrethashes_to_lockedlocks assert current_state.transfers_pair[0].payee_state == 'payee_expired' assert not channel.is_secret_known(payer_channel.partner_state, secrethash) safe_to_wait, _ = mediator.is_safe_to_wait( lock_expiration=lock.expiration, reveal_timeout=payer_channel.reveal_timeout, block_number=lock.expiration + 10, ) assert not safe_to_wait iteration = mediator.state_transition( mediator_state=current_state, state_change=next_block, channelidentifiers_to_channels=pair.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, ) msg = 'At the next block we should not get the same event' assert not must_contain_entry(iteration.events, EventUnlockClaimFailed, {}), msg
def events_for_onchain_secretreveal_if_dangerzone( channelmap: typing.ChannelMap, secrethash: typing.SecretHash, transfers_pair: typing.List[MediationPairState], block_number: typing.BlockNumber, ) -> typing.List[Event]: """ Reveal the secret on-chain if the lock enters the unsafe region and the secret is not yet on-chain. """ events = list() all_payer_channels = [ get_payer_channel(channelmap, pair) for pair in transfers_pair ] transaction_sent = has_secret_registration_started( all_payer_channels, transfers_pair, secrethash, ) # Only consider the transfers which have a pair. This means if we have a # waiting transfer and for some reason the node knows the secret, it will # not try to register it. Otherwise it would be possible for an attacker to # reveal the secret late, just to force the node to send an unecessary # transaction. for pair in get_pending_transfer_pairs(transfers_pair): payer_channel = get_payer_channel(channelmap, pair) lock = pair.payer_transfer.lock safe_to_wait, _ = is_safe_to_wait( lock.expiration, payer_channel.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( payer_channel.partner_state, pair.payer_transfer.lock.secrethash, ) if not safe_to_wait and secret_known: pair.payer_state = 'payer_waiting_secret_reveal' if not transaction_sent: secret = channel.get_secret( payer_channel.partner_state, lock.secrethash, ) reveal_events = secret_registry.events_for_onchain_secretreveal( payer_channel, secret, lock.expiration, ) events.extend(reveal_events) transaction_sent = True return events
def test_regression_mediator_not_update_payer_state_twice(): """ Regression Test for https://github.com/raiden-network/raiden/issues/3086 Make sure that after a lock expired the mediator doesn't update the pair twice causing EventUnlockClaimFailed to be generated at every block. """ amount = 10 block_number = 5 initiator = HOP1 initiator_key = HOP1_KEY mediator_address = HOP2 target = HOP3 expiration = 30 pseudo_random_generator = random.Random() payer_channel = factories.make_channel( partner_balance=amount, our_balance=amount, our_address=mediator_address, partner_address=initiator, token_address=UNIT_TOKEN_ADDRESS, ) payer_route = factories.route_from_channel(payer_channel) payer_transfer = factories.make_signed_transfer_for( channel_state=payer_channel, amount=amount, initiator=initiator, target=target, expiration=expiration, secret=UNIT_SECRET, sender=initiator, pkey=initiator_key, ) payee_channel = factories.make_channel( our_balance=amount, our_address=mediator_address, partner_address=target, token_address=UNIT_TOKEN_ADDRESS, ) available_routes = [factories.route_from_channel(payee_channel)] channel_map = { payee_channel.identifier: payee_channel, payer_channel.identifier: payer_channel, } init_state_change = ActionInitMediator( routes=available_routes, from_route=payer_route, from_transfer=payer_transfer, ) initial_state = None iteration = mediator.state_transition( mediator_state=initial_state, state_change=init_state_change, channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) assert iteration.new_state is not None current_state = iteration.new_state send_transfer = must_contain_entry(iteration.events, SendLockedTransfer, {}) assert send_transfer transfer = send_transfer.transfer block_expiration_number = transfer.lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 block = Block( block_number=block_expiration_number, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = mediator.state_transition( mediator_state=current_state, state_change=block, channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, ) msg = 'At the expiration block we should get an EventUnlockClaimFailed' assert must_contain_entry(iteration.events, EventUnlockClaimFailed, {}), msg current_state = iteration.new_state next_block = Block( block_number=block_expiration_number + 1, gas_limit=1, block_hash=factories.make_transaction_hash(), ) # Initiator receives the secret reveal after the lock expired receive_secret = ReceiveSecretReveal( secret=UNIT_SECRET, sender=payee_channel.partner_state.address, ) iteration = mediator.state_transition( mediator_state=current_state, state_change=receive_secret, channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=next_block.block_number, ) current_state = iteration.new_state lock = payer_transfer.lock secrethash = lock.secrethash assert secrethash in payer_channel.partner_state.secrethashes_to_lockedlocks assert current_state.transfers_pair[0].payee_state == 'payee_expired' assert not channel.is_secret_known(payer_channel.partner_state, secrethash) safe_to_wait, _ = mediator.is_safe_to_wait( lock_expiration=lock.expiration, reveal_timeout=payer_channel.reveal_timeout, block_number=lock.expiration + 10, ) assert not safe_to_wait iteration = mediator.state_transition( mediator_state=current_state, state_change=next_block, channelidentifiers_to_channels=channel_map, pseudo_random_generator=pseudo_random_generator, block_number=block_expiration_number, ) msg = 'At the next block we should not get the same event' assert not must_contain_entry(iteration.events, EventUnlockClaimFailed, {}), msg