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 can not 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_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known_offchain: 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 handle_inittarget(state_change): """ Handles an ActionInitTarget state change. """ from_transfer = state_change.from_transfer from_route = state_change.from_route block_number = state_change.block_number state = TargetState( state_change.our_address, from_route, from_transfer, block_number, ) safe_to_wait = is_safe_to_wait( from_transfer, from_route.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if safe_to_wait: secret_request = SendSecretRequest( from_transfer.identifier, from_transfer.amount, from_transfer.hashlock, from_transfer.initiator, ) iteration = TransitionResult(state, [secret_request]) else: iteration = TransitionResult(state, list()) return iteration
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_inittarget(state_change): """ Handle an ActionInitTarget state change. """ from_transfer = state_change.from_transfer from_route = state_change.from_route block_number = state_change.block_number state = TargetState( state_change.our_address, from_route, from_transfer, block_number, ) safe_to_wait = is_safe_to_wait( from_transfer, from_route.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if safe_to_wait: secret_request = SendSecretRequest( from_transfer.identifier, from_transfer.amount, from_transfer.hashlock, from_transfer.initiator, ) iteration = TransitionResult(state, [secret_request]) else: iteration = TransitionResult(state, list()) return iteration
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 events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, block_hash: BlockHash, ) -> List[Event]: """ Emits the event for revealing the secret on-chain if the transfer can not 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_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash ) has_onchain_reveal_started = target_state.state == TargetTransferState.ONCHAIN_SECRET_REVEAL if not safe_to_wait and secret_known_offchain and not has_onchain_reveal_started: target_state.state = TargetTransferState.ONCHAIN_SECRET_REVEAL secret = channel.get_secret(channel_state.partner_state, transfer.lock.secrethash) assert secret, "secret should be known at this point" return secret_registry.events_for_onchain_secretreveal( channel_state=channel_state, secret=secret, expiration=expiration, block_hash=block_hash, ) return list()
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_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator queue_name = b'global' secret_request = SendSecretRequest( recipient, queue_name, message_identifier, transfer.payment_identifier, transfer.lock.amount, transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' withdraw_failed = EventWithdrawFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [withdraw_failed]) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait, unsafe_msg = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely unlock the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = unsafe_msg unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [unlock_failed]) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely unlock the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator queue_name = b'global' secret_request = SendSecretRequest( recipient, queue_name, message_identifier, transfer.payment_identifier, transfer.lock.amount, transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [unlock_failed]) return iteration
def test_is_safe_to_wait(): """ It's safe to wait for a secret while there are more than reveal timeout blocks until the lock expiration. """ amount = 10 expiration = 40 transfer = factories.make_transfer(amount, factories.HOP1, expiration) # expiration is in 30 blocks, 19 blocks safe for waiting block_number = 10 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # expiration is in 30 blocks, 09 blocks safe for waiting block_number = 20 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # expiration is in 30 blocks, 1 block safe for waiting block_number = 29 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # at the block 30 it's not safe to wait anymore block_number = 30 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False block_number = 40 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False block_number = 50 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False
def handle_send_secret_request_light( target_state: TargetTransferState, state_change: ActionSendSecretRequestLight, channel_state: NettingChannelState, block_number: BlockNumber ) -> TransitionResult[TargetTransferState]: """ Handles an ActionInitTarget state change. """ transfer = target_state.transfer assert channel_state.identifier == transfer.balance_proof.channel_identifier events = list() safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. # # We add a new validation. # It is verified that if there was an invoice it was paid successfully, # if it was not, the payment is interrupted # by not generating an event send secret request if safe_to_wait: secret_request_light = SendSecretRequestLight( sender=Address(target_state.transfer.target), recipient=Address(target_state.transfer.initiator), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=state_change.secret_request.message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, signed_secret_request=state_change.secret_request ) store_secret_request_event = StoreMessageEvent(state_change.secret_request.message_identifier, transfer.payment_identifier, 5, state_change.secret_request, True) events.append(secret_request_light) events.append(store_secret_request_event) iteration = TransitionResult(target_state, events) return iteration
def events_for_close(from_transfer, from_route, block_number): """ Emits the event for closing the netting channel if from_transfer needs to be settled on-chain. """ safe_to_wait = is_safe_to_wait( from_transfer, from_route.reveal_timeout, block_number, ) if not safe_to_wait: channel_close = ContractSendChannelClose(from_route.channel_address, ) return [channel_close] return list()
def handle_inittarget(state_change, channel_state, block_number): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, errormsg = channel.handle_receive_mediatedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: secret_request = SendSecretRequest( transfer.identifier, transfer.lock.amount, transfer.lock.hashlock, transfer.initiator, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' withdraw_failed = EventWithdrawFailed( identifier=transfer.identifier, hashlock=transfer.lock.hashlock, reason=failure_reason, ) iteration = TransitionResult(target_state, [withdraw_failed]) return iteration
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 events_for_close(state): """ Emits the event for closing the netting channel if from_transfer needs to be settled on-chain. """ from_transfer = state.from_transfer from_route = state.from_route safe_to_wait = is_safe_to_wait( from_transfer, from_route.reveal_timeout, state.block_number, ) secret_known = from_transfer.secret is not None if not safe_to_wait and secret_known: state.state = 'waiting_close' channel_close = ContractSendChannelClose( from_route.channel_address, from_transfer.token, ) return [channel_close] return list()
def events_for_close(state): """ Emits the event for closing the netting channel if from_transfer needs to be settled on-chain. """ from_transfer = state.from_transfer from_route = state.from_route safe_to_wait = is_safe_to_wait( from_transfer, from_route.reveal_timeout, state.block_number, ) secret_known = from_transfer.secret is not None if not safe_to_wait and secret_known: state.state = 'waiting_close' channel_close = ContractSendChannelClose( from_route.channel_address, from_transfer.token, ) return [channel_close] return list()
def events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, ): """ Emits the event for revealing the secret on-chain if the transfer can not 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_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash, ) has_onchain_reveal_started = ( target_state.state == TargetTransferState.ONCHAIN_SECRET_REVEAL ) if not safe_to_wait and secret_known_offchain and not has_onchain_reveal_started: target_state.state = TargetTransferState.ONCHAIN_SECRET_REVEAL 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 test_is_safe_to_wait(): """ It's safe to wait for a secret while there are more than reveal timeout blocks until the lock expiration. """ amount = 10 expiration = 40 initiator = factories.HOP1 target = factories.HOP2 transfer = factories.make_transfer(amount, initiator, target, expiration) # expiration is in 30 blocks, 19 blocks safe for waiting block_number = 10 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # expiration is in 30 blocks, 09 blocks safe for waiting block_number = 20 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # expiration is in 30 blocks, 1 block safe for waiting block_number = 29 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True # at the block 30 it's not safe to wait anymore block_number = 30 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False block_number = 40 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False block_number = 50 reveal_timeout = 10 assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False
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 handle_inittarget( state_change: ActionInitTarget, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. if safe_to_wait: message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) else: # 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. unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration
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
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 handle_inittarget( state_change: ActionInitTarget, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. if safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=typing.Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) else: # 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. unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration
def handle_inittarget_light( state_change: ActionInitTargetLight, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, storage ) -> TransitionResult[TargetTransferState]: """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg, handle_invoice_result = channel.handle_receive_lockedtransfer_light( channel_state, transfer, storage ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. # # We add a new validation. # It is verified that if there was an invoice it was paid successfully, # if it was not, the payment is interrupted # by not generating an event send secret request if safe_to_wait and handle_invoice_result['is_valid']: payment = LightClientPayment( state_change.transfer.target, state_change.transfer.initiator, False, channel_state.token_network_identifier, transfer.lock.amount, str(date.today()), LightClientPaymentStatus.Pending, transfer.payment_identifier ) payment_exists = LightClientService.get_light_client_payment(payment.payment_id, storage) if not payment_exists: LightClientMessageHandler.store_light_client_payment(payment, storage) message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) store_locked_transfer_event = StoreMessageEvent(transfer.message_identifier, transfer.payment_identifier, 1, state_change.signed_lockedtransfer, True) secret_request_message = SecretRequest.from_event(secret_request) store_secret_request_event = StoreMessageEvent(message_identifier, transfer.payment_identifier, 5, secret_request_message, False) channel_events.append(store_secret_request_event) channel_events.append(store_locked_transfer_event) iteration = TransitionResult(target_state, channel_events) else: # 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. assert errormsg, "handle_receive_lockedtransfer should return error msg if not valid" unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration